summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/CMakeLists.txt1
-rw-r--r--indra/cmake/00-Common.cmake4
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/WebRTC.cmake32
-rw-r--r--indra/llcommon/llmemory.cpp42
-rw-r--r--indra/llcommon/llprofilercategories.h21
-rw-r--r--indra/llcommon/llsys.cpp12
-rw-r--r--indra/llfilesystem/lldir.cpp8
-rw-r--r--indra/llfilesystem/lldir.h2
-rw-r--r--indra/llimage/llimageworker.cpp3
-rw-r--r--indra/llmath/llquaternion.h1
-rw-r--r--indra/llrender/llglslshader.cpp65
-rw-r--r--indra/llrender/llglslshader.h9
-rw-r--r--indra/llrender/llgltexture.cpp21
-rw-r--r--indra/llrender/llgltexture.h6
-rw-r--r--indra/llrender/llimagegl.cpp260
-rw-r--r--indra/llrender/llimagegl.h35
-rw-r--r--indra/llrender/llrender.cpp2
-rw-r--r--indra/llrender/llrender.h1
-rw-r--r--indra/llrender/llshadermgr.cpp2
-rw-r--r--indra/llrender/llshadermgr.h2
-rw-r--r--indra/llrender/llvertexbuffer.cpp106
-rw-r--r--indra/llrender/llvertexbuffer.h18
-rw-r--r--indra/llui/llaccordionctrl.cpp8
-rw-r--r--indra/llui/llaccordionctrltab.cpp6
-rw-r--r--indra/llui/llflatlistview.cpp49
-rw-r--r--indra/llwebrtc/CMakeLists.txt61
-rw-r--r--indra/llwebrtc/llwebrtc.cpp1345
-rw-r--r--indra/llwebrtc/llwebrtc.h278
-rw-r--r--indra/llwebrtc/llwebrtc_impl.h382
-rw-r--r--indra/llwindow/llwindow.cpp5
-rw-r--r--indra/llwindow/llwindow.h19
-rw-r--r--indra/llwindow/llwindowheadless.h2
-rw-r--r--indra/llwindow/llwindowmacosx.cpp3
-rw-r--r--indra/llwindow/llwindowmacosx.h5
-rw-r--r--indra/llwindow/llwindowwin32.cpp14
-rw-r--r--indra/llwindow/llwindowwin32.h10
-rw-r--r--indra/newview/CMakeLists.txt14
-rw-r--r--indra/newview/app_settings/settings.xml61
-rw-r--r--indra/newview/app_settings/shaders/class1/deferred/normgenF.glsl41
-rw-r--r--indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl34
-rw-r--r--indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl162
-rw-r--r--indra/newview/featuretable.txt2
-rw-r--r--indra/newview/featuretable_mac.txt2
-rw-r--r--indra/newview/gltf/animation.cpp98
-rw-r--r--indra/newview/gltf/animation.h3
-rw-r--r--indra/newview/gltf/asset.cpp299
-rw-r--r--indra/newview/gltf/asset.h127
-rw-r--r--indra/newview/gltf/buffer_util.h10
-rw-r--r--indra/newview/gltf/common.h14
-rw-r--r--indra/newview/gltf/primitive.cpp90
-rw-r--r--indra/newview/gltf/primitive.h21
-rw-r--r--indra/newview/gltfscenemanager.cpp355
-rw-r--r--indra/newview/gltfscenemanager.h7
-rw-r--r--indra/newview/llagent.cpp6
-rw-r--r--indra/newview/llappviewer.cpp8
-rw-r--r--indra/newview/llappviewer.h1
-rw-r--r--indra/newview/llappviewerwin32.cpp6
-rw-r--r--indra/newview/llavataractions.cpp7
-rw-r--r--indra/newview/llconversationview.cpp2
-rw-r--r--indra/newview/lldebugview.cpp6
-rw-r--r--indra/newview/lldrawpool.cpp7
-rw-r--r--indra/newview/lldrawpoolalpha.cpp2
-rw-r--r--indra/newview/lldrawpoolbump.cpp388
-rw-r--r--indra/newview/lldrawpoolbump.h5
-rw-r--r--indra/newview/lldrawpoolpbropaque.cpp5
-rw-r--r--indra/newview/llfloateravatar.cpp5
-rw-r--r--indra/newview/llfloaterchangeitemthumbnail.cpp22
-rw-r--r--indra/newview/llfloaterdestinations.cpp5
-rw-r--r--indra/newview/llfloaterfonttest.cpp5
-rw-r--r--indra/newview/llfloatergltfasseteditor.cpp527
-rw-r--r--indra/newview/llfloatergltfasseteditor.h101
-rw-r--r--indra/newview/llfloaterimsession.cpp13
-rw-r--r--indra/newview/llfloaterimsession.h8
-rw-r--r--indra/newview/llgltffolderitem.cpp164
-rw-r--r--indra/newview/llgltffolderitem.h128
-rw-r--r--indra/newview/llgltffoldermodel.cpp73
-rw-r--r--indra/newview/llgltffoldermodel.h91
-rw-r--r--indra/newview/llgroupactions.cpp2
-rw-r--r--indra/newview/llheroprobemanager.cpp50
-rw-r--r--indra/newview/llheroprobemanager.h3
-rw-r--r--indra/newview/llimview.cpp505
-rw-r--r--indra/newview/llimview.h30
-rw-r--r--indra/newview/lllocalbitmaps.cpp1
-rw-r--r--indra/newview/llmeshrepository.h2
-rw-r--r--indra/newview/llmodelpreview.cpp26
-rw-r--r--indra/newview/llnetmap.cpp20
-rw-r--r--indra/newview/llpanelgroup.cpp2
-rw-r--r--indra/newview/llpanelgroup.h2
-rw-r--r--indra/newview/llpanelpeople.cpp2
-rw-r--r--indra/newview/llpanelpeople.h2
-rw-r--r--indra/newview/llpanelprofile.cpp50
-rw-r--r--indra/newview/llpanelprofile.h2
-rw-r--r--indra/newview/llpanelvoicedevicesettings.cpp67
-rw-r--r--indra/newview/llpanelvolume.cpp25
-rw-r--r--indra/newview/llreflectionmap.cpp35
-rw-r--r--indra/newview/llspeakers.cpp12
-rw-r--r--indra/newview/llspeakers.h1
-rw-r--r--indra/newview/llsurface.cpp169
-rw-r--r--indra/newview/llsurface.h11
-rw-r--r--indra/newview/llsurfacepatch.cpp9
-rw-r--r--indra/newview/lltextureview.cpp67
-rw-r--r--indra/newview/llviewercamera.cpp14
-rw-r--r--indra/newview/llviewercamera.h5
-rw-r--r--indra/newview/llviewercontrol.cpp3
-rw-r--r--indra/newview/llviewerfloaterreg.cpp2
-rw-r--r--indra/newview/llviewerhelp.cpp2
-rw-r--r--indra/newview/llviewermedia.cpp65
-rw-r--r--indra/newview/llviewermenu.cpp29
-rw-r--r--indra/newview/llviewerobject.cpp4
-rw-r--r--indra/newview/llviewerobject.h1
-rwxr-xr-xindra/newview/llviewerregion.cpp13
-rw-r--r--indra/newview/llviewerregion.h3
-rw-r--r--indra/newview/llviewershadermgr.cpp13
-rw-r--r--indra/newview/llviewertexture.cpp360
-rw-r--r--indra/newview/llviewertexture.h26
-rw-r--r--indra/newview/llviewertexturelist.cpp24
-rw-r--r--indra/newview/llviewertexturelist.h18
-rw-r--r--indra/newview/llviewerwindow.cpp146
-rw-r--r--indra/newview/llviewerwindow.h11
-rw-r--r--indra/newview/llvlcomposition.cpp412
-rw-r--r--indra/newview/llvlcomposition.h2
-rw-r--r--indra/newview/llvoavatar.cpp30
-rw-r--r--indra/newview/llvoavatar.h2
-rw-r--r--indra/newview/llvocache.cpp1
-rw-r--r--indra/newview/llvoicechannel.cpp244
-rw-r--r--indra/newview/llvoicechannel.h95
-rw-r--r--indra/newview/llvoiceclient.cpp678
-rw-r--r--indra/newview/llvoiceclient.h142
-rw-r--r--indra/newview/llvoicevivox.cpp538
-rw-r--r--indra/newview/llvoicevivox.h193
-rw-r--r--indra/newview/llvoicewebrtc.cpp3087
-rw-r--r--indra/newview/llvoicewebrtc.h743
-rw-r--r--indra/newview/llvovolume.cpp37
-rw-r--r--indra/newview/llvovolume.h2
-rw-r--r--indra/newview/llvowater.cpp15
-rw-r--r--indra/newview/llvowater.h3
-rw-r--r--indra/newview/llworld.cpp2
-rw-r--r--indra/newview/llworldmipmap.cpp1
-rw-r--r--indra/newview/llworldmipmap.h6
-rw-r--r--indra/newview/pipeline.cpp3
-rw-r--r--indra/newview/skins/default/xui/de/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml284
-rw-r--r--indra/newview/skins/default/xui/en/floater_incoming_call.xml2
-rw-r--r--indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml30
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml48
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml14
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_sound.xml68
-rw-r--r--indra/newview/skins/default/xui/es/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/it/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/ja/menu_viewer.xml1
-rw-r--r--indra/newview/skins/default/xui/pl/menu_viewer.xml5
-rw-r--r--indra/newview/skins/default/xui/pt/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/ru/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/tr/menu_viewer.xml6
-rw-r--r--indra/newview/skins/default/xui/zh/menu_viewer.xml6
-rwxr-xr-xindra/newview/viewer_manifest.py19
157 files changed, 10545 insertions, 3804 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 500ffa3e8b..422927704a 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -54,6 +54,7 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llmessage)
add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive)
add_subdirectory(${LIBS_OPEN_PREFIX}llrender)
add_subdirectory(${LIBS_OPEN_PREFIX}llfilesystem)
+add_subdirectory(${LIBS_OPEN_PREFIX}llwebrtc)
add_subdirectory(${LIBS_OPEN_PREFIX}llwindow)
add_subdirectory(${LIBS_OPEN_PREFIX}llxml)
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index dbbf12bd3d..07deaa56b0 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -190,6 +190,10 @@ if (LINUX OR DARWIN)
list(APPEND GCC_WARNINGS -Wno-reorder -Wno-non-virtual-dtor )
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13)
+ list(APPEND GCC_WARNINGS -Wno-unused-but-set-variable -Wno-unused-variable )
+ endif()
+
add_compile_options(${GCC_WARNINGS})
add_compile_options(-m${ADDRESS_SIZE})
endif (LINUX OR DARWIN)
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index 32ce6fab92..0f338931c0 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES
ViewerMiscLibs.cmake
VisualLeakDetector.cmake
LibVLCPlugin.cmake
+ WebRTC.cmake
XmlRpcEpi.cmake
xxHash.cmake
ZLIBNG.cmake
diff --git a/indra/cmake/WebRTC.cmake b/indra/cmake/WebRTC.cmake
new file mode 100644
index 0000000000..230522a40a
--- /dev/null
+++ b/indra/cmake/WebRTC.cmake
@@ -0,0 +1,32 @@
+# -*- cmake -*-
+include(Linking)
+include(Prebuilt)
+
+include_guard()
+
+add_library( ll::webrtc INTERFACE IMPORTED )
+target_include_directories( ll::webrtc SYSTEM INTERFACE "${LIBS_PREBUILT_DIR}/include/webrtc" "${LIBS_PREBUILT_DIR}/include/webrtc/third_party/abseil-cpp")
+use_prebuilt_binary(webrtc)
+
+if (WINDOWS)
+ target_link_libraries( ll::webrtc INTERFACE webrtc.lib )
+elseif (DARWIN)
+ FIND_LIBRARY(COREAUDIO_LIBRARY CoreAudio)
+ FIND_LIBRARY(COREGRAPHICS_LIBRARY CoreGraphics)
+ FIND_LIBRARY(AUDIOTOOLBOX_LIBRARY AudioToolbox)
+ FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation)
+ FIND_LIBRARY(COCOA_LIBRARY Cocoa)
+
+ target_link_libraries( ll::webrtc INTERFACE
+ libwebrtc.a
+ ${COREAUDIO_LIBRARY}
+ ${AUDIOTOOLBOX_LIBRARY}
+ ${COREGRAPHICS_LIBRARY}
+ ${COREFOUNDATION_LIBRARY}
+ ${COCOA_LIBRARY}
+ )
+elseif (LINUX)
+ target_link_libraries( ll::webrtc INTERFACE libwebrtc.a X11 )
+endif (WINDOWS)
+
+
diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp
index 4b7d60d654..104c40f0d7 100644
--- a/indra/llcommon/llmemory.cpp
+++ b/indra/llcommon/llmemory.cpp
@@ -39,6 +39,7 @@
#elif LL_LINUX
# include <unistd.h>
# include <sys/resource.h>
+# include <sys/sysinfo.h>
#endif
#include "llmemory.h"
@@ -85,6 +86,7 @@ void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size)
void LLMemory::updateMemoryInfo()
{
LL_PROFILE_ZONE_SCOPED
+ U32Kilobytes avail_phys;
#if LL_WINDOWS
PROCESS_MEMORY_COUNTERS counters;
@@ -95,22 +97,10 @@ void LLMemory::updateMemoryInfo()
}
sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize));
- sample(sAllocatedMem, sAllocatedMemInKB);
sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage));
sample(sVirtualMem, sAllocatedPageSizeInKB);
-
- U32Kilobytes avail_phys, avail_virtual;
+ U32Kilobytes avail_virtual;
LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ;
- sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB);
-
- if(sMaxPhysicalMemInKB > sAllocatedMemInKB)
- {
- sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ;
- }
- else
- {
- sAvailPhysicalMemInKB = U32Kilobytes(0);
- }
#elif defined(LL_DARWIN)
task_vm_info info;
@@ -147,21 +137,39 @@ void LLMemory::updateMemoryInfo()
if (result == KERN_SUCCESS) {
// This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
// Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
- sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
- sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
- }
+ avail_phys = U32Bytes(vmstat.free_count * page_size);
+ sMaxHeapSizeInKB = LLMemoryInfo::getHardwareMemSize();
+ }
else
{
LL_WARNS() << "task_info failed" << LL_ENDL;
}
-
+#elif defined(LL_LINUX)
+ // Use sysinfo() to get the total physical memory.
+ struct sysinfo info;
+ sysinfo(&info);
+ sMaxHeapSizeInKB = U32Kilobytes::convert((U64Bytes)info.totalram); // Total RAM in system
+ avail_phys = U32Kilobytes::convert((U64Bytes)info.freeram); // Total Free RAM in system
+ sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(LLMemory::getCurrentRSS())); // represents the RAM allocated by this process only (in line with the windows implementation)
#else
//not valid for other systems for now.
+ LL_WARNS() << "LLMemory::updateMemoryInfo() not implemented for this platform." << LL_ENDL;
sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());
sMaxPhysicalMemInKB = U64Bytes(U32_MAX);
sAvailPhysicalMemInKB = U64Bytes(U32_MAX);
#endif
+ sample(sAllocatedMem, sAllocatedMemInKB);
+ // sMaxPhysicalMem - max this process can use = the lesser of (what we already have + what's available) or MaxHeap
+ sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB);
+ if(sMaxPhysicalMemInKB > sAllocatedMemInKB)
+ {
+ sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ;
+ }
+ else
+ {
+ sAvailPhysicalMemInKB = U32Kilobytes(0);
+ }
return ;
}
diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h
index 617431f629..1c4f0f5624 100644
--- a/indra/llcommon/llprofilercategories.h
+++ b/indra/llcommon/llprofilercategories.h
@@ -1,5 +1,5 @@
/**
- * @file llprofiler_ategories.h
+ * @file llprofilercategories.h
* @brief Profiling categories to minimize Tracy memory usage when viewing captures.
*
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
@@ -33,7 +33,7 @@
// LL_PROFILER_CATEGORY_ENABLE_DRAWPOOL
// LL_PROFILER_CATEGORY_ENABLE_LLSD
// LL_PROFILER_CATEGORY_ENABLE_MEMORY
-// LL_PROFILER_CATEGORY_ENABLE_SHADERS
+// LL_PROFILER_CATEGORY_ENABLE_SHADER
//
// NOTE: You can still manually use:
// LL_PROFILE_ZONE_SCOPED();
@@ -67,6 +67,8 @@
#define LL_PROFILER_CATEGORY_ENABLE_VERTEX 1
#define LL_PROFILER_CATEGORY_ENABLE_VOLUME 1
#define LL_PROFILER_CATEGORY_ENABLE_WIN32 1
+#define LL_PROFILER_CATEGORY_ENABLE_GLTF 1
+#define LL_PROFILER_CATEGORY_ENABLE_VOICE 1
#if LL_PROFILER_CATEGORY_ENABLE_APP
#define LL_PROFILE_ZONE_NAMED_CATEGORY_APP LL_PROFILE_ZONE_NAMED
@@ -276,5 +278,20 @@
#define LL_PROFILE_ZONE_SCOPED_CATEGORY_WIN32
#endif
+#if LL_PROFILER_CATEGORY_ENABLE_GLTF
+ #define LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF LL_PROFILE_ZONE_NAMED
+ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF LL_PROFILE_ZONE_SCOPED
+#else
+ #define LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF(name)
+ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF
+#endif
+#if LL_PROFILER_CATEGORY_ENABLE_VOICE
+ #define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE LL_PROFILE_ZONE_NAMED
+ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE LL_PROFILE_ZONE_SCOPED
+#else
+ #define LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE(name)
+ #define LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+#endif
+
#endif // LL_PROFILER_CATEGORIES_H
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index 496ec0d869..cfb05873df 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -226,16 +226,8 @@ LLOSInfo::LLOSInfo() :
if (mBuild >= 22000)
{
// At release Windows 11 version was 10.0.22000.194
- // Windows 10 version was 10.0.19043.1266
- // There is no warranty that Win10 build won't increase,
- // so until better solution is found or Microsoft updates
- // SDK with IsWindows11OrGreater(), indicate "10/11"
- //
- // Current alternatives:
- // Query WMI's Win32_OperatingSystem for OS string. Slow
- // and likely to return 'compatibility' string.
- // Check presence of dlls/libs or may be their version.
- mOSStringSimple = "Microsoft Windows 10/11 ";
+ // According to microsoft win 10 won't ever get that far.
+ mOSStringSimple = "Microsoft Windows 11 ";
}
}
diff --git a/indra/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp
index 483ef0fd02..8ee2c309a5 100644
--- a/indra/llfilesystem/lldir.cpp
+++ b/indra/llfilesystem/lldir.cpp
@@ -876,15 +876,15 @@ std::string LLDir::getTempFilename() const
}
// static
-std::string LLDir::getScrubbedFileName(const std::string uncleanFileName)
+std::string LLDir::getScrubbedFileName(std::string_view uncleanFileName)
{
std::string name(uncleanFileName);
const std::string illegalChars(getForbiddenFileChars());
// replace any illegal file chars with and underscore '_'
- for( unsigned int i = 0; i < illegalChars.length(); i++ )
+ for (const char& ch : illegalChars)
{
- std::string::size_type j = -1;
- while((j = name.find(illegalChars[i])) > std::string::npos)
+ std::string::size_type j{ 0 };
+ while ((j = name.find(ch, j)) != std::string::npos)
{
name[j] = '_';
}
diff --git a/indra/llfilesystem/lldir.h b/indra/llfilesystem/lldir.h
index be82f55e46..b0d2b6aada 100644
--- a/indra/llfilesystem/lldir.h
+++ b/indra/llfilesystem/lldir.h
@@ -178,7 +178,7 @@ class LLDir
static std::string getDumpLogsDirPath(const std::string &file_name = "");
// For producing safe download file names from potentially unsafe ones
- static std::string getScrubbedFileName(const std::string uncleanFileName);
+ static std::string getScrubbedFileName(std::string_view uncleanFileName);
static std::string getForbiddenFileChars();
void setDumpDir( const std::string& path );
diff --git a/indra/llimage/llimageworker.cpp b/indra/llimage/llimageworker.cpp
index accbd9964b..bdaef0c653 100644
--- a/indra/llimage/llimageworker.cpp
+++ b/indra/llimage/llimageworker.cpp
@@ -97,6 +97,9 @@ LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
U32 decode_id = ++mDecodeCount;
+ if (decode_id == 0)
+ decode_id = ++mDecodeCount;
+
// Instantiate the ImageRequest right in the lambda, why not?
bool posted = mThreadPool->getQueue().post(
[req = ImageRequest(image, discard, needs_aux, responder, decode_id)]
diff --git a/indra/llmath/llquaternion.h b/indra/llmath/llquaternion.h
index 2caa993007..6136c59ed1 100644
--- a/indra/llmath/llquaternion.h
+++ b/indra/llmath/llquaternion.h
@@ -132,6 +132,7 @@ public:
friend LLQuaternion operator~(const LLQuaternion &a); // Returns a* (Conjugate of a)
bool operator==(const LLQuaternion &b) const; // Returns a == b
bool operator!=(const LLQuaternion &b) const; // Returns a != b
+ F64 operator[](int idx) const { return mQ[idx]; }
friend const LLQuaternion& operator*=(LLQuaternion &a, const LLQuaternion &b); // Returns a * b
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 79979657f1..25e4a88f28 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -54,6 +54,8 @@ using std::string;
GLuint LLGLSLShader::sCurBoundShader = 0;
LLGLSLShader* LLGLSLShader::sCurBoundShaderPtr = NULL;
S32 LLGLSLShader::sIndexedTextureChannels = 0;
+U32 LLGLSLShader::sMaxGLTFMaterials = 0;
+U32 LLGLSLShader::sMaxGLTFNodes = 0;
bool LLGLSLShader::sProfileEnabled = false;
std::set<LLGLSLShader*> LLGLSLShader::sInstances;
LLGLSLShader::defines_map_t LLGLSLShader::sGlobalDefines;
@@ -978,7 +980,9 @@ bool LLGLSLShader::mapUniforms()
const char* ubo_names[] =
{
"ReflectionProbes", // UB_REFLECTION_PROBES
- "GLTFJoints", // UB_GLTF_JOINTS
+ "GLTFJoints", // UB_GLTF_JOINTS
+ "GLTFNodes", // UB_GLTF_NODES
+ "GLTFMaterials", // UB_GLTF_MATERIALS
};
llassert(LL_ARRAY_SIZE(ubo_names) == NUM_UNIFORM_BLOCKS);
@@ -1099,7 +1103,8 @@ S32 LLGLSLShader::bindTexture(S32 uniform, LLTexture* texture, LLTexUnit::eTextu
if (uniform < 0 || uniform >= (S32)mTexture.size())
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform out of range: " << uniform << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
return -1;
}
@@ -1120,6 +1125,8 @@ S32 LLGLSLShader::bindTexture(S32 uniform, LLRenderTarget* texture, bool depth,
if (uniform < 0 || uniform >= (S32)mTexture.size())
{
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
return -1;
}
@@ -1167,7 +1174,8 @@ S32 LLGLSLShader::unbindTexture(S32 uniform, LLTexUnit::eTextureType mode)
if (uniform < 0 || uniform >= (S32)mTexture.size())
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform out of range: " << uniform << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
return -1;
}
@@ -1192,7 +1200,8 @@ S32 LLGLSLShader::enableTexture(S32 uniform, LLTexUnit::eTextureType mode, LLTex
if (uniform < 0 || uniform >= (S32)mTexture.size())
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform out of range: " << uniform << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
return -1;
}
@@ -1213,7 +1222,8 @@ S32 LLGLSLShader::disableTexture(S32 uniform, LLTexUnit::eTextureType mode, LLTe
if (uniform < 0 || uniform >= (S32)mTexture.size())
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform out of range: " << uniform << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
return -1;
}
S32 index = mTexture[uniform];
@@ -1244,7 +1254,8 @@ void LLGLSLShader::uniform1i(U32 index, GLint x)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1269,7 +1280,8 @@ void LLGLSLShader::uniform1f(U32 index, GLfloat x)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1304,7 +1316,8 @@ void LLGLSLShader::uniform2f(U32 index, GLfloat x, GLfloat y)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1330,7 +1343,8 @@ void LLGLSLShader::uniform3f(U32 index, GLfloat x, GLfloat y, GLfloat z)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1356,7 +1370,8 @@ void LLGLSLShader::uniform4f(U32 index, GLfloat x, GLfloat y, GLfloat z, GLfloat
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1382,7 +1397,8 @@ void LLGLSLShader::uniform1iv(U32 index, U32 count, const GLint* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1408,7 +1424,8 @@ void LLGLSLShader::uniform4iv(U32 index, U32 count, const GLint* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1435,7 +1452,8 @@ void LLGLSLShader::uniform1fv(U32 index, U32 count, const GLfloat* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1461,7 +1479,8 @@ void LLGLSLShader::uniform2fv(U32 index, U32 count, const GLfloat* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1487,7 +1506,8 @@ void LLGLSLShader::uniform3fv(U32 index, U32 count, const GLfloat* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1513,7 +1533,8 @@ void LLGLSLShader::uniform4fv(U32 index, U32 count, const GLfloat* v)
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1540,7 +1561,8 @@ void LLGLSLShader::uniformMatrix2fv(U32 index, U32 count, GLboolean transpose, c
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1560,7 +1582,8 @@ void LLGLSLShader::uniformMatrix3fv(U32 index, U32 count, GLboolean transpose, c
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1580,7 +1603,8 @@ void LLGLSLShader::uniformMatrix3x4fv(U32 index, U32 count, GLboolean transpose,
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
@@ -1600,7 +1624,8 @@ void LLGLSLShader::uniformMatrix4fv(U32 index, U32 count, GLboolean transpose, c
{
if (mUniform.size() <= index)
{
- LL_SHADER_UNIFORM_ERRS() << "Uniform index out of bounds." << LL_ENDL;
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << index << LL_ENDL;
+ llassert(false);
return;
}
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index cc2ba0fcff..86e5625dca 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -147,8 +147,10 @@ public:
enum UniformBlock : GLuint
{
- UB_REFLECTION_PROBES,
- UB_GLTF_JOINTS,
+ UB_REFLECTION_PROBES, // "ReflectionProbes"
+ UB_GLTF_JOINTS, // "GLTFJoints"
+ UB_GLTF_NODES, // "GLTFNodes"
+ UB_GLTF_MATERIALS, // "GLTFMaterials"
NUM_UNIFORM_BLOCKS
};
@@ -163,6 +165,9 @@ public:
static LLGLSLShader* sCurBoundShaderPtr;
static S32 sIndexedTextureChannels;
+ static U32 sMaxGLTFMaterials;
+ static U32 sMaxGLTFNodes;
+
static void initProfile();
static void finishProfile(bool emit_report = true);
diff --git a/indra/llrender/llgltexture.cpp b/indra/llrender/llgltexture.cpp
index e614f45986..4dcca5a726 100644
--- a/indra/llrender/llgltexture.cpp
+++ b/indra/llrender/llgltexture.cpp
@@ -351,20 +351,6 @@ void LLGLTexture::forceUpdateBindStats(void) const
return mGLTexturep->forceUpdateBindStats() ;
}
-U32 LLGLTexture::getTexelsInAtlas() const
-{
- llassert(mGLTexturep.notNull()) ;
-
- return mGLTexturep->getTexelsInAtlas() ;
-}
-
-U32 LLGLTexture::getTexelsInGLTexture() const
-{
- llassert(mGLTexturep.notNull()) ;
-
- return mGLTexturep->getTexelsInGLTexture() ;
-}
-
bool LLGLTexture::isGLTextureCreated() const
{
llassert(mGLTexturep.notNull()) ;
@@ -372,13 +358,6 @@ bool LLGLTexture::isGLTextureCreated() const
return mGLTexturep->isGLTextureCreated() ;
}
-S32 LLGLTexture::getDiscardLevelInAtlas() const
-{
- llassert(mGLTexturep.notNull()) ;
-
- return mGLTexturep->getDiscardLevelInAtlas() ;
-}
-
void LLGLTexture::destroyGLTexture()
{
if(mGLTexturep.notNull() && mGLTexturep->getHasGLTexture())
diff --git a/indra/llrender/llgltexture.h b/indra/llrender/llgltexture.h
index 0901243f8f..a7de20dc5c 100644
--- a/indra/llrender/llgltexture.h
+++ b/indra/llrender/llgltexture.h
@@ -51,10 +51,10 @@ public:
BOOST_NONE = 0,
BOOST_AVATAR ,
BOOST_AVATAR_BAKED ,
- BOOST_SCULPTED ,
BOOST_TERRAIN , // Needed for minimap generation for now. Lower than BOOST_HIGH so the texture stats don't get forced, i.e. texture stats are manually managed by minimap/terrain instead.
BOOST_HIGH = 10,
+ BOOST_SCULPTED ,
BOOST_BUMP ,
BOOST_UNUSED_1 , // Placeholder to avoid disrupting habits around texture debug
BOOST_SELECTED ,
@@ -75,7 +75,6 @@ public:
AVATAR_SCRATCH_TEX,
DYNAMIC_TEX,
MEDIA,
- ATLAS,
OTHER,
MAX_GL_IMAGE_CATEGORY
};
@@ -156,10 +155,7 @@ public:
bool isJustBound()const ;
void forceUpdateBindStats(void) const;
- U32 getTexelsInAtlas() const ;
- U32 getTexelsInGLTexture() const ;
bool isGLTextureCreated() const ;
- S32 getDiscardLevelInAtlas() const ;
LLGLTextureState getTextureState() const { return mTextureState; }
//---------------------------------------------------------------------------------------------
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 7e5cd628c1..956bcef352 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -41,6 +41,7 @@
#include "llrender.h"
#include "llwindow.h"
#include "llframetimer.h"
+#include <unordered_set>
extern LL_COMMON_API bool on_main_thread();
@@ -130,10 +131,9 @@ S32 LLImageGL::sCount = 0;
bool LLImageGL::sGlobalUseAnisotropic = false;
F32 LLImageGL::sLastFrameTime = 0.f;
-bool LLImageGL::sAllowReadBackRaw = false ;
LLImageGL* LLImageGL::sDefaultGLTexture = NULL ;
bool LLImageGL::sCompressTextures = false;
-std::set<LLImageGL*> LLImageGL::sImageList;
+std::unordered_set<LLImageGL*> LLImageGL::sImageList;
bool LLImageGLThread::sEnabledTextures = false;
@@ -150,6 +150,9 @@ S32 LLImageGL::sMaxCategories = 1 ;
//optimization for when we don't need to calculate mIsMask
bool LLImageGL::sSkipAnalyzeAlpha;
+U32 LLImageGL::sScratchPBO = 0;
+U32 LLImageGL::sScratchPBOSize = 0;
+
//------------------------
//****************************************************************************************************
@@ -159,20 +162,6 @@ bool LLImageGL::sSkipAnalyzeAlpha;
//**************************************************************************************
//below are functions for debug use
//do not delete them even though they are not currently being used.
-void check_all_images()
-{
- for (std::set<LLImageGL*>::iterator iter = LLImageGL::sImageList.begin();
- iter != LLImageGL::sImageList.end(); iter++)
- {
- LLImageGL* glimage = *iter;
- if (glimage->getTexName() && glimage->isGLTextureCreated())
- {
- gGL.getTexUnit(0)->bind(glimage) ;
- glimage->checkTexSize() ;
- gGL.getTexUnit(0)->unbind(glimage->getTarget()) ;
- }
- }
-}
void LLImageGL::checkTexSize(bool forced) const
{
@@ -252,6 +241,11 @@ void LLImageGL::initClass(LLWindow* window, S32 num_catagories, bool skip_analyz
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
sSkipAnalyzeAlpha = skip_analyze_alpha;
+ if (sScratchPBO == 0)
+ {
+ glGenBuffers(1, &sScratchPBO);
+ }
+
if (thread_texture_loads || thread_media_updates)
{
LLImageGLThread::createInstance(window);
@@ -265,6 +259,12 @@ void LLImageGL::cleanupClass()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
LLImageGLThread::deleteSingleton();
+ if (sScratchPBO != 0)
+ {
+ glDeleteBuffers(1, &sScratchPBO);
+ sScratchPBO = 0;
+ sScratchPBOSize = 0;
+ }
}
@@ -360,66 +360,19 @@ void LLImageGL::updateStats(F32 current_time)
//----------------------------------------------------------------------------
//static
-void LLImageGL::destroyGL(bool save_state)
+void LLImageGL::destroyGL()
{
for (S32 stage = 0; stage < gGLManager.mNumTextureImageUnits; stage++)
{
gGL.getTexUnit(stage)->unbind(LLTexUnit::TT_TEXTURE);
}
-
- sAllowReadBackRaw = true ;
- for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
- iter != sImageList.end(); iter++)
- {
- LLImageGL* glimage = *iter;
- if (glimage->mTexName)
- {
- if (save_state && glimage->isGLTextureCreated() && glimage->mComponents)
- {
- glimage->mSaveData = new LLImageRaw;
- if(!glimage->readBackRaw(glimage->mCurrentDiscardLevel, glimage->mSaveData, false)) //necessary, keep it.
- {
- glimage->mSaveData = NULL ;
- }
- }
-
- glimage->destroyGLTexture();
- stop_glerror();
- }
- }
- sAllowReadBackRaw = false ;
-}
-
-//static
-void LLImageGL::restoreGL()
-{
- for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
- iter != sImageList.end(); iter++)
- {
- LLImageGL* glimage = *iter;
- if(glimage->getTexName())
- {
- LL_ERRS() << "tex name is not 0." << LL_ENDL ;
- }
- if (glimage->mSaveData.notNull())
- {
- if (glimage->getComponents() && glimage->mSaveData->getComponents())
- {
- glimage->createGLTexture(glimage->mCurrentDiscardLevel, glimage->mSaveData, 0, true, glimage->getCategory());
- stop_glerror();
- }
- glimage->mSaveData = NULL; // deletes data
- }
- }
}
//static
void LLImageGL::dirtyTexOptions()
{
- for (std::set<LLImageGL*>::iterator iter = sImageList.begin();
- iter != sImageList.end(); iter++)
+ for (auto& glimage : sImageList)
{
- LLImageGL* glimage = *iter;
glimage->mTexOptionsDirty = true;
stop_glerror();
}
@@ -542,10 +495,6 @@ void LLImageGL::init(bool usemipmaps)
mHeight = 0;
mCurrentDiscardLevel = -1;
- mDiscardLevelInAtlas = -1 ;
- mTexelsInAtlas = 0 ;
- mTexelsInGLTexture = 0 ;
-
mAllowCompression = true;
mTarget = GL_TEXTURE_2D;
@@ -622,9 +571,6 @@ bool LLImageGL::setSize(S32 width, S32 height, S32 ncomponents, S32 discard_leve
return false;
}
- // pickmask validity depends on old image size, delete it
- freePickMask();
-
mWidth = width;
mHeight = height;
mComponents = ncomponents;
@@ -1025,98 +971,6 @@ bool LLImageGL::setImage(const U8* data_in, bool data_hasmips /* = false */, S32
return true;
}
-bool LLImageGL::preAddToAtlas(S32 discard_level, const LLImageRaw* raw_image)
-{
- //not compatible with core GL profile
- llassert(!LLRender::sGLCoreProfile);
-
- if (gGLManager.mIsDisabled)
- {
- LL_WARNS() << "Trying to create a texture while GL is disabled!" << LL_ENDL;
- return false;
- }
- llassert(gGLManager.mInited);
- stop_glerror();
-
- if (discard_level < 0)
- {
- llassert(mCurrentDiscardLevel >= 0);
- discard_level = mCurrentDiscardLevel;
- }
-
- // Actual image width/height = raw image width/height * 2^discard_level
- S32 w = raw_image->getWidth() << discard_level;
- S32 h = raw_image->getHeight() << discard_level;
-
- // setSize may call destroyGLTexture if the size does not match
- if (!setSize(w, h, raw_image->getComponents(), discard_level))
- {
- LL_WARNS() << "Trying to create a texture with incorrect dimensions!" << LL_ENDL;
- return false;
- }
-
- if (!mHasExplicitFormat)
- {
- switch (mComponents)
- {
- case 1:
- // Use luminance alpha (for fonts)
- mFormatInternal = GL_LUMINANCE8;
- mFormatPrimary = GL_LUMINANCE;
- mFormatType = GL_UNSIGNED_BYTE;
- break;
- case 2:
- // Use luminance alpha (for fonts)
- mFormatInternal = GL_LUMINANCE8_ALPHA8;
- mFormatPrimary = GL_LUMINANCE_ALPHA;
- mFormatType = GL_UNSIGNED_BYTE;
- break;
- case 3:
- mFormatInternal = GL_RGB8;
- mFormatPrimary = GL_RGB;
- mFormatType = GL_UNSIGNED_BYTE;
- break;
- case 4:
- mFormatInternal = GL_RGBA8;
- mFormatPrimary = GL_RGBA;
- mFormatType = GL_UNSIGNED_BYTE;
- break;
- default:
- LL_ERRS() << "Bad number of components for texture: " << (U32) getComponents() << LL_ENDL;
- }
- }
-
- mCurrentDiscardLevel = discard_level;
- mDiscardLevelInAtlas = discard_level;
- mTexelsInAtlas = raw_image->getWidth() * raw_image->getHeight() ;
- mLastBindTime = sLastFrameTime;
- mGLTextureCreated = false ;
-
- glPixelStorei(GL_UNPACK_ROW_LENGTH, raw_image->getWidth());
- stop_glerror();
-
- if(mFormatSwapBytes)
- {
- glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
- stop_glerror();
- }
-
- return true ;
-}
-
-void LLImageGL::postAddToAtlas()
-{
- if(mFormatSwapBytes)
- {
- glPixelStorei(GL_UNPACK_SWAP_BYTES, 0);
- stop_glerror();
- }
-
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- gGL.getTexUnit(0)->setTextureFilteringOption(mFilterOption);
- stop_glerror();
-}
-
U32 type_width_from_pixtype(U32 pixtype)
{
U32 type_width = 0;
@@ -1752,7 +1606,6 @@ bool LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, bool data_
mTextureMemory = (S64Bytes)getMipBytes(mCurrentDiscardLevel);
- mTexelsInGLTexture = getWidth() * getHeight();
// mark this as bound at this point, so we don't throw it out immediately
mLastBindTime = sLastFrameTime;
@@ -1830,8 +1683,7 @@ void LLImageGL::syncTexName(LLGLuint texname)
bool LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw, bool compressed_ok) const
{
- llassert_always(sAllowReadBackRaw) ;
- //LL_ERRS() << "should not call this function!" << LL_ENDL ;
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
if (discard_level < 0)
{
@@ -2297,6 +2149,8 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h)
//----------------------------------------------------------------------------
U32 LLImageGL::createPickMask(S32 pWidth, S32 pHeight)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+ freePickMask();
U32 pick_width = pWidth/2 + 1;
U32 pick_height = pHeight/2 + 1;
@@ -2314,7 +2168,6 @@ U32 LLImageGL::createPickMask(S32 pWidth, S32 pHeight)
//----------------------------------------------------------------------------
void LLImageGL::freePickMask()
{
- // pickmask validity depends on old image size, delete it
if (mPickMask != NULL)
{
delete [] mPickMask;
@@ -2352,16 +2205,16 @@ void LLImageGL::updatePickMask(S32 width, S32 height, const U8* data_in)
return ;
}
- freePickMask();
-
if (mFormatType != GL_UNSIGNED_BYTE ||
((mFormatPrimary != GL_RGBA)
&& (mFormatPrimary != GL_SRGB_ALPHA)))
{
//cannot generate a pick mask for this texture
+ freePickMask();
return;
}
+
#ifdef SHOW_ASSERT
const U32 pickSize = createPickMask(width, height);
#else // SHOW_ASSERT
@@ -2460,6 +2313,73 @@ void LLImageGL::resetCurTexSizebar()
sCurTexSizeBar = -1 ;
sCurTexPickSize = -1 ;
}
+
+bool LLImageGL::scaleDown(S32 desired_discard)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
+
+ if (mTarget != GL_TEXTURE_2D)
+ {
+ return false;
+ }
+
+ desired_discard = llmin(desired_discard, mMaxDiscardLevel);
+
+ if (desired_discard <= mCurrentDiscardLevel)
+ {
+ return false;
+ }
+
+ S32 mip = desired_discard - mCurrentDiscardLevel;
+
+ S32 desired_width = getWidth(desired_discard);
+ S32 desired_height = getHeight(desired_discard);
+
+ U64 size = getBytes(desired_discard);
+ llassert(size <= 2048*2048*4); // we shouldn't be using this method to downscale huge textures, but it'll work
+ gGL.getTexUnit(0)->bind(this);
+
+
+ if (sScratchPBO == 0)
+ {
+ glGenBuffers(1, &sScratchPBO);
+ sScratchPBOSize = 0;
+ }
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, sScratchPBO);
+
+ if (size > sScratchPBOSize)
+ {
+ glBufferData(GL_PIXEL_PACK_BUFFER, size, NULL, GL_STREAM_COPY);
+ sScratchPBOSize = size;
+ }
+
+ glGetTexImage(mTarget, mip, mFormatPrimary, mFormatType, nullptr);
+
+ free_tex_image(mTexName);
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, sScratchPBO);
+ glTexImage2D(mTarget, 0, mFormatPrimary, desired_width, desired_height, 0, mFormatPrimary, mFormatType, nullptr);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ alloc_tex_image(desired_width, desired_height, mFormatPrimary);
+
+ if (mHasMipMaps)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("scaleDown - glGenerateMipmap");
+ glGenerateMipmap(mTarget);
+ }
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+ mCurrentDiscardLevel = desired_discard;
+
+ return true;
+}
+
+
//----------------------------------------------------------------------------
#if LL_IMAGEGL_THREAD_CHECK
void LLImageGL::checkActiveThread()
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index 5c7a5ce821..3892e9c014 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -39,6 +39,7 @@
#include "llrender.h"
#include "threadpool.h"
#include "workqueue.h"
+#include <unordered_set>
#define LL_IMAGEGL_THREAD_CHECK 0 //set to 1 to enable thread debugging for ImageGL
@@ -83,9 +84,8 @@ public:
// needs to be called every frame
static void updateStats(F32 current_time);
- // Save off / restore GL textures
- static void destroyGL(bool save_state = true);
- static void restoreGL();
+ // cleanup GL state
+ static void destroyGL();
static void dirtyTexOptions();
static bool checkSize(S32 width, S32 height);
@@ -148,6 +148,10 @@ public:
S32 getDiscardLevel() const { return mCurrentDiscardLevel; }
S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; }
+ // override the current discard level
+ // should only be used for local textures where you know exactly what you're doing
+ void setDiscardLevel(S32 level) { mCurrentDiscardLevel = level; }
+
S32 getCurrentWidth() const { return mWidth ;}
S32 getCurrentHeight() const { return mHeight ;}
S32 getWidth(S32 discard_level = -1) const;
@@ -194,26 +198,26 @@ public:
void setFilteringOption(LLTexUnit::eTextureFilterOptions option);
LLTexUnit::eTextureFilterOptions getFilteringOption(void) const { return mFilterOption; }
- LLGLenum getTexTarget()const { return mTarget ;}
- S8 getDiscardLevelInAtlas()const {return mDiscardLevelInAtlas;}
- U32 getTexelsInAtlas()const { return mTexelsInAtlas ;}
- U32 getTexelsInGLTexture()const {return mTexelsInGLTexture;}
-
+ LLGLenum getTexTarget()const { return mTarget; }
void init(bool usemipmaps);
virtual void cleanup(); // Clean up the LLImageGL so it can be reinitialized. Be careful when using this in derived class destructors
void setNeedsAlphaAndPickMask(bool need_mask);
- bool preAddToAtlas(S32 discard_level, const LLImageRaw* raw_image);
- void postAddToAtlas() ;
-
#if LL_IMAGEGL_THREAD_CHECK
// thread debugging
std::thread::id mActiveThread;
void checkActiveThread();
#endif
+ // scale down to the desired discard level using GPU
+ // returns true if texture was scaled down
+ // desired discard will be clamped to max discard
+ // if desired discard is less than or equal to current discard, no scaling will occur
+ // only works for GL_TEXTURE_2D target
+ bool scaleDown(S32 desired_discard);
+
public:
// Various GL/Rendering options
S64Bytes mTextureMemory;
@@ -240,15 +244,10 @@ private:
bool mGLTextureCreated ;
LLGLuint mTexName;
- //LLGLuint mNewTexName = 0; // tex name set by background thread to be applied in main thread
U16 mWidth;
U16 mHeight;
S8 mCurrentDiscardLevel;
- S8 mDiscardLevelInAtlas;
- U32 mTexelsInAtlas ;
- U32 mTexelsInGLTexture;
-
bool mAllowCompression;
protected:
@@ -275,7 +274,7 @@ protected:
// STATICS
public:
- static std::set<LLImageGL*> sImageList;
+ static std::unordered_set<LLImageGL*> sImageList;
static S32 sCount;
static F32 sLastFrameTime;
@@ -301,6 +300,8 @@ public:
private:
static S32 sMaxCategories;
static bool sSkipAnalyzeAlpha;
+ static U32 sScratchPBO;
+ static U32 sScratchPBOSize;
//the flag to allow to call readBackRaw(...).
//can be removed if we do not use that function at all.
diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp
index cfefde3acc..a0209fab43 100644
--- a/indra/llrender/llrender.cpp
+++ b/indra/llrender/llrender.cpp
@@ -991,6 +991,8 @@ void LLRender::syncLightState()
void LLRender::syncMatrices()
{
STOP_GLERROR;
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
+
static const U32 name[] =
{
LLShaderMgr::MODELVIEW_MATRIX,
diff --git a/indra/llrender/llrender.h b/indra/llrender/llrender.h
index ebdc9e751d..be9f3895e7 100644
--- a/indra/llrender/llrender.h
+++ b/indra/llrender/llrender.h
@@ -548,6 +548,5 @@ glh::matrix4f gl_perspective(GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloa
glh::matrix4f gl_lookat(LLVector3 eye, LLVector3 center, LLVector3 up);
#define LL_SHADER_LOADING_WARNS(...) LL_WARNS()
-#define LL_SHADER_UNIFORM_ERRS(...) LL_ERRS("Shader")
#endif
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index 574cf55a0e..a8e9f20b40 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -1185,6 +1185,8 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("normal_texcoord"); // (GLTF)
mReservedUniforms.push_back("metallic_roughness_texcoord"); // (GLTF)
mReservedUniforms.push_back("occlusion_texcoord"); // (GLTF)
+ mReservedUniforms.push_back("gltf_node_id"); // (GLTF)
+ mReservedUniforms.push_back("gltf_material_id"); // (GLTF)
mReservedUniforms.push_back("terrain_texture_transforms"); // (GLTF)
diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h
index c00aff3a9a..fe6137c448 100644
--- a/indra/llrender/llshadermgr.h
+++ b/indra/llrender/llshadermgr.h
@@ -63,6 +63,8 @@ public:
NORMAL_TEXCOORD, // "normal_texcoord" (GLTF)
METALLIC_ROUGHNESS_TEXCOORD, // "metallic_roughness_texcoord" (GLTF)
OCCLUSION_TEXCOORD, // "occlusion_texcoord" (GLTF)
+ GLTF_NODE_ID, // "gltf_node_id" (GLTF)
+ GLTF_MATERIAL_ID, // "gltf_material_id" (GLTF)
TERRAIN_TEXTURE_TRANSFORMS, // "terrain_texture_transforms" (GLTF)
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index f82ec30242..2eb7c21f77 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -806,6 +806,13 @@ void LLVertexBuffer::drawRange(U32 mode, U32 start, U32 end, U32 count, U32 indi
STOP_GLERROR;
}
+void LLVertexBuffer::drawRangeFast(U32 mode, U32 start, U32 end, U32 count, U32 indices_offset) const
+{
+ glDrawRangeElements(sGLMode[mode], start, end, count, mIndicesType,
+ (GLvoid*)(indices_offset * (size_t)mIndicesStride));
+}
+
+
void LLVertexBuffer::draw(U32 mode, U32 count, U32 indices_offset) const
{
drawRange(mode, 0, mNumVerts-1, count, indices_offset);
@@ -1062,12 +1069,6 @@ bool LLVertexBuffer::updateNumVerts(U32 nverts)
bool success = true;
- if (nverts > 65536)
- {
- LL_WARNS() << "Vertex buffer overflow!" << LL_ENDL;
- nverts = 65536;
- }
-
U32 needed_size = calcOffsets(mTypeMask, mOffsets, nverts);
if (needed_size != mSize)
@@ -1210,7 +1211,7 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count)
// end -- last byte to copy (NOT last byte + 1)
// data -- data to be flushed
// dst -- mMappedData or mMappedIndexData
-static void flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst)
+void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst)
{
#if LL_DARWIN
LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("vb memcpy");
@@ -1218,6 +1219,8 @@ static void flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst)
// copy into mapped buffer
memcpy(dst+start, data, end-start+1);
#else
+ llassert(target == GL_ARRAY_BUFFER ? sGLRenderBuffer == mGLBuffer : sGLRenderIndices == mGLIndices);
+
// skip mapped data and stream to GPU via glBufferSubData
if (end != 0)
{
@@ -1627,81 +1630,51 @@ void LLVertexBuffer::setupVertexBuffer()
void LLVertexBuffer::setPositionData(const LLVector4a* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, 0, sizeof(LLVector4a) * getNumVerts()-1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setTexCoord0Data(const LLVector2* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD0], mOffsets[TYPE_TEXCOORD0] + sTypeSize[TYPE_TEXCOORD0] * getNumVerts() - 1, (U8*)data, mMappedData);
}
void LLVertexBuffer::setTexCoord1Data(const LLVector2* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD1], mOffsets[TYPE_TEXCOORD1] + sTypeSize[TYPE_TEXCOORD1] * getNumVerts() - 1, (U8*)data, mMappedData);
}
void LLVertexBuffer::setColorData(const LLColor4U* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_COLOR], mOffsets[TYPE_COLOR] + sTypeSize[TYPE_COLOR] * getNumVerts() - 1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setNormalData(const LLVector4a* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_NORMAL], mOffsets[TYPE_NORMAL] + sTypeSize[TYPE_NORMAL] * getNumVerts() - 1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setTangentData(const LLVector4a* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TANGENT], mOffsets[TYPE_TANGENT] + sTypeSize[TYPE_TANGENT] * getNumVerts() - 1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setWeight4Data(const LLVector4a* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_WEIGHT4], mOffsets[TYPE_WEIGHT4] + sTypeSize[TYPE_WEIGHT4] * getNumVerts() - 1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setJointData(const U64* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderBuffer == mGLBuffer);
-#endif
flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_JOINT], mOffsets[TYPE_JOINT] + sTypeSize[TYPE_JOINT] * getNumVerts() - 1, (U8*) data, mMappedData);
}
void LLVertexBuffer::setIndexData(const U16* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderIndices == mGLIndices);
-#endif
flush_vbo(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(U16) * getNumIndices() - 1, (U8*) data, mMappedIndexData);
}
void LLVertexBuffer::setIndexData(const U32* data)
{
-#if !LL_DARWIN
- llassert(sGLRenderIndices == mGLIndices);
-#endif
if (mIndicesType != GL_UNSIGNED_INT)
{ // HACK -- vertex buffers are initialized as 16-bit indices, but can be switched to 32-bit indices
mIndicesType = GL_UNSIGNED_INT;
@@ -1711,3 +1684,62 @@ void LLVertexBuffer::setIndexData(const U32* data)
flush_vbo(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(U32) * getNumIndices() - 1, (U8*)data, mMappedIndexData);
}
+void LLVertexBuffer::setPositionData(const LLVector4a* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, offset * sizeof(LLVector4a), (offset + count) * sizeof(LLVector4a) - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setNormalData(const LLVector4a* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_NORMAL] + offset * sTypeSize[TYPE_NORMAL], mOffsets[TYPE_NORMAL] + (offset + count) * sTypeSize[TYPE_NORMAL] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setTexCoord0Data(const LLVector2* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD0] + offset * sTypeSize[TYPE_TEXCOORD0], mOffsets[TYPE_TEXCOORD0] + (offset + count) * sTypeSize[TYPE_TEXCOORD0] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setTexCoord1Data(const LLVector2* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TEXCOORD1] + offset * sTypeSize[TYPE_TEXCOORD1], mOffsets[TYPE_TEXCOORD1] + (offset + count) * sTypeSize[TYPE_TEXCOORD1] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setColorData(const LLColor4U* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_COLOR] + offset * sTypeSize[TYPE_COLOR], mOffsets[TYPE_COLOR] + (offset + count) * sTypeSize[TYPE_COLOR] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setTangentData(const LLVector4a* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_TANGENT] + offset * sTypeSize[TYPE_TANGENT], mOffsets[TYPE_TANGENT] + (offset + count) * sTypeSize[TYPE_TANGENT] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setWeight4Data(const LLVector4a* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_WEIGHT4] + offset * sTypeSize[TYPE_WEIGHT4], mOffsets[TYPE_WEIGHT4] + (offset + count) * sTypeSize[TYPE_WEIGHT4] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setJointData(const U64* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ARRAY_BUFFER, mOffsets[TYPE_JOINT] + offset * sTypeSize[TYPE_JOINT], mOffsets[TYPE_JOINT] + (offset + count) * sTypeSize[TYPE_JOINT] - 1, (U8*)data, mMappedData);
+}
+
+void LLVertexBuffer::setIndexData(const U16* data, U32 offset, U32 count)
+{
+ flush_vbo(GL_ELEMENT_ARRAY_BUFFER, offset * sizeof(U16), (offset + count) * sizeof(U16) - 1, (U8*)data, mMappedIndexData);
+}
+
+void LLVertexBuffer::setIndexData(const U32* data, U32 offset, U32 count)
+{
+ if (mIndicesType != GL_UNSIGNED_INT)
+ { // HACK -- vertex buffers are initialized as 16-bit indices, but can be switched to 32-bit indices
+ mIndicesType = GL_UNSIGNED_INT;
+ mIndicesStride = 4;
+ mNumIndices /= 2;
+ }
+ flush_vbo(GL_ELEMENT_ARRAY_BUFFER, offset * sizeof(U32), (offset + count) * sizeof(U32) - 1, (U8*)data, mMappedIndexData);
+}
+
+
+
+
diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h
index 601096abf9..49500e28ce 100644
--- a/indra/llrender/llvertexbuffer.h
+++ b/indra/llrender/llvertexbuffer.h
@@ -202,6 +202,18 @@ public:
void setIndexData(const U16* data);
void setIndexData(const U32* data);
+ void setPositionData(const LLVector4a* data, U32 offset, U32 count);
+ void setNormalData(const LLVector4a* data, U32 offset, U32 count);
+ void setTangentData(const LLVector4a* data, U32 offset, U32 count);
+ void setWeight4Data(const LLVector4a* data, U32 offset, U32 count);
+ void setJointData(const U64* data, U32 offset, U32 count);
+ void setTexCoord0Data(const LLVector2* data, U32 offset, U32 count);
+ void setTexCoord1Data(const LLVector2* data, U32 offset, U32 count);
+ void setColorData(const LLColor4U* data, U32 offset, U32 count);
+ void setIndexData(const U16* data, U32 offset, U32 count);
+ void setIndexData(const U32* data, U32 offset, U32 count);
+
+
U32 getNumVerts() const { return mNumVerts; }
U32 getNumIndices() const { return mNumIndices; }
@@ -219,6 +231,10 @@ public:
void drawArrays(U32 mode, U32 offset, U32 count) const;
void drawRange(U32 mode, U32 start, U32 end, U32 count, U32 indices_offset) const;
+ // draw without syncing matrices. If you're positive there have been no matrix
+ // since the last call to syncMatrices, this is much faster than drawRange
+ void drawRangeFast(U32 mode, U32 start, U32 end, U32 count, U32 indices_offset) const;
+
//for debugging, validate data in given range is valid
bool validateRange(U32 start, U32 end, U32 count, U32 offset) const;
@@ -256,6 +272,8 @@ private:
friend class LLNavShapeVBOManager;
friend class LLNavMeshVBOManager;
+ void flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst);
+
LLVertexBuffer(U32 typemask, U32 usage)
: LLVertexBuffer(typemask)
{}
diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp
index 4682044d6e..06f7a20add 100644
--- a/indra/llui/llaccordionctrl.cpp
+++ b/indra/llui/llaccordionctrl.cpp
@@ -379,12 +379,10 @@ void LLAccordionCtrl::initNoTabsWidget(const LLTextBox::Params& tb_params)
void LLAccordionCtrl::updateNoTabsHelpTextVisibility()
{
- bool visible_exists = false;
- std::vector<LLAccordionCtrlTab*>::const_iterator it = mAccordionTabs.begin();
- const std::vector<LLAccordionCtrlTab*>::const_iterator it_end = mAccordionTabs.end();
- while (it < it_end)
+ bool visible_exists{ false };
+ for (auto accordion_tab : mAccordionTabs)
{
- if ((*(it++))->getVisible())
+ if (accordion_tab->getVisible())
{
visible_exists = true;
break;
diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp
index 6d58a2545c..ac66525030 100644
--- a/indra/llui/llaccordionctrltab.cpp
+++ b/indra/llui/llaccordionctrltab.cpp
@@ -602,15 +602,13 @@ void LLAccordionCtrlTab::setSelected(bool is_selected)
LLView* LLAccordionCtrlTab::findContainerView()
{
- child_list_const_iter_t it = getChildList()->begin(), it_end = getChildList()->end();
- while (it != it_end)
+ for (auto child : *getChildList())
{
- LLView* child = *(it++);
if (DD_HEADER_NAME != child->getName() && child->getVisible())
return child;
}
- return NULL;
+ return nullptr;
}
void LLAccordionCtrlTab::selectOnFocusReceived()
diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index 1799968afb..53f39766c6 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -1250,17 +1250,15 @@ void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
detached_items.clear();
// Go through items and detach valid items, remove them from items panel
// and add to detached_items.
- pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end();
- while (iter != iter_end)
+ for (auto item_pair : mItemPairs)
{
- LLPanel* pItem = (*iter)->first;
+ LLPanel* pItem = item_pair->first;
if (1 == pItem->notify(action))
{
- selectItemPair((*iter), false);
+ selectItemPair(item_pair, false);
mItemsPanel->removeChild(pItem);
- detached_items.push_back(pItem);
+ detached_items.emplace_back(pItem);
}
- iter++;
}
if (!detached_items.empty())
{
@@ -1268,12 +1266,10 @@ void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
if (detached_items.size() == mItemPairs.size())
{
// This way will be faster if all items were disconnected
- pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end();
- while (iter != iter_end)
+ for (auto item_pair : mItemPairs)
{
- (*iter)->first = NULL;
- delete *iter;
- iter++;
+ item_pair->first = nullptr;
+ delete item_pair;
}
mItemPairs.clear();
// Also set items panel height to zero.
@@ -1286,26 +1282,16 @@ void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
}
else
{
- std::vector<LLPanel*>::const_iterator
- detached_iter = detached_items.begin(),
- detached_iter_end = detached_items.end();
- while (detached_iter < detached_iter_end)
+ for (auto detached_item : detached_items)
{
- LLPanel* pDetachedItem = *detached_iter;
- pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end();
- while (iter != iter_end)
+ auto found_pos = std::find_if(mItemPairs.begin(), mItemPairs.end(), [detached_item](auto item_pair) { return item_pair->first == detached_item; });
+ if (found_pos != mItemPairs.end())
{
- item_pair_t* item_pair = *iter;
- if (item_pair->first == pDetachedItem)
- {
- mItemPairs.erase(iter);
- item_pair->first = NULL;
- delete item_pair;
- break;
- }
- iter++;
+ mItemPairs.erase(found_pos);
+ auto item_pair = *found_pos;
+ item_pair->first = nullptr;
+ delete item_pair;
}
- detached_iter++;
}
rearrangeItems();
}
@@ -1412,11 +1398,10 @@ void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent)
action.with("match_filter", cur_filter);
mHasMatchedItems = false;
- bool visibility_changed = false;
- pairs_const_iterator_t iter = getItemPairs().begin(), iter_end = getItemPairs().end();
- while (iter != iter_end)
+ bool visibility_changed{ false };
+ for (auto item_pair : getItemPairs())
{
- LLPanel* pItem = (*(iter++))->first;
+ LLPanel* pItem = item_pair->first;
visibility_changed |= updateItemVisibility(pItem, action);
}
diff --git a/indra/llwebrtc/CMakeLists.txt b/indra/llwebrtc/CMakeLists.txt
new file mode 100644
index 0000000000..fd1788c5d4
--- /dev/null
+++ b/indra/llwebrtc/CMakeLists.txt
@@ -0,0 +1,61 @@
+# -*- cmake -*-
+
+# some webrtc headers require C++ 20
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+include(00-Common)
+include(Linking)
+include(WebRTC)
+
+project(llwebrtc)
+
+if (LINUX)
+ add_compile_options(-Wno-deprecated-declarations) # webrtc::CreateAudioDeviceWithDataObserver is deprecated
+endif (LINUX)
+
+set(llwebrtc_SOURCE_FILES
+ llwebrtc.cpp
+ )
+
+set(llwebrtc_HEADER_FILES
+ CMakeLists.txt
+ llwebrtc.h
+ llwebrtc_impl.h
+ )
+
+list(APPEND llwebrtc_SOURCE_FILES ${llwebrtc_HEADER_FILES})
+
+add_library (llwebrtc SHARED ${llwebrtc_SOURCE_FILES})
+
+set_target_properties(llwebrtc PROPERTIES PUBLIC_HEADER llwebrtc.h)
+
+if (WINDOWS)
+ target_link_libraries(llwebrtc PRIVATE ll::webrtc
+ secur32
+ winmm
+ dmoguids
+ wmcodecdspuuid
+ msdmo
+ strmiids
+ iphlpapi)
+elseif (DARWIN)
+ target_link_libraries(llwebrtc PRIVATE ll::webrtc)
+elseif (LINUX)
+ target_link_libraries(llwebrtc PRIVATE ll::webrtc)
+endif (WINDOWS)
+
+target_include_directories( llwebrtc INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
+
+if (WINDOWS)
+ set_property(TARGET llwebrtc PROPERTY
+ MSVC_RUNTIME_LIBRARY "MultiThreadedDebug")
+endif (WINDOWS)
+
+ADD_CUSTOM_COMMAND(TARGET llwebrtc POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ $<TARGET_FILE:llwebrtc>
+ ${SHARED_LIB_STAGING_DIR})
+# Add tests
+if (LL_TESTS)
+endif (LL_TESTS)
diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp
new file mode 100644
index 0000000000..6dc632aba4
--- /dev/null
+++ b/indra/llwebrtc/llwebrtc.cpp
@@ -0,0 +1,1345 @@
+/**
+ * @file llwebrtc.cpp
+ * @brief WebRTC interface implementation
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llwebrtc_impl.h"
+#include <algorithm>
+#include <string.h>
+
+#include "api/audio_codecs/audio_decoder_factory.h"
+#include "api/audio_codecs/audio_encoder_factory.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/media_stream_interface.h"
+#include "api/media_stream_track.h"
+#include "modules/audio_processing/audio_buffer.h"
+#include "modules/audio_mixer/audio_mixer_impl.h"
+
+namespace llwebrtc
+{
+
+static int16_t PLAYOUT_DEVICE_DEFAULT = -1;
+static int16_t PLAYOUT_DEVICE_BAD = -2;
+static int16_t RECORD_DEVICE_DEFAULT = -1;
+static int16_t RECORD_DEVICE_BAD = -2;
+
+LLAudioDeviceObserver::LLAudioDeviceObserver() : mSumVector {0}, mMicrophoneEnergy(0.0) {}
+
+float LLAudioDeviceObserver::getMicrophoneEnergy() { return mMicrophoneEnergy; }
+
+// TODO: Pull smoothing/filtering code into a common helper function
+// for LLAudioDeviceObserver and LLCustomProcessor
+
+void LLAudioDeviceObserver::OnCaptureData(const void *audio_samples,
+ const size_t num_samples,
+ const size_t bytes_per_sample,
+ const size_t num_channels,
+ const uint32_t samples_per_sec)
+{
+ // calculate the energy
+ float energy = 0;
+ const short *samples = (const short *) audio_samples;
+ for (size_t index = 0; index < num_samples * num_channels; index++)
+ {
+ float sample = (static_cast<float>(samples[index]) / (float) 32767);
+ energy += sample * sample;
+ }
+
+ // smooth it.
+ size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]);
+ float totalSum = 0;
+ int i;
+ for (i = 0; i < (buffer_size - 1); i++)
+ {
+ mSumVector[i] = mSumVector[i + 1];
+ totalSum += mSumVector[i];
+ }
+ mSumVector[i] = energy;
+ totalSum += energy;
+ mMicrophoneEnergy = std::sqrt(totalSum / (num_samples * buffer_size));
+}
+
+void LLAudioDeviceObserver::OnRenderData(const void *audio_samples,
+ const size_t num_samples,
+ const size_t bytes_per_sample,
+ const size_t num_channels,
+ const uint32_t samples_per_sec)
+{
+}
+
+LLCustomProcessor::LLCustomProcessor() : mSampleRateHz(0), mNumChannels(0), mMicrophoneEnergy(0.0), mGain(1.0)
+{
+ memset(mSumVector, 0, sizeof(mSumVector));
+}
+
+void LLCustomProcessor::Initialize(int sample_rate_hz, int num_channels)
+{
+ mSampleRateHz = sample_rate_hz;
+ mNumChannels = num_channels;
+ memset(mSumVector, 0, sizeof(mSumVector));
+}
+
+void LLCustomProcessor::Process(webrtc::AudioBuffer *audio_in)
+{
+ webrtc::StreamConfig stream_config;
+ stream_config.set_sample_rate_hz(mSampleRateHz);
+ stream_config.set_num_channels(mNumChannels);
+ std::vector<float *> frame;
+ std::vector<float> frame_samples;
+
+ if (audio_in->num_channels() < 1 || audio_in->num_frames() < 480)
+ {
+ return;
+ }
+
+ // grab the input audio
+ frame_samples.resize(stream_config.num_samples());
+ frame.resize(stream_config.num_channels());
+ for (size_t ch = 0; ch < stream_config.num_channels(); ++ch)
+ {
+ frame[ch] = &(frame_samples)[ch * stream_config.num_frames()];
+ }
+
+ audio_in->CopyTo(stream_config, &frame[0]);
+
+ // calculate the energy
+ float energy = 0;
+ for (size_t index = 0; index < stream_config.num_samples(); index++)
+ {
+ float sample = frame_samples[index];
+ sample = sample * mGain; // apply gain
+ frame_samples[index] = sample; // write processed sample back to buffer.
+ energy += sample * sample;
+ }
+
+ audio_in->CopyFrom(&frame[0], stream_config);
+
+ // smooth it.
+ size_t buffer_size = sizeof(mSumVector) / sizeof(mSumVector[0]);
+ float totalSum = 0;
+ int i;
+ for (i = 0; i < (buffer_size - 1); i++)
+ {
+ mSumVector[i] = mSumVector[i + 1];
+ totalSum += mSumVector[i];
+ }
+ mSumVector[i] = energy;
+ totalSum += energy;
+ mMicrophoneEnergy = std::sqrt(totalSum / (stream_config.num_samples() * buffer_size));
+}
+
+//
+// LLWebRTCImpl implementation
+//
+
+LLWebRTCImpl::LLWebRTCImpl() :
+ mPeerCustomProcessor(nullptr),
+ mMute(true),
+ mTuningMode(false),
+ mPlayoutDevice(0),
+ mRecordingDevice(0),
+ mTuningAudioDeviceObserver(nullptr)
+{
+}
+
+void LLWebRTCImpl::init()
+{
+ mPlayoutDevice = 0;
+ mRecordingDevice = 0;
+ rtc::InitializeSSL();
+
+ // Normal logging is rather spammy, so turn it off.
+ rtc::LogMessage::LogToDebug(rtc::LS_NONE);
+ rtc::LogMessage::SetLogToStderr(true);
+
+ mTaskQueueFactory = webrtc::CreateDefaultTaskQueueFactory();
+
+ // Create the native threads.
+ mNetworkThread = rtc::Thread::CreateWithSocketServer();
+ mNetworkThread->SetName("WebRTCNetworkThread", nullptr);
+ mNetworkThread->Start();
+ mWorkerThread = rtc::Thread::Create();
+ mWorkerThread->SetName("WebRTCWorkerThread", nullptr);
+ mWorkerThread->Start();
+ mSignalingThread = rtc::Thread::Create();
+ mSignalingThread->SetName("WebRTCSignalingThread", nullptr);
+ mSignalingThread->Start();
+
+ mTuningAudioDeviceObserver = new LLAudioDeviceObserver;
+ mWorkerThread->PostTask(
+ [this]()
+ {
+ // Initialize the audio devices on the Worker Thread
+ mTuningDeviceModule =
+ webrtc::CreateAudioDeviceWithDataObserver(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio,
+ mTaskQueueFactory.get(),
+ std::unique_ptr<webrtc::AudioDeviceDataObserver>(mTuningAudioDeviceObserver));
+
+ mTuningDeviceModule->Init();
+ mTuningDeviceModule->SetPlayoutDevice(mPlayoutDevice);
+ mTuningDeviceModule->SetRecordingDevice(mRecordingDevice);
+ mTuningDeviceModule->EnableBuiltInAEC(false);
+ mTuningDeviceModule->SetAudioDeviceSink(this);
+ mTuningDeviceModule->InitMicrophone();
+ mTuningDeviceModule->InitSpeaker();
+ mTuningDeviceModule->InitRecording();
+ mTuningDeviceModule->InitPlayout();
+ mTuningDeviceModule->SetStereoRecording(true);
+ mTuningDeviceModule->SetStereoPlayout(true);
+ updateDevices();
+ });
+
+ mWorkerThread->BlockingCall(
+ [this]()
+ {
+ // the peer device module doesn't need an observer
+ // as we pull peer data after audio processing.
+ mPeerDeviceModule = webrtc::CreateAudioDeviceWithDataObserver(webrtc::AudioDeviceModule::AudioLayer::kPlatformDefaultAudio,
+ mTaskQueueFactory.get(),
+ nullptr);
+ mPeerDeviceModule->Init();
+ mPeerDeviceModule->SetPlayoutDevice(mPlayoutDevice);
+ mPeerDeviceModule->SetRecordingDevice(mRecordingDevice);
+ mPeerDeviceModule->EnableBuiltInAEC(false);
+ mPeerDeviceModule->InitMicrophone();
+ mPeerDeviceModule->InitSpeaker();
+ mPeerDeviceModule->InitRecording();
+ mPeerDeviceModule->InitPlayout();
+ mPeerDeviceModule->SetStereoRecording(true);
+ mPeerDeviceModule->SetStereoPlayout(true);
+ });
+
+ // The custom processor allows us to retrieve audio data (and levels)
+ // from after other audio processing such as AEC, AGC, etc.
+ mPeerCustomProcessor = new LLCustomProcessor;
+ webrtc::AudioProcessingBuilder apb;
+ apb.SetCapturePostProcessing(std::unique_ptr<webrtc::CustomProcessing>(mPeerCustomProcessor));
+ mAudioProcessingModule = apb.Create();
+
+ webrtc::AudioProcessing::Config apm_config;
+ apm_config.echo_canceller.enabled = false;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = false;
+ apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+ apm_config.gain_controller2.enabled = false;
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
+ apm_config.pipeline.multi_channel_capture = true;
+
+ webrtc::ProcessingConfig processing_config;
+ processing_config.input_stream().set_num_channels(2);
+ processing_config.input_stream().set_sample_rate_hz(48000);
+ processing_config.output_stream().set_num_channels(2);
+ processing_config.output_stream().set_sample_rate_hz(48000);
+ processing_config.reverse_input_stream().set_num_channels(2);
+ processing_config.reverse_input_stream().set_sample_rate_hz(48000);
+ processing_config.reverse_output_stream().set_num_channels(2);
+ processing_config.reverse_output_stream().set_sample_rate_hz(48000);
+
+ mAudioProcessingModule->ApplyConfig(apm_config);
+ mAudioProcessingModule->Initialize(processing_config);
+
+
+ mPeerConnectionFactory = webrtc::CreatePeerConnectionFactory(mNetworkThread.get(),
+ mWorkerThread.get(),
+ mSignalingThread.get(),
+ mPeerDeviceModule,
+ webrtc::CreateBuiltinAudioEncoderFactory(),
+ webrtc::CreateBuiltinAudioDecoderFactory(),
+ nullptr /* video_encoder_factory */,
+ nullptr /* video_decoder_factory */,
+ nullptr /* audio_mixer */,
+ mAudioProcessingModule);
+
+ mWorkerThread->BlockingCall([this]() { mPeerDeviceModule->StartPlayout(); });
+}
+
+void LLWebRTCImpl::terminate()
+{
+ for (auto &connection : mPeerConnections)
+ {
+ connection->terminate();
+ }
+
+ // connection->terminate() above spawns a number of Signaling thread calls to
+ // shut down the connection. The following Blocking Call will wait
+ // until they're done before it's executed, allowing time to clean up.
+
+ mSignalingThread->BlockingCall([this]() { mPeerConnectionFactory = nullptr; });
+
+ mPeerConnections.clear();
+
+ mWorkerThread->BlockingCall(
+ [this]()
+ {
+ if (mTuningDeviceModule)
+ {
+ mTuningDeviceModule->StopRecording();
+ mTuningDeviceModule->Terminate();
+ }
+ if (mPeerDeviceModule)
+ {
+ mPeerDeviceModule->StopRecording();
+ mPeerDeviceModule->Terminate();
+ }
+ mTuningDeviceModule = nullptr;
+ mPeerDeviceModule = nullptr;
+ mTaskQueueFactory = nullptr;
+ });
+}
+
+//
+// Devices functions
+//
+// Most device-related functionality needs to happen
+// on the worker thread (the audio thread,) so those calls will be
+// proxied over to that thread.
+//
+void LLWebRTCImpl::setRecording(bool recording)
+{
+ mWorkerThread->PostTask(
+ [this, recording]()
+ {
+ if (recording)
+ {
+ mPeerDeviceModule->StartRecording();
+ }
+ else
+ {
+ mPeerDeviceModule->StopRecording();
+ }
+ });
+}
+
+void LLWebRTCImpl::setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config)
+{
+ webrtc::AudioProcessing::Config apm_config;
+ apm_config.echo_canceller.enabled = config.mEchoCancellation;
+ apm_config.echo_canceller.mobile_mode = false;
+ apm_config.gain_controller1.enabled = config.mAGC;
+ apm_config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::kAdaptiveAnalog;
+ apm_config.gain_controller2.enabled = false;
+ apm_config.high_pass_filter.enabled = true;
+ apm_config.transient_suppression.enabled = true;
+ apm_config.pipeline.multi_channel_render = true;
+ apm_config.pipeline.multi_channel_capture = true;
+ apm_config.pipeline.multi_channel_capture = true;
+
+ switch (config.mNoiseSuppressionLevel)
+ {
+ case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_NONE:
+ apm_config.noise_suppression.enabled = false;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ break;
+ case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_LOW:
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ break;
+ case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_MODERATE:
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
+ break;
+ case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_HIGH:
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh;
+ break;
+ case LLWebRTCDeviceInterface::AudioConfig::NOISE_SUPPRESSION_LEVEL_VERY_HIGH:
+ apm_config.noise_suppression.enabled = true;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kVeryHigh;
+ break;
+ default:
+ apm_config.noise_suppression.enabled = false;
+ apm_config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kLow;
+ }
+ mAudioProcessingModule->ApplyConfig(apm_config);
+}
+
+void LLWebRTCImpl::refreshDevices()
+{
+ mWorkerThread->PostTask([this]() { updateDevices(); });
+}
+
+void LLWebRTCImpl::setDevicesObserver(LLWebRTCDevicesObserver *observer) { mVoiceDevicesObserverList.emplace_back(observer); }
+
+void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer)
+{
+ std::vector<LLWebRTCDevicesObserver *>::iterator it =
+ std::find(mVoiceDevicesObserverList.begin(), mVoiceDevicesObserverList.end(), observer);
+ if (it != mVoiceDevicesObserverList.end())
+ {
+ mVoiceDevicesObserverList.erase(it);
+ }
+}
+
+void ll_set_device_module_capture_device(rtc::scoped_refptr<webrtc::AudioDeviceModule> device_module, int16_t device)
+{
+ device_module->StopRecording();
+#if WEBRTC_WIN
+ if (device < 0)
+ {
+ device_module->SetRecordingDevice(webrtc::AudioDeviceModule::kDefaultDevice);
+ }
+ else
+ {
+ device_module->SetRecordingDevice(device);
+ }
+#else
+ // passed in default is -1, but the device list
+ // has it at 0
+ device_module->SetRecordingDevice(device + 1);
+#endif
+ device_module->InitMicrophone();
+ device_module->InitRecording();
+ device_module->SetStereoRecording(false);
+ device_module->StartRecording();
+}
+
+void LLWebRTCImpl::setCaptureDevice(const std::string &id)
+{
+ int16_t recordingDevice = RECORD_DEVICE_DEFAULT;
+ if (id != "Default")
+ {
+ for (int16_t i = 0; i < mRecordingDeviceList.size(); i++)
+ {
+ if (mRecordingDeviceList[i].mID == id)
+ {
+ recordingDevice = i;
+ break;
+ }
+ }
+ }
+ if (recordingDevice == mRecordingDevice)
+ {
+ return;
+ }
+ mRecordingDevice = recordingDevice;
+ if (mTuningMode)
+ {
+ mWorkerThread->PostTask([this, recordingDevice]() { ll_set_device_module_capture_device(mTuningDeviceModule, recordingDevice); });
+ }
+ else
+ {
+ mWorkerThread->PostTask([this, recordingDevice]() { ll_set_device_module_capture_device(mPeerDeviceModule, recordingDevice); });
+ }
+}
+
+
+void ll_set_device_module_render_device(rtc::scoped_refptr<webrtc::AudioDeviceModule> device_module, int16_t device)
+{
+ device_module->StopPlayout();
+#if WEBRTC_WIN
+ if (device < 0)
+ {
+ device_module->SetPlayoutDevice(webrtc::AudioDeviceModule::kDefaultDevice);
+ }
+ else
+ {
+ device_module->SetPlayoutDevice(device);
+ }
+#else
+ device_module->SetPlayoutDevice(device + 1);
+#endif
+ device_module->InitSpeaker();
+ device_module->InitPlayout();
+ device_module->SetStereoPlayout(true);
+}
+
+void LLWebRTCImpl::setRenderDevice(const std::string &id)
+{
+ int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT;
+ if (id != "Default")
+ {
+ for (int16_t i = 0; i < mPlayoutDeviceList.size(); i++)
+ {
+ if (mPlayoutDeviceList[i].mID == id)
+ {
+ playoutDevice = i;
+ break;
+ }
+ }
+ }
+ if (playoutDevice == mPlayoutDevice)
+ {
+ return;
+ }
+ mPlayoutDevice = playoutDevice;
+
+ if (mTuningMode)
+ {
+ mWorkerThread->PostTask(
+ [this, playoutDevice]()
+ {
+ ll_set_device_module_render_device(mTuningDeviceModule, playoutDevice);
+ });
+ }
+ else
+ {
+ mWorkerThread->PostTask(
+ [this, playoutDevice]()
+ {
+ ll_set_device_module_render_device(mPeerDeviceModule, playoutDevice);
+ mPeerDeviceModule->StartPlayout();
+ });
+ }
+}
+
+// updateDevices needs to happen on the worker thread.
+void LLWebRTCImpl::updateDevices()
+{
+ int16_t renderDeviceCount = mTuningDeviceModule->PlayoutDevices();
+
+ mPlayoutDeviceList.clear();
+#if WEBRTC_WIN
+ int16_t index = 0;
+#else
+ // index zero is always "Default" for darwin/linux,
+ // which is a special case, so skip it.
+ int16_t index = 1;
+#endif
+ for (; index < renderDeviceCount; index++)
+ {
+ char name[webrtc::kAdmMaxDeviceNameSize];
+ char guid[webrtc::kAdmMaxGuidSize];
+ mTuningDeviceModule->PlayoutDeviceName(index, name, guid);
+ mPlayoutDeviceList.emplace_back(name, guid);
+ }
+
+ int16_t captureDeviceCount = mTuningDeviceModule->RecordingDevices();
+
+ mRecordingDeviceList.clear();
+#if WEBRTC_WIN
+ index = 0;
+#else
+ // index zero is always "Default" for darwin/linux,
+ // which is a special case, so skip it.
+ index = 1;
+#endif
+ for (; index < captureDeviceCount; index++)
+ {
+ char name[webrtc::kAdmMaxDeviceNameSize];
+ char guid[webrtc::kAdmMaxGuidSize];
+ mTuningDeviceModule->RecordingDeviceName(index, name, guid);
+ mRecordingDeviceList.emplace_back(name, guid);
+ }
+
+ for (auto &observer : mVoiceDevicesObserverList)
+ {
+ observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList);
+ }
+}
+
+void LLWebRTCImpl::OnDevicesUpdated()
+{
+ // reset these to a bad value so an update is forced
+ mRecordingDevice = RECORD_DEVICE_BAD;
+ mPlayoutDevice = PLAYOUT_DEVICE_BAD;
+
+ updateDevices();
+}
+
+
+void LLWebRTCImpl::setTuningMode(bool enable)
+{
+ mTuningMode = enable;
+ mWorkerThread->PostTask(
+ [this, enable] {
+ if (enable)
+ {
+ mPeerDeviceModule->StopRecording();
+ mPeerDeviceModule->StopPlayout();
+ ll_set_device_module_render_device(mTuningDeviceModule, mPlayoutDevice);
+ ll_set_device_module_capture_device(mTuningDeviceModule, mRecordingDevice);
+ mTuningDeviceModule->InitPlayout();
+ mTuningDeviceModule->InitRecording();
+ mTuningDeviceModule->StartRecording();
+ // TODO: Starting Playout on the TDM appears to create an audio artifact (click)
+ // in this case, so disabling it for now. We may have to do something different
+ // if we enable 'echo playback' via the TDM when tuning.
+ //mTuningDeviceModule->StartPlayout();
+ }
+ else
+ {
+ mTuningDeviceModule->StopRecording();
+ //mTuningDeviceModule->StopPlayout();
+ ll_set_device_module_render_device(mPeerDeviceModule, mPlayoutDevice);
+ ll_set_device_module_capture_device(mPeerDeviceModule, mRecordingDevice);
+ mPeerDeviceModule->InitPlayout();
+ mPeerDeviceModule->InitRecording();
+ mPeerDeviceModule->StartPlayout();
+ mPeerDeviceModule->StartRecording();
+ }
+ }
+ );
+ mSignalingThread->PostTask(
+ [this, enable]
+ {
+ for (auto &connection : mPeerConnections)
+ {
+ if (enable)
+ {
+ connection->enableSenderTracks(false);
+ }
+ else
+ {
+ connection->resetMute();
+ }
+ connection->enableReceiverTracks(!enable);
+ }
+ });
+}
+
+float LLWebRTCImpl::getTuningAudioLevel() { return -20 * log10f(mTuningAudioDeviceObserver->getMicrophoneEnergy()); }
+
+float LLWebRTCImpl::getPeerConnectionAudioLevel() { return -20 * log10f(mPeerCustomProcessor->getMicrophoneEnergy()); }
+
+void LLWebRTCImpl::setPeerConnectionGain(float gain) { mPeerCustomProcessor->setGain(gain); }
+
+
+//
+// Peer Connection Helpers
+//
+
+LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection()
+{
+ rtc::scoped_refptr<LLWebRTCPeerConnectionImpl> peerConnection = rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>(new rtc::RefCountedObject<LLWebRTCPeerConnectionImpl>());
+ peerConnection->init(this);
+
+ mPeerConnections.emplace_back(peerConnection);
+ peerConnection->enableSenderTracks(!mMute);
+ return peerConnection.get();
+}
+
+void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection)
+{
+ std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>>::iterator it =
+ std::find(mPeerConnections.begin(), mPeerConnections.end(), peer_connection);
+ if (it != mPeerConnections.end())
+ {
+ mPeerConnections.erase(it);
+ }
+ if (mPeerConnections.empty())
+ {
+ setRecording(false);
+ }
+}
+
+
+//
+// LLWebRTCPeerConnectionImpl implementation.
+//
+// Most peer connection (signaling) happens on
+// the signaling thread.
+
+LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() :
+ mWebRTCImpl(nullptr),
+ mPeerConnection(nullptr),
+ mMute(false),
+ mAnswerReceived(false)
+{
+}
+
+LLWebRTCPeerConnectionImpl::~LLWebRTCPeerConnectionImpl()
+{
+ mSignalingObserverList.clear();
+ mDataObserverList.clear();
+}
+
+//
+// LLWebRTCPeerConnection interface
+//
+
+void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl)
+{
+ mWebRTCImpl = webrtc_impl;
+ mPeerConnectionFactory = mWebRTCImpl->getPeerConnectionFactory();
+}
+void LLWebRTCPeerConnectionImpl::terminate()
+{
+ mWebRTCImpl->PostSignalingTask(
+ [=]()
+ {
+ if (mPeerConnection)
+ {
+ if (mDataChannel)
+ {
+ {
+ mDataChannel->Close();
+ mDataChannel = nullptr;
+ }
+ }
+
+ mPeerConnection->Close();
+ if (mLocalStream)
+ {
+ auto tracks = mLocalStream->GetAudioTracks();
+ for (auto& track : tracks)
+ {
+ mLocalStream->RemoveTrack(track);
+ }
+ mLocalStream = nullptr;
+ }
+ mPeerConnection = nullptr;
+
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnPeerConnectionClosed();
+ }
+ }
+ });
+}
+
+void LLWebRTCPeerConnectionImpl::setSignalingObserver(LLWebRTCSignalingObserver *observer) { mSignalingObserverList.emplace_back(observer); }
+
+void LLWebRTCPeerConnectionImpl::unsetSignalingObserver(LLWebRTCSignalingObserver *observer)
+{
+ std::vector<LLWebRTCSignalingObserver *>::iterator it =
+ std::find(mSignalingObserverList.begin(), mSignalingObserverList.end(), observer);
+ if (it != mSignalingObserverList.end())
+ {
+ mSignalingObserverList.erase(it);
+ }
+}
+
+
+bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnectionInterface::InitOptions& options)
+{
+ RTC_DCHECK(!mPeerConnection);
+ mAnswerReceived = false;
+
+ mWebRTCImpl->PostSignalingTask(
+ [this,options]()
+ {
+ webrtc::PeerConnectionInterface::RTCConfiguration config;
+ for (auto server : options.mServers)
+ {
+ webrtc::PeerConnectionInterface::IceServer ice_server;
+ for (auto url : server.mUrls)
+ {
+ ice_server.urls.push_back(url);
+ }
+ ice_server.username = server.mUserName;
+ ice_server.password = server.mPassword;
+ config.servers.push_back(ice_server);
+ }
+ config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
+
+ config.set_min_port(60000);
+ config.set_max_port(60100);
+
+ webrtc::PeerConnectionDependencies pc_dependencies(this);
+ auto error_or_peer_connection = mPeerConnectionFactory->CreatePeerConnectionOrError(config, std::move(pc_dependencies));
+ if (error_or_peer_connection.ok())
+ {
+ mPeerConnection = std::move(error_or_peer_connection.value());
+ }
+ else
+ {
+ RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message();
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnRenegotiationNeeded();
+ }
+ return;
+ }
+
+ webrtc::DataChannelInit init;
+ init.ordered = true;
+
+ auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init);
+ if (data_channel_or_error.ok())
+ {
+ mDataChannel = std::move(data_channel_or_error.value());
+
+ mDataChannel->RegisterObserver(this);
+ }
+
+ cricket::AudioOptions audioOptions;
+ audioOptions.auto_gain_control = true;
+ audioOptions.echo_cancellation = true;
+ audioOptions.noise_suppression = true;
+
+ mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream");
+
+ rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
+ mPeerConnectionFactory->CreateAudioTrack("SLAudio", mPeerConnectionFactory->CreateAudioSource(audioOptions).get()));
+ audio_track->set_enabled(false);
+ mLocalStream->AddTrack(audio_track);
+
+ mPeerConnection->AddTrack(audio_track, {"SLStream"});
+
+ auto senders = mPeerConnection->GetSenders();
+
+ for (auto &sender : senders)
+ {
+ webrtc::RtpParameters params;
+ webrtc::RtpCodecParameters codecparam;
+ codecparam.name = "opus";
+ codecparam.kind = cricket::MEDIA_TYPE_AUDIO;
+ codecparam.clock_rate = 48000;
+ codecparam.num_channels = 2;
+ codecparam.parameters["stereo"] = "1";
+ codecparam.parameters["sprop-stereo"] = "1";
+ params.codecs.push_back(codecparam);
+ sender->SetParameters(params);
+ }
+
+ auto receivers = mPeerConnection->GetReceivers();
+ for (auto &receiver : receivers)
+ {
+ webrtc::RtpParameters params;
+ webrtc::RtpCodecParameters codecparam;
+ codecparam.name = "opus";
+ codecparam.kind = cricket::MEDIA_TYPE_AUDIO;
+ codecparam.clock_rate = 48000;
+ codecparam.num_channels = 2;
+ codecparam.parameters["stereo"] = "1";
+ codecparam.parameters["sprop-stereo"] = "1";
+ params.codecs.push_back(codecparam);
+ receiver->SetParameters(params);
+ }
+
+ webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions;
+ mPeerConnection->CreateOffer(this, offerOptions);
+ });
+
+ return true;
+}
+
+bool LLWebRTCPeerConnectionImpl::shutdownConnection()
+{
+ terminate();
+ return true;
+}
+
+void LLWebRTCPeerConnectionImpl::enableSenderTracks(bool enable)
+{
+ // set_enabled shouldn't be done on the worker thread.
+ if (mPeerConnection)
+ {
+ auto senders = mPeerConnection->GetSenders();
+ for (auto &sender : senders)
+ {
+ sender->track()->set_enabled(enable);
+ }
+ }
+}
+
+void LLWebRTCPeerConnectionImpl::enableReceiverTracks(bool enable)
+{
+ // set_enabled shouldn't be done on the worker thread
+ if (mPeerConnection)
+ {
+ auto receivers = mPeerConnection->GetReceivers();
+ for (auto &receiver : receivers)
+ {
+ receiver->track()->set_enabled(enable);
+ }
+ }
+}
+
+// Tell the peer connection that we've received a SDP answer from the sim.
+void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp)
+{
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Remote SDP: " << sdp;
+
+ mWebRTCImpl->PostSignalingTask(
+ [this, sdp]()
+ {
+ if (mPeerConnection)
+ {
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->peer_connection_state();
+ mPeerConnection->SetRemoteDescription(webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, sdp),
+ rtc::scoped_refptr<webrtc::SetRemoteDescriptionObserverInterface>(this));
+ }
+ });
+}
+
+
+//
+// LLWebRTCAudioInterface implementation
+//
+
+void LLWebRTCPeerConnectionImpl::setMute(bool mute)
+{
+ mMute = mute;
+ mWebRTCImpl->PostSignalingTask(
+ [this]()
+ {
+ if (mPeerConnection)
+ {
+ auto senders = mPeerConnection->GetSenders();
+
+ RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size();
+ for (auto &sender : senders)
+ {
+ auto track = sender->track();
+ if (track)
+ {
+ track->set_enabled(!mMute);
+ }
+ }
+ }
+ });
+}
+
+void LLWebRTCPeerConnectionImpl::resetMute()
+{
+ setMute(mMute);
+}
+
+void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume)
+{
+ mWebRTCImpl->PostSignalingTask(
+ [this, volume]()
+ {
+ if (mPeerConnection)
+ {
+ auto receivers = mPeerConnection->GetReceivers();
+
+ for (auto &receiver : receivers)
+ {
+ for (auto &stream : receiver->streams())
+ {
+ for (auto &track : stream->GetAudioTracks())
+ {
+ track->GetSource()->SetVolume(volume);
+ }
+ }
+ }
+ }
+ });
+}
+
+void LLWebRTCPeerConnectionImpl::setSendVolume(float volume)
+{
+ mWebRTCImpl->PostSignalingTask(
+ [this, volume]()
+ {
+ if (mLocalStream)
+ {
+ for (auto &track : mLocalStream->GetAudioTracks())
+ {
+ track->GetSource()->SetVolume(volume*5.0);
+ }
+ }
+ });
+}
+
+//
+// PeerConnectionObserver implementation.
+//
+
+void LLWebRTCPeerConnectionImpl::OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
+ const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams)
+{
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id();
+ webrtc::RtpParameters params;
+ webrtc::RtpCodecParameters codecparam;
+ codecparam.name = "opus";
+ codecparam.kind = cricket::MEDIA_TYPE_AUDIO;
+ codecparam.clock_rate = 48000;
+ codecparam.num_channels = 2;
+ codecparam.parameters["stereo"] = "1";
+ codecparam.parameters["sprop-stereo"] = "1";
+ params.codecs.push_back(codecparam);
+ receiver->SetParameters(params);
+}
+
+void LLWebRTCPeerConnectionImpl::OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver)
+{
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << receiver->id();
+}
+
+void LLWebRTCPeerConnectionImpl::OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel)
+{
+ if (mDataChannel)
+ {
+ mDataChannel->UnregisterObserver();
+ }
+ mDataChannel = channel;
+ channel->RegisterObserver(this);
+}
+
+void LLWebRTCPeerConnectionImpl::OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state)
+{
+ LLWebRTCSignalingObserver::EIceGatheringState webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW;
+ switch (new_state)
+ {
+ case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringNew:
+ webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW;
+ break;
+ case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringGathering:
+ webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_GATHERING;
+ break;
+ case webrtc::PeerConnectionInterface::IceGatheringState::kIceGatheringComplete:
+ webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE;
+ break;
+ default:
+ RTC_LOG(LS_ERROR) << __FUNCTION__ << " Bad Ice Gathering State" << new_state;
+ webrtc_new_state = LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW;
+ return;
+ }
+
+ if (mAnswerReceived)
+ {
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnIceGatheringState(webrtc_new_state);
+ }
+ }
+}
+
+// Called any time the PeerConnectionState changes.
+void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state)
+{
+ RTC_LOG(LS_ERROR) << __FUNCTION__ << " Peer Connection State Change " << new_state;
+
+ switch (new_state)
+ {
+ case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected:
+ {
+ mWebRTCImpl->PostWorkerTask([this]() {
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnAudioEstablished(this);
+ }
+ });
+ break;
+ }
+ case webrtc::PeerConnectionInterface::PeerConnectionState::kFailed:
+ case webrtc::PeerConnectionInterface::PeerConnectionState::kDisconnected:
+ {
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnRenegotiationNeeded();
+ }
+
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+// Convert an ICE candidate into a string appropriate for trickling
+// to the Secondlife WebRTC server via the sim.
+static std::string iceCandidateToTrickleString(const webrtc::IceCandidateInterface *candidate)
+{
+ std::ostringstream candidate_stream;
+
+ candidate_stream <<
+ candidate->candidate().foundation() << " " <<
+ std::to_string(candidate->candidate().component()) << " " <<
+ candidate->candidate().protocol() << " " <<
+ std::to_string(candidate->candidate().priority()) << " " <<
+ candidate->candidate().address().ipaddr().ToString() << " " <<
+ candidate->candidate().address().PortAsString() << " typ ";
+
+ if (candidate->candidate().type() == cricket::LOCAL_PORT_TYPE)
+ {
+ candidate_stream << "host";
+ }
+ else if (candidate->candidate().type() == cricket::STUN_PORT_TYPE)
+ {
+ candidate_stream << "srflx " <<
+ "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " <<
+ "rport " << candidate->candidate().related_address().PortAsString();
+ }
+ else if (candidate->candidate().type() == cricket::RELAY_PORT_TYPE)
+ {
+ candidate_stream << "relay " <<
+ "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " <<
+ "rport " << candidate->candidate().related_address().PortAsString();
+ }
+ else if (candidate->candidate().type() == cricket::PRFLX_PORT_TYPE)
+ {
+ candidate_stream << "prflx " <<
+ "raddr " << candidate->candidate().related_address().ipaddr().ToString() << " " <<
+ "rport " << candidate->candidate().related_address().PortAsString();
+ }
+ else {
+ RTC_LOG(LS_ERROR) << __FUNCTION__ << " Unknown candidate type " << candidate->candidate().type();
+ }
+ if (candidate->candidate().protocol() == "tcp")
+ {
+ candidate_stream << " tcptype " << candidate->candidate().tcptype();
+ }
+
+ return candidate_stream.str();
+}
+
+// The webrtc library has a new ice candidate.
+void LLWebRTCPeerConnectionImpl::OnIceCandidate(const webrtc::IceCandidateInterface *candidate)
+{
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
+
+ if (!candidate)
+ {
+ RTC_LOG(LS_ERROR) << __FUNCTION__ << " No Ice Candidate Given";
+ return;
+ }
+ if (mAnswerReceived)
+ {
+ // We've already received an answer SDP from the Secondlife WebRTC server
+ // so simply tell observers about our new ice candidate.
+ for (auto &observer : mSignalingObserverList)
+ {
+ LLWebRTCIceCandidate ice_candidate;
+ ice_candidate.mCandidate = iceCandidateToTrickleString(candidate);
+ ice_candidate.mMLineIndex = candidate->sdp_mline_index();
+ ice_candidate.mSdpMid = candidate->sdp_mid();
+ observer->OnIceCandidate(ice_candidate);
+ }
+ }
+ else
+ {
+ // As we've not yet received our answer, cache the candidate.
+ mCachedIceCandidates.push_back(
+ webrtc::CreateIceCandidate(candidate->sdp_mid(),
+ candidate->sdp_mline_index(),
+ candidate->candidate()));
+ }
+}
+
+//
+// CreateSessionDescriptionObserver implementation.
+//
+void LLWebRTCPeerConnectionImpl::OnSuccess(webrtc::SessionDescriptionInterface *desc)
+{
+ std::string sdp;
+ desc->ToString(&sdp);
+ RTC_LOG(LS_INFO) << sdp;
+ ;
+ // mangle the sdp as this is the only way currently to bump up
+ // the send audio rate to 48k
+ std::istringstream sdp_stream(sdp);
+ std::ostringstream sdp_mangled_stream;
+ std::string sdp_line;
+ std::string opus_payload;
+ while (std::getline(sdp_stream, sdp_line))
+ {
+ int bandwidth = 0;
+ int payload_id = 0;
+ // force mono down, stereo up
+ if (std::sscanf(sdp_line.c_str(), "a=rtpmap:%i opus/%i/2", &payload_id, &bandwidth) == 2)
+ {
+ opus_payload = std::to_string(payload_id);
+ sdp_mangled_stream << "a=rtpmap:" << opus_payload << " opus/48000/2" << "\n";
+ }
+ else if (sdp_line.find("a=fmtp:" + opus_payload) == 0)
+ {
+ sdp_mangled_stream << sdp_line << "a=fmtp:" << opus_payload
+ << " minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1;maxplaybackrate=48000;sprop-maxplaybackrate=48000;sprop-maxcapturerate=48000\n";
+ }
+ else
+ {
+ sdp_mangled_stream << sdp_line << "\n";
+ }
+ }
+
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Local SDP: " << sdp_mangled_stream.str();
+ std::string mangled_sdp = sdp_mangled_stream.str();
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnOfferAvailable(mangled_sdp);
+ }
+
+ mPeerConnection->SetLocalDescription(std::unique_ptr<webrtc::SessionDescriptionInterface>(
+ webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, mangled_sdp)),
+ rtc::scoped_refptr<webrtc::SetLocalDescriptionObserverInterface>(this));
+
+}
+
+void LLWebRTCPeerConnectionImpl::OnFailure(webrtc::RTCError error)
+{
+ RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message();
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnRenegotiationNeeded();
+ }
+}
+
+//
+// SetRemoteDescriptionObserverInterface implementation.
+//
+void LLWebRTCPeerConnectionImpl::OnSetRemoteDescriptionComplete(webrtc::RTCError error)
+{
+ // we've received an answer SDP from the sim.
+
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " " << mPeerConnection->signaling_state();
+ if (!error.ok())
+ {
+ RTC_LOG(LS_ERROR) << ToString(error.type()) << ": " << error.message();
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnRenegotiationNeeded();
+ }
+ return;
+ }
+ mAnswerReceived = true;
+
+ // tell the observers about any cached ICE candidates.
+ for (auto &observer : mSignalingObserverList)
+ {
+ for (auto &candidate : mCachedIceCandidates)
+ {
+ LLWebRTCIceCandidate ice_candidate;
+ ice_candidate.mCandidate = iceCandidateToTrickleString(candidate.get());
+ ice_candidate.mMLineIndex = candidate->sdp_mline_index();
+ ice_candidate.mSdpMid = candidate->sdp_mid();
+ observer->OnIceCandidate(ice_candidate);
+ }
+ }
+ mCachedIceCandidates.clear();
+ if (mPeerConnection)
+ {
+ OnIceGatheringChange(mPeerConnection->ice_gathering_state());
+ }
+
+}
+
+//
+// SetLocalDescriptionObserverInterface implementation.
+//
+void LLWebRTCPeerConnectionImpl::OnSetLocalDescriptionComplete(webrtc::RTCError error)
+{
+}
+
+//
+// DataChannelObserver implementation
+//
+
+void LLWebRTCPeerConnectionImpl::OnStateChange()
+{
+ if (!mDataChannel)
+ {
+ return;
+ }
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State: " << webrtc::DataChannelInterface::DataStateString(mDataChannel->state());
+ switch (mDataChannel->state())
+ {
+ case webrtc::DataChannelInterface::kOpen:
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Open";
+ for (auto &observer : mSignalingObserverList)
+ {
+ observer->OnDataChannelReady(this);
+ }
+ break;
+ case webrtc::DataChannelInterface::kConnecting:
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State Connecting";
+ break;
+ case webrtc::DataChannelInterface::kClosing:
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closing";
+ break;
+ case webrtc::DataChannelInterface::kClosed:
+ RTC_LOG(LS_INFO) << __FUNCTION__ << " Data Channel State closed";
+ break;
+ default:
+ break;
+ }
+}
+
+void LLWebRTCPeerConnectionImpl::OnMessage(const webrtc::DataBuffer& buffer)
+{
+ std::string data((const char*)buffer.data.cdata(), buffer.size());
+ for (auto &observer : mDataObserverList)
+ {
+ observer->OnDataReceived(data, buffer.binary);
+ }
+}
+
+//
+// LLWebRTCDataInterface
+//
+
+void LLWebRTCPeerConnectionImpl::sendData(const std::string& data, bool binary)
+{
+ if (mDataChannel)
+ {
+ rtc::CopyOnWriteBuffer cowBuffer(data.data(), data.length());
+ webrtc::DataBuffer buffer(cowBuffer, binary);
+ mWebRTCImpl->PostNetworkTask([this, buffer]() {
+ if (mDataChannel)
+ {
+ mDataChannel->Send(buffer);
+ }
+ });
+ }
+}
+
+void LLWebRTCPeerConnectionImpl::setDataObserver(LLWebRTCDataObserver* observer)
+{
+ mDataObserverList.emplace_back(observer);
+}
+
+void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observer)
+{
+ std::vector<LLWebRTCDataObserver *>::iterator it =
+ std::find(mDataObserverList.begin(), mDataObserverList.end(), observer);
+ if (it != mDataObserverList.end())
+ {
+ mDataObserverList.erase(it);
+ }
+}
+
+LLWebRTCImpl * gWebRTCImpl = nullptr;
+LLWebRTCDeviceInterface * getDeviceInterface()
+{
+ return gWebRTCImpl;
+}
+
+LLWebRTCPeerConnectionInterface* newPeerConnection()
+{
+ return gWebRTCImpl->newPeerConnection();
+}
+
+void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection)
+{
+ gWebRTCImpl->freePeerConnection(peer_connection);
+}
+
+
+void init()
+{
+ gWebRTCImpl = new LLWebRTCImpl();
+ gWebRTCImpl->init();
+}
+
+void terminate()
+{
+ if (gWebRTCImpl)
+ {
+ gWebRTCImpl->terminate();
+ gWebRTCImpl = nullptr;
+ }
+}
+
+} // namespace llwebrtc
diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h
new file mode 100644
index 0000000000..54eefc8554
--- /dev/null
+++ b/indra/llwebrtc/llwebrtc.h
@@ -0,0 +1,278 @@
+/**
+ * @file llwebrtc.h
+ * @brief WebRTC interface
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free tSoftware
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+/*
+ * llwebrtc wraps the native webrtc c++ library in a dynamic library with a simlified interface
+ * so that the viewer can use it. This is done because native webrtc has a different
+ * overall threading model than the viewer.
+ * The native webrtc library is also compiled with clang, and has memory management
+ * functions that conflict namespace-wise with those in the viewer.
+ *
+ * Due to these differences, code from the viewer cannot be pulled in to this
+ * dynamic library, so it remains very simple.
+ */
+
+#ifndef LLWEBRTC_H
+#define LLWEBRTC_H
+
+#include <string>
+#include <vector>
+
+#ifdef LL_MAKEDLL
+#ifdef WEBRTC_WIN
+#define LLSYMEXPORT __declspec(dllexport)
+#elif WEBRTC_LINUX
+#define LLSYMEXPORT __attribute__((visibility("default")))
+#else
+#define LLSYMEXPORT /**/
+#endif
+#else
+#define LLSYMEXPORT /**/
+#endif // LL_MAKEDLL
+
+namespace llwebrtc
+{
+
+// LLWebRTCVoiceDevice is a simple representation of the
+// components of a device, used to communicate this
+// information to the viewer.
+
+
+// A note on threading.
+// Native WebRTC has it's own threading model. Some discussion
+// can be found here (https://webrtc.github.io/webrtc-org/native-code/native-apis/)
+//
+// Note that all callbacks to observers will occurr on one of the WebRTC native threads
+// (signaling, worker, etc.) Care should be taken to assure there are not
+// bad interactions with the viewer threads.
+
+class LLWebRTCVoiceDevice
+{
+ public:
+ std::string mDisplayName; // friendly name for user interface purposes
+ std::string mID; // internal value for selection
+
+ LLWebRTCVoiceDevice(const std::string &display_name, const std::string &id) :
+ mDisplayName(display_name),
+ mID(id)
+ {
+ if (mID.empty())
+ {
+ mID = display_name;
+ }
+ };
+};
+
+typedef std::vector<LLWebRTCVoiceDevice> LLWebRTCVoiceDeviceList;
+
+
+// The LLWebRTCDeviceObserver should be implemented by the viewer
+// webrtc module, which will receive notifications when devices
+// change (are unplugged, etc.)
+class LLWebRTCDevicesObserver
+{
+ public:
+ virtual void OnDevicesChanged(const LLWebRTCVoiceDeviceList &render_devices,
+ const LLWebRTCVoiceDeviceList &capture_devices) = 0;
+};
+
+
+// The LLWebRTCDeviceInterface provides a way for the viewer
+// to enumerate, set, and get notifications of changes
+// for both capture (microphone) and render (speaker)
+// devices.
+
+class LLWebRTCDeviceInterface
+{
+ public:
+ struct AudioConfig {
+
+ bool mAGC { true };
+
+ bool mEchoCancellation { true };
+
+ // TODO: The various levels of noise suppression are configured
+ // on the APM which would require setting config on the APM.
+ // We should pipe the various values through
+ // later.
+ typedef enum {
+ NOISE_SUPPRESSION_LEVEL_NONE = 0,
+ NOISE_SUPPRESSION_LEVEL_LOW,
+ NOISE_SUPPRESSION_LEVEL_MODERATE,
+ NOISE_SUPPRESSION_LEVEL_HIGH,
+ NOISE_SUPPRESSION_LEVEL_VERY_HIGH
+ } ENoiseSuppressionLevel;
+ ENoiseSuppressionLevel mNoiseSuppressionLevel { NOISE_SUPPRESSION_LEVEL_VERY_HIGH };
+ };
+
+ virtual void setAudioConfig(AudioConfig config) = 0;
+
+ // instructs webrtc to refresh the device list.
+ virtual void refreshDevices() = 0;
+
+ // set the capture and render devices using the unique identifier for the device
+ virtual void setCaptureDevice(const std::string& id) = 0;
+ virtual void setRenderDevice(const std::string& id) = 0;
+
+ // Device observers for device change callbacks.
+ virtual void setDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
+ virtual void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) = 0;
+
+ // tuning and audio levels
+ virtual void setTuningMode(bool enable) = 0;
+ virtual float getTuningAudioLevel() = 0; // for use during tuning
+ virtual float getPeerConnectionAudioLevel() = 0; // for use when not tuning
+ virtual void setPeerConnectionGain(float gain) = 0;
+};
+
+// LLWebRTCAudioInterface provides the viewer with a way
+// to set audio characteristics (mute, send and receive volume)
+class LLWebRTCAudioInterface
+{
+ public:
+ virtual void setMute(bool mute) = 0;
+ virtual void setReceiveVolume(float volume) = 0; // volume between 0.0 and 1.0
+ virtual void setSendVolume(float volume) = 0; // volume between 0.0 and 1.0
+};
+
+// LLWebRTCDataObserver allows the viewer voice module to be notified when
+// data is received over the data channel.
+class LLWebRTCDataObserver
+{
+public:
+ virtual void OnDataReceived(const std::string& data, bool binary) = 0;
+};
+
+// LLWebRTCDataInterface allows the viewer to send data over the data channel.
+class LLWebRTCDataInterface
+{
+public:
+
+ virtual void sendData(const std::string& data, bool binary=false) = 0;
+
+ virtual void setDataObserver(LLWebRTCDataObserver *observer) = 0;
+ virtual void unsetDataObserver(LLWebRTCDataObserver *observer) = 0;
+};
+
+// LLWebRTCIceCandidate is a basic structure containing
+// information needed for ICE trickling.
+struct LLWebRTCIceCandidate
+{
+ std::string mCandidate;
+ std::string mSdpMid;
+ int mMLineIndex;
+};
+
+// LLWebRTCSignalingObserver provides a way for the native
+// webrtc library to notify the viewer voice module of
+// various state changes.
+class LLWebRTCSignalingObserver
+{
+ public:
+
+ typedef enum e_ice_gathering_state {
+ ICE_GATHERING_NEW,
+ ICE_GATHERING_GATHERING,
+ ICE_GATHERING_COMPLETE
+ } EIceGatheringState;
+
+ // Called when ICE gathering states have changed.
+ // This may be called at any time, as ICE gathering
+ // can be redone while a connection is up.
+ virtual void OnIceGatheringState(EIceGatheringState state) = 0;
+
+ // Called when a new ice candidate is available.
+ virtual void OnIceCandidate(const LLWebRTCIceCandidate& candidate) = 0;
+
+ // Called when an offer is available after a connection is requested.
+ virtual void OnOfferAvailable(const std::string& sdp) = 0;
+
+ // Called when a connection enters a failure state and renegotiation is needed.
+ virtual void OnRenegotiationNeeded() = 0;
+
+ // Called when a peer connection has shut down
+ virtual void OnPeerConnectionClosed() = 0;
+
+ // Called when the audio channel has been established and audio
+ // can begin.
+ virtual void OnAudioEstablished(LLWebRTCAudioInterface *audio_interface) = 0;
+
+ // Called when the data channel has been established and data
+ // transfer can begin.
+ virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0;
+};
+
+// LLWebRTCPeerConnectionInterface representsd a connection to a peer,
+// in most cases a Secondlife WebRTC server. This interface
+// allows for management of this peer connection.
+class LLWebRTCPeerConnectionInterface
+{
+ public:
+
+ struct InitOptions
+ {
+ // equivalent of PeerConnectionInterface::IceServer
+ struct IceServers {
+
+ // Valid formats are described in RFC7064 and RFC7065.
+ // Urls should containe dns hostnames (not IP addresses)
+ // as the TLS certificate policy is 'secure.'
+ // and we do not currentply support TLS extensions.
+ std::vector<std::string> mUrls;
+ std::string mUserName;
+ std::string mPassword;
+ };
+
+ std::vector<IceServers> mServers;
+ };
+
+ virtual bool initializeConnection(const InitOptions& options) = 0;
+ virtual bool shutdownConnection() = 0;
+
+ virtual void setSignalingObserver(LLWebRTCSignalingObserver* observer) = 0;
+ virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0;
+
+ virtual void AnswerAvailable(const std::string &sdp) = 0;
+};
+
+// The following define the dynamic linked library
+// exports.
+
+// This library must be initialized before use.
+LLSYMEXPORT void init();
+
+// And should be terminated as part of shutdown.
+LLSYMEXPORT void terminate();
+
+// Return an interface for device management.
+LLSYMEXPORT LLWebRTCDeviceInterface* getDeviceInterface();
+
+// Allocate and free peer connections.
+LLSYMEXPORT LLWebRTCPeerConnectionInterface* newPeerConnection();
+LLSYMEXPORT void freePeerConnection(LLWebRTCPeerConnectionInterface *connection);
+}
+
+#endif // LLWEBRTC_H
diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h
new file mode 100644
index 0000000000..2fb5525519
--- /dev/null
+++ b/indra/llwebrtc/llwebrtc_impl.h
@@ -0,0 +1,382 @@
+/**
+ * @file llwebrtc_impl.h
+ * @brief WebRTC dynamic library implementation header
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLWEBRTC_IMPL_H
+#define LLWEBRTC_IMPL_H
+
+#define LL_MAKEDLL
+#if defined(_WIN32) || defined(_WIN64)
+#define WEBRTC_WIN 1
+#elif defined(__APPLE__)
+#define WEBRTC_MAC 1
+#define WEBRTC_POSIX 1
+#elif __linux__
+#define WEBRTC_LINUX 1
+#define WEBRTC_POSIX 1
+#endif
+
+#include "llwebrtc.h"
+// WebRTC Includes
+#ifdef WEBRTC_WIN
+#pragma warning(disable : 4996) // ignore 'deprecated.' We don't use the functions marked
+ // deprecated in the webrtc headers, but msvc complains anyway.
+ // Clang doesn't, and that's generally what webrtc uses.
+#pragma warning(disable : 4068) // ignore 'invalid pragma.' There are clang pragma's in
+ // the webrtc headers, which msvc doesn't recognize.
+#endif // WEBRTC_WIN
+
+#include "api/scoped_refptr.h"
+#include "rtc_base/ref_count.h"
+#include "rtc_base/ref_counted_object.h"
+#include "rtc_base/ssl_adapter.h"
+#include "rtc_base/thread.h"
+#include "api/peer_connection_interface.h"
+#include "api/media_stream_interface.h"
+#include "api/create_peerconnection_factory.h"
+#include "modules/audio_device/include/audio_device.h"
+#include "modules/audio_device/include/audio_device_data_observer.h"
+#include "rtc_base/task_queue.h"
+#include "api/task_queue/task_queue_factory.h"
+#include "api/task_queue/default_task_queue_factory.h"
+#include "modules/audio_device/include/audio_device_defines.h"
+
+
+namespace llwebrtc
+{
+
+class LLWebRTCPeerConnectionImpl;
+
+
+// Implements a class allowing capture of audio data
+// to determine audio level of the microphone.
+class LLAudioDeviceObserver : public webrtc::AudioDeviceDataObserver
+{
+ public:
+ LLAudioDeviceObserver();
+
+ // Retrieve the RMS audio loudness
+ float getMicrophoneEnergy();
+
+ // Data retrieved from the caputure device is
+ // passed in here for processing.
+ void OnCaptureData(const void *audio_samples,
+ const size_t num_samples,
+ const size_t bytes_per_sample,
+ const size_t num_channels,
+ const uint32_t samples_per_sec) override;
+
+ // This is for data destined for the render device.
+ // not currently used.
+ void OnRenderData(const void *audio_samples,
+ const size_t num_samples,
+ const size_t bytes_per_sample,
+ const size_t num_channels,
+ const uint32_t samples_per_sec) override;
+
+ protected:
+ static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing (30 frames)
+ float mSumVector[NUM_PACKETS_TO_FILTER];
+ float mMicrophoneEnergy;
+};
+
+// Used to process/retrieve audio levels after
+// all of the processing (AGC, AEC, etc.) for display in-world to the user.
+class LLCustomProcessor : public webrtc::CustomProcessing
+{
+ public:
+ LLCustomProcessor();
+ ~LLCustomProcessor() override {}
+
+ // (Re-) Initializes the submodule.
+ void Initialize(int sample_rate_hz, int num_channels) override;
+
+ // Analyzes the given capture or render signal.
+ void Process(webrtc::AudioBuffer *audio) override;
+
+ // Returns a string representation of the module state.
+ std::string ToString() const override { return ""; }
+
+ float getMicrophoneEnergy() { return mMicrophoneEnergy; }
+
+ void setGain(float gain) { mGain = gain; }
+
+ protected:
+ static const int NUM_PACKETS_TO_FILTER = 30; // 300 ms of smoothing
+ int mSampleRateHz;
+ int mNumChannels;
+
+ float mSumVector[NUM_PACKETS_TO_FILTER];
+ float mMicrophoneEnergy;
+ float mGain;
+};
+
+
+// Primary singleton implementation for interfacing
+// with the native webrtc library.
+class LLWebRTCImpl : public LLWebRTCDeviceInterface, public webrtc::AudioDeviceSink
+{
+ public:
+ LLWebRTCImpl();
+ ~LLWebRTCImpl() {}
+
+ void init();
+ void terminate();
+
+ //
+ // LLWebRTCDeviceInterface
+ //
+
+ void setAudioConfig(LLWebRTCDeviceInterface::AudioConfig config = LLWebRTCDeviceInterface::AudioConfig()) override;
+
+ void refreshDevices() override;
+
+ void setDevicesObserver(LLWebRTCDevicesObserver *observer) override;
+ void unsetDevicesObserver(LLWebRTCDevicesObserver *observer) override;
+
+ void setCaptureDevice(const std::string& id) override;
+ void setRenderDevice(const std::string& id) override;
+
+ void setTuningMode(bool enable) override;
+ float getTuningAudioLevel() override;
+ float getPeerConnectionAudioLevel() override;
+
+ void setPeerConnectionGain(float gain) override;
+
+ //
+ // AudioDeviceSink
+ //
+ void OnDevicesUpdated() override;
+
+ //
+ // Helpers
+ //
+
+ // The following thread helpers allow the
+ // LLWebRTCPeerConnectionImpl class to post
+ // tasks to the native webrtc threads.
+ void PostWorkerTask(absl::AnyInvocable<void() &&> task,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mWorkerThread->PostTask(std::move(task), location);
+ }
+
+ void PostSignalingTask(absl::AnyInvocable<void() &&> task,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mSignalingThread->PostTask(std::move(task), location);
+ }
+
+ void PostNetworkTask(absl::AnyInvocable<void() &&> task,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mNetworkThread->PostTask(std::move(task), location);
+ }
+
+ void WorkerBlockingCall(rtc::FunctionView<void()> functor,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mWorkerThread->BlockingCall(std::move(functor), location);
+ }
+
+ void SignalingBlockingCall(rtc::FunctionView<void()> functor,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mSignalingThread->BlockingCall(std::move(functor), location);
+ }
+
+ void NetworkBlockingCall(rtc::FunctionView<void()> functor,
+ const webrtc::Location& location = webrtc::Location::Current())
+ {
+ mNetworkThread->BlockingCall(std::move(functor), location);
+ }
+
+ // Allows the LLWebRTCPeerConnectionImpl class to retrieve the
+ // native webrtc PeerConnectionFactory.
+ rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> getPeerConnectionFactory()
+ {
+ return mPeerConnectionFactory;
+ }
+
+ // create or destroy a peer connection.
+ LLWebRTCPeerConnectionInterface* newPeerConnection();
+ void freePeerConnection(LLWebRTCPeerConnectionInterface* peer_connection);
+
+ // enables/disables capture via the capture device
+ void setRecording(bool recording);
+
+ protected:
+ // The native webrtc threads
+ std::unique_ptr<rtc::Thread> mNetworkThread;
+ std::unique_ptr<rtc::Thread> mWorkerThread;
+ std::unique_ptr<rtc::Thread> mSignalingThread;
+
+ // The factory that allows creation of native webrtc PeerConnections.
+ rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
+
+ rtc::scoped_refptr<webrtc::AudioProcessing> mAudioProcessingModule;
+
+ // more native webrtc stuff
+ std::unique_ptr<webrtc::TaskQueueFactory> mTaskQueueFactory;
+
+
+ // Devices
+ void updateDevices();
+ rtc::scoped_refptr<webrtc::AudioDeviceModule> mTuningDeviceModule;
+ rtc::scoped_refptr<webrtc::AudioDeviceModule> mPeerDeviceModule;
+ std::vector<LLWebRTCDevicesObserver *> mVoiceDevicesObserverList;
+
+ // accessors in native webrtc for devices aren't apparently implemented yet.
+ bool mTuningMode;
+ int32_t mRecordingDevice;
+ LLWebRTCVoiceDeviceList mRecordingDeviceList;
+
+ int32_t mPlayoutDevice;
+ LLWebRTCVoiceDeviceList mPlayoutDeviceList;
+
+ bool mMute;
+
+ LLAudioDeviceObserver * mTuningAudioDeviceObserver;
+ LLCustomProcessor * mPeerCustomProcessor;
+
+ // peer connections
+ std::vector<rtc::scoped_refptr<LLWebRTCPeerConnectionImpl>> mPeerConnections;
+};
+
+
+// The implementation of a peer connection, which contains
+// the various interfaces used by the viewer to interact with
+// the webrtc connection.
+class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
+ public LLWebRTCAudioInterface,
+ public LLWebRTCDataInterface,
+ public webrtc::PeerConnectionObserver,
+ public webrtc::CreateSessionDescriptionObserver,
+ public webrtc::SetRemoteDescriptionObserverInterface,
+ public webrtc::SetLocalDescriptionObserverInterface,
+ public webrtc::DataChannelObserver
+
+{
+ public:
+ LLWebRTCPeerConnectionImpl();
+ ~LLWebRTCPeerConnectionImpl();
+
+ void init(LLWebRTCImpl * webrtc_impl);
+ void terminate();
+
+ virtual void AddRef() const override = 0;
+ virtual rtc::RefCountReleaseStatus Release() const override = 0;
+
+ //
+ // LLWebRTCPeerConnection
+ //
+ bool initializeConnection(const InitOptions& options) override;
+ bool shutdownConnection() override;
+
+ void setSignalingObserver(LLWebRTCSignalingObserver *observer) override;
+ void unsetSignalingObserver(LLWebRTCSignalingObserver *observer) override;
+ void AnswerAvailable(const std::string &sdp) override;
+
+ //
+ // LLWebRTCAudioInterface
+ //
+ void setMute(bool mute) override;
+ void setReceiveVolume(float volume) override; // volume between 0.0 and 1.0
+ void setSendVolume(float volume) override; // volume between 0.0 and 1.0
+
+ //
+ // LLWebRTCDataInterface
+ //
+ void sendData(const std::string& data, bool binary=false) override;
+ void setDataObserver(LLWebRTCDataObserver *observer) override;
+ void unsetDataObserver(LLWebRTCDataObserver *observer) override;
+
+ //
+ // PeerConnectionObserver implementation.
+ //
+
+ void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState new_state) override {}
+ void OnAddTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
+ const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>> &streams) override;
+ void OnRemoveTrack(rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;
+ void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override;
+ void OnRenegotiationNeeded() override {}
+ void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState new_state) override {};
+ void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState new_state) override;
+ void OnIceCandidate(const webrtc::IceCandidateInterface *candidate) override;
+ void OnIceConnectionReceivingChange(bool receiving) override {}
+ void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState new_state) override;
+
+ //
+ // CreateSessionDescriptionObserver implementation.
+ //
+ void OnSuccess(webrtc::SessionDescriptionInterface *desc) override;
+ void OnFailure(webrtc::RTCError error) override;
+
+ //
+ // SetRemoteDescriptionObserverInterface implementation.
+ //
+ void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override;
+
+ //
+ // SetLocalDescriptionObserverInterface implementation.
+ //
+ void OnSetLocalDescriptionComplete(webrtc::RTCError error) override;
+
+ //
+ // DataChannelObserver implementation.
+ //
+ void OnStateChange() override;
+ void OnMessage(const webrtc::DataBuffer& buffer) override;
+
+ // Helpers
+ void resetMute();
+ void enableSenderTracks(bool enable);
+ void enableReceiverTracks(bool enable);
+
+ protected:
+
+ LLWebRTCImpl * mWebRTCImpl;
+
+ rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> mPeerConnectionFactory;
+
+ bool mMute;
+
+ // signaling
+ std::vector<LLWebRTCSignalingObserver *> mSignalingObserverList;
+ std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> mCachedIceCandidates;
+ bool mAnswerReceived;
+
+ rtc::scoped_refptr<webrtc::PeerConnectionInterface> mPeerConnection;
+ rtc::scoped_refptr<webrtc::MediaStreamInterface> mLocalStream;
+
+ // data
+ std::vector<LLWebRTCDataObserver *> mDataObserverList;
+ rtc::scoped_refptr<webrtc::DataChannelInterface> mDataChannel;
+};
+
+}
+
+#endif // LLWEBRTC_IMPL_H
diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp
index 964b173e74..56c393be0f 100644
--- a/indra/llwindow/llwindow.cpp
+++ b/indra/llwindow/llwindow.cpp
@@ -409,7 +409,6 @@ LLWindow* LLWindowManager::createWindow(
bool ignore_pixel_depth,
U32 fsaa_samples,
U32 max_cores,
- U32 max_vram,
F32 max_gl_version)
{
LLWindow* new_window;
@@ -427,11 +426,11 @@ LLWindow* LLWindowManager::createWindow(
#elif LL_WINDOWS
new_window = new LLWindowWin32(callbacks,
title, name, x, y, width, height, flags,
- fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_vram, max_gl_version);
+ fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_cores, max_gl_version);
#elif LL_DARWIN
new_window = new LLWindowMacOSX(callbacks,
title, name, x, y, width, height, flags,
- fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples, max_vram);
+ fullscreen, clearBg, enable_vsync, use_gl, ignore_pixel_depth, fsaa_samples);
#endif
}
else
diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h
index b7bb426654..5e06e665f3 100644
--- a/indra/llwindow/llwindow.h
+++ b/indra/llwindow/llwindow.h
@@ -164,8 +164,6 @@ public:
virtual F32 getPixelAspectRatio() = 0;
virtual void setNativeAspectRatio(F32 aspect) = 0;
- virtual void setMaxVRAMMegabytes(U32 max_vram) = 0;
-
virtual void beforeDialog() {}; // prepare to put up an OS dialog (if special measures are required, such as in fullscreen mode)
virtual void afterDialog() {}; // undo whatever was done in beforeDialog()
@@ -286,14 +284,14 @@ protected:
// Platform-neutral for accessing the platform specific message box
S32 OSMessageBox(const std::string& text, const std::string& caption, U32 type);
-const U32 OSMB_OK = 0;
-const U32 OSMB_OKCANCEL = 1;
-const U32 OSMB_YESNO = 2;
+constexpr U32 OSMB_OK = 0;
+constexpr U32 OSMB_OKCANCEL = 1;
+constexpr U32 OSMB_YESNO = 2;
-const S32 OSBTN_YES = 0;
-const S32 OSBTN_NO = 1;
-const S32 OSBTN_OK = 2;
-const S32 OSBTN_CANCEL = 3;
+constexpr S32 OSBTN_YES = 0;
+constexpr S32 OSBTN_NO = 1;
+constexpr S32 OSBTN_OK = 2;
+constexpr S32 OSBTN_CANCEL = 3;
//
// LLWindowManager
@@ -313,7 +311,6 @@ public:
bool ignore_pixel_depth = false,
U32 fsaa_samples = 0,
U32 max_cores = 0,
- U32 max_vram = 0,
F32 max_gl_version = 4.6f);
static bool destroyWindow(LLWindow* window);
static bool isWindowValid(LLWindow *window);
@@ -329,6 +326,4 @@ extern const S32 gURLProtocolWhitelistCount;
extern const std::string gURLProtocolWhitelist[];
//extern const std::string gURLProtocolWhitelistHandler[];
-void simpleEscapeString ( std::string& stringIn );
-
#endif // _LL_window_h_
diff --git a/indra/llwindow/llwindowheadless.h b/indra/llwindow/llwindowheadless.h
index 32c12b0d47..5696b69a59 100644
--- a/indra/llwindow/llwindowheadless.h
+++ b/indra/llwindow/llwindowheadless.h
@@ -101,8 +101,6 @@ public:
/*virtual*/ F32 getPixelAspectRatio() override { return 1.0f; }
/*virtual*/ void setNativeAspectRatio(F32 ratio) override {}
- void setMaxVRAMMegabytes(U32 max_vram) override {}
-
/*virtual*/ void *getPlatformWindow() override { return 0; }
/*virtual*/ void bringToFront() override {}
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp
index cd00f4d33c..80001b14ee 100644
--- a/indra/llwindow/llwindowmacosx.cpp
+++ b/indra/llwindow/llwindowmacosx.cpp
@@ -122,8 +122,7 @@ LLWindowMacOSX::LLWindowMacOSX(LLWindowCallbacks* callbacks,
bool fullscreen, bool clearBg,
bool enable_vsync, bool use_gl,
bool ignore_pixel_depth,
- U32 fsaa_samples,
- U32 max_vram)
+ U32 fsaa_samples)
: LLWindow(NULL, fullscreen, flags)
{
// *HACK: During window construction we get lots of OS events for window
diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h
index 3be89c255a..f5b6441746 100644
--- a/indra/llwindow/llwindowmacosx.h
+++ b/indra/llwindow/llwindowmacosx.h
@@ -100,8 +100,6 @@ public:
F32 getPixelAspectRatio() override;
void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; }
- virtual void setMaxVRAMMegabytes(U32 max_vram) override {}
-
void beforeDialog() override;
void afterDialog() override;
@@ -154,8 +152,7 @@ protected:
const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags,
bool fullscreen, bool clearBg, bool enable_vsync, bool use_gl,
bool ignore_pixel_depth,
- U32 fsaa_samples,
- U32 max_vram);
+ U32 fsaa_samples);
~LLWindowMacOSX();
void initCursors();
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index 3d349b2080..f0f7e03691 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -404,7 +404,6 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
using FuncType = std::function<void()>;
// call GetMessage() and pull enqueue messages for later processing
- void gatherInput();
HWND mWindowHandleThrd = NULL;
HDC mhDCThrd = 0;
@@ -412,8 +411,6 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
// until after some graphics setup. See SL-20177. -Cosmic,2023-09-18
bool mGLReady = false;
bool mGotGLBuffer = false;
-
- U32 mMaxVRAM = 0; // maximum amount of vram to allow in the "budget", or 0 for no maximum (see updateVRAMUsage)
};
@@ -425,7 +422,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
bool ignore_pixel_depth,
U32 fsaa_samples,
U32 max_cores,
- U32 max_vram,
F32 max_gl_version)
:
LLWindow(callbacks, fullscreen, flags),
@@ -434,7 +430,6 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
{
sMainThreadId = LLThread::currentID();
mWindowThread = new LLWindowWin32Thread();
- mWindowThread->mMaxVRAM = max_vram;
//MAINT-516 -- force a load of opengl32.dll just in case windows went sideways
LoadLibrary(L"opengl32.dll");
@@ -4545,15 +4540,6 @@ std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList()
// Fonts previously in getFontListSans() have moved to fonts.xml.
return std::vector<std::string>();
}
-
-void LLWindowWin32::setMaxVRAMMegabytes(U32 max_vram)
-{
- if (mWindowThread)
- {
- mWindowThread->mMaxVRAM = max_vram;
- }
-}
-
#endif // LL_WINDOWS
inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h
index 82541ace6e..287402faa0 100644
--- a/indra/llwindow/llwindowwin32.h
+++ b/indra/llwindow/llwindowwin32.h
@@ -108,8 +108,6 @@ public:
/*virtual*/ F32 getPixelAspectRatio();
/*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; }
- /*virtual*/ void setMaxVRAMMegabytes(U32 max_vram) override;
-
/*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b );
/*virtual*/ void *getPlatformWindow();
@@ -142,11 +140,10 @@ protected:
LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, int x, int y, int width, int height, U32 flags,
bool fullscreen, bool clearBg, bool enable_vsync, bool use_gl,
- bool ignore_pixel_depth, U32 fsaa_samples, U32 max_cores, U32 max_vram, F32 max_gl_version);
+ bool ignore_pixel_depth, U32 fsaa_samples, U32 max_cores, F32 max_gl_version);
~LLWindowWin32();
void initCursors();
- void initInputDevices();
HCURSOR loadColorCursor(LPCTSTR name);
bool isValid();
void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size);
@@ -163,7 +160,6 @@ protected:
bool shouldPostQuit() { return mPostQuit; }
- void fillCompositionForm(const LLRect& bounds, COMPOSITIONFORM *form);
void fillCandidateForm(const LLCoordGL& caret, const LLRect& bounds, CANDIDATEFORM *form);
void fillCharPosition(const LLCoordGL& caret, const LLRect& bounds, const LLRect& control, IMECHARPOSITION *char_position);
void fillCompositionLogfont(LOGFONT *logfont);
@@ -178,10 +174,8 @@ protected:
//
bool getClientRectInScreenSpace(RECT* rectp);
- void updateJoystick( );
static LRESULT CALLBACK mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param);
- static bool CALLBACK enumChildWindows(HWND h_wnd, LPARAM l_param);
//
@@ -288,8 +282,6 @@ private:
extern LLW32MsgCallback gAsyncMsgCallback;
extern LPWSTR gIconResource;
-static void handleMessage( const MSG& msg );
-
S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 type);
#endif //LL_LLWINDOWWIN32_H
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7568c08430..cbcc85cc1c 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -219,6 +219,7 @@ set(viewer_SOURCE_FILES
llfloaterfonttest.cpp
llfloaterforgetuser.cpp
llfloatergesture.cpp
+ llfloatergltfasseteditor.cpp
llfloatergodtools.cpp
llfloatergotoline.cpp
llfloatergridstatus.cpp
@@ -313,6 +314,8 @@ set(viewer_SOURCE_FILES
llgesturemgr.cpp
llgiveinventory.cpp
llglsandbox.cpp
+ llgltffolderitem.cpp
+ llgltffoldermodel.cpp
llgltfmateriallist.cpp
llgltfmaterialpreviewmgr.cpp
llgroupactions.cpp
@@ -694,6 +697,7 @@ set(viewer_SOURCE_FILES
llvoiceclient.cpp
llvoicevisualizer.cpp
llvoicevivox.cpp
+ llvoicewebrtc.cpp
llvoinventorylistener.cpp
llvopartgroup.cpp
llvosky.cpp
@@ -878,6 +882,7 @@ set(viewer_HEADER_FILES
llfloaterfonttest.h
llfloaterforgetuser.h
llfloatergesture.h
+ llfloatergltfasseteditor.h
llfloatergodtools.h
llfloatergotoline.h
llfloatergridstatus.h
@@ -974,6 +979,8 @@ set(viewer_HEADER_FILES
llgesturelistener.h
llgesturemgr.h
llgiveinventory.h
+ llgltffolderitem.h
+ llgltffoldermodel.h
llgltfmateriallist.h
llgltfmaterialpreviewmgr.h
llgroupactions.h
@@ -1346,6 +1353,7 @@ set(viewer_HEADER_FILES
llvoiceclient.h
llvoicevisualizer.h
llvoicevivox.h
+ llvoicewebrtc.h
llvoinventorylistener.h
llvopartgroup.h
llvosky.h
@@ -1444,6 +1452,7 @@ if (LINUX)
endif (LINUX)
if (WINDOWS)
+
list(APPEND viewer_SOURCE_FILES
llappviewerwin32.cpp
llwindebug.cpp
@@ -1719,6 +1728,7 @@ if (WINDOWS)
${SHARED_LIB_STAGING_DIR}/openjp2.dll
${SHARED_LIB_STAGING_DIR}/libhunspell.dll
${SHARED_LIB_STAGING_DIR}/uriparser.dll
+ ${SHARED_LIB_STAGING_DIR}/llwebrtc.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll
@@ -1777,13 +1787,14 @@ if (WINDOWS)
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
stage_third_party_libs
+ llwebrtc
${COPY_INPUT_DEPENDENCIES}
COMMENT "Performing viewer_manifest copy"
)
add_custom_target(copy_w_viewer_manifest ALL DEPENDS ${CMAKE_CFG_INTDIR}/copy_touched.bat)
- add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon copy_w_viewer_manifest)
+ add_dependencies(${VIEWER_BINARY_NAME} stage_third_party_libs llcommon llwebrtc copy_w_viewer_manifest)
if (EXISTS ${CMAKE_SOURCE_DIR}/copy_win_scripts)
add_dependencies(${VIEWER_BINARY_NAME} copy_win_scripts)
@@ -1906,6 +1917,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}
llcorehttp
llcommon
llmeshoptimizer
+ llwebrtc
ll::ndof
lllogin
llprimitive
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 5f44d24a63..c455b6f5f1 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -7716,6 +7716,28 @@
<key>Value</key>
<integer>0</integer>
</map>
+ <key>RenderMinFreeMainMemoryThreshold</key>
+ <map>
+ <key>Comment</key>
+ <string>Minimum of available physical memory in MB before textures get scaled down</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>512</integer>
+ </map>
+ <key>RenderLowMemMinDiscardIncrement</key>
+ <map>
+ <key>Comment</key>
+ <string>Minimum increment of discard level if system memory gets low</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.1</real>
+ </map>
<key>RenderMaxTextureIndex</key>
<map>
<key>Comment</key>
@@ -13065,7 +13087,7 @@
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
- <integer>1</integer>
+ <integer>0</integer>
</map>
<key>AutoDisengageMic</key>
<map>
@@ -13243,6 +13265,39 @@
<key>Value</key>
<integer>44125</integer>
</map>
+ <key>VoiceEchoCancellation</key>
+ <map>
+ <key>Comment</key>
+ <string>Voice Echo Cancellation</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>VoiceAutomaticGainControl</key>
+ <map>
+ <key>Comment</key>
+ <string>Voice Automatic Gain Control</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>VoiceNoiseSuppressionLevel</key>
+ <map>
+ <key>Comment</key>
+ <string>Voice Noise Suppression Level</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <integer>4</integer>
+ </map>
<key>WarningsAsChat</key>
<map>
<key>Comment</key>
@@ -13257,13 +13312,13 @@
<key>VoiceServerType</key>
<map>
<key>Comment</key>
- <string>The type of voice server to connect to.</string>
+ <string>The type of voice server to use for group, conference, and p2p calls.</string>
<key>Persist</key>
<integer>0</integer>
<key>Type</key>
<string>String</string>
<key>Value</key>
- <string>vivox</string>
+ <string/>
</map>
<key>WLSkyDetail</key>
<map>
diff --git a/indra/newview/app_settings/shaders/class1/deferred/normgenF.glsl b/indra/newview/app_settings/shaders/class1/deferred/normgenF.glsl
index 902746366d..6d05d983f3 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/normgenF.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/normgenF.glsl
@@ -25,24 +25,53 @@
/*[EXTRA_CODE_HERE]*/
+
+
+// generate a normal map using an approximation of the old emboss bump map "brightness/darkness" technique
+// srcMap is a source color image, output should be a normal
+
out vec4 frag_color;
-uniform sampler2D alphaMap;
+uniform sampler2D srcMap;
in vec2 vary_texcoord0;
uniform float stepX;
uniform float stepY;
uniform float norm_scale;
+uniform int bump_code;
+
+#define BE_BRIGHTNESS 1
+#define BE_DARKNESS 2
+
+// get luminance or inverse luminance depending on bump_code
+float getBumpValue(vec2 texcoord)
+{
+ vec3 c = texture(srcMap, texcoord).rgb;
+
+ vec3 WEIGHT = vec3(0.2995, 0.5875, 0.1145);
+
+ float l = dot(c, WEIGHT);
+
+ if (bump_code == BE_DARKNESS)
+ {
+ l = 1.0 - l;
+ }
+
+ return l;
+}
+
void main()
{
- float c = texture(alphaMap, vary_texcoord0).r;
+ float c = getBumpValue(vary_texcoord0).r;
+
+ float scaler = 512.0;
- vec3 right = vec3(norm_scale, 0, (texture(alphaMap, vary_texcoord0+vec2(stepX, 0)).r-c)*255);
- vec3 left = vec3(-norm_scale, 0, (texture(alphaMap, vary_texcoord0-vec2(stepX, 0)).r-c)*255);
- vec3 up = vec3(0, -norm_scale, (texture(alphaMap, vary_texcoord0-vec2(0, stepY)).r-c)*255);
- vec3 down = vec3(0, norm_scale, (texture(alphaMap, vary_texcoord0+vec2(0, stepY)).r-c)*255);
+ vec3 right = vec3(norm_scale, 0, (getBumpValue(vary_texcoord0+vec2(stepX, 0)).r-c)*scaler);
+ vec3 left = vec3(-norm_scale, 0, (getBumpValue(vary_texcoord0-vec2(stepX, 0)).r-c)*scaler);
+ vec3 up = vec3(0, -norm_scale, (getBumpValue(vary_texcoord0-vec2(0, stepY)).r-c)*scaler);
+ vec3 down = vec3(0, norm_scale, (getBumpValue(vary_texcoord0+vec2(0, stepY)).r-c)*scaler);
vec3 norm = cross(right, down) + cross(down, left) + cross(left,up) + cross(up, right);
diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl
index 789c00259b..ac4ff50552 100644
--- a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl
+++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl
@@ -28,18 +28,40 @@
// GLTF pbrMetallicRoughness implementation
+uniform int gltf_material_id;
+
+vec3 emissiveColor = vec3(0,0,0);
+float metallicFactor = 1.0;
+float roughnessFactor = 1.0;
+float minimum_alpha = -1.0;
+
+layout (std140) uniform GLTFMaterials
+{
+ // see pbrmetallicroughnessV.glsl for packing
+ vec4 gltf_material_data[MAX_UBO_VEC4S];
+};
+
+void unpackMaterial()
+{
+ if (gltf_material_id > -1)
+ {
+ int idx = gltf_material_id*12;
+ emissiveColor = gltf_material_data[idx+10].rgb;
+ roughnessFactor = gltf_material_data[idx+11].g;
+ metallicFactor = gltf_material_data[idx+11].b;
+ minimum_alpha -= gltf_material_data[idx+11].a;
+ }
+}
// ==================================
// needed by all variants
// ==================================
uniform sampler2D diffuseMap; //always in sRGB space
uniform sampler2D emissiveMap;
-uniform vec3 emissiveColor;
in vec3 vary_position;
in vec4 vertex_color;
in vec2 base_color_uv;
in vec2 emissive_uv;
-uniform float minimum_alpha;
void mirrorClip(vec3 pos);
vec3 linear_to_srgb(vec3 c);
@@ -54,8 +76,6 @@ vec3 srgb_to_linear(vec3 c);
uniform sampler2D normalMap;
uniform sampler2D metallicRoughnessMap;
uniform sampler2D occlusionMap;
-uniform float metallicFactor;
-uniform float roughnessFactor;
in vec3 vary_normal;
in vec3 vary_tangent;
flat in float vary_sign;
@@ -154,7 +174,7 @@ out vec4 frag_data[4];
void main()
{
-
+ unpackMaterial();
// ==================================
// all variants
// mirror clip
@@ -165,6 +185,10 @@ void main()
vec3 pos = vary_position;
mirrorClip(pos);
+#ifdef ALPHA_BLEND
+ //waterClip(pos);
+#endif
+
vec4 basecolor = texture(diffuseMap, base_color_uv.xy).rgba;
basecolor.rgb = srgb_to_linear(basecolor.rgb);
basecolor *= vertex_color;
diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl
index aac3dc917f..6a628bc852 100644
--- a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl
+++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl
@@ -26,19 +26,95 @@
// GLTF pbrMetallicRoughness implementation
uniform mat4 modelview_matrix;
-
-#ifdef HAS_SKIN
uniform mat4 projection_matrix;
-#else
-uniform mat3 normal_matrix;
-uniform mat4 modelview_projection_matrix;
+
+#ifdef MULTI_UV
+in vec2 texcoord1;
+int base_color_texcoord = 0;
+int emissive_texcoord = 0;
+#ifndef UNLIT
+int normal_texcoord = 0;
+int metallic_roughness_texcoord = 0;
+int occlusion_texcoord = 0;
+#endif
+#endif
+
+uniform int gltf_material_id;
+
+layout (std140) uniform GLTFMaterials
+{
+ // index by gltf_material_id*12
+
+ // [gltf_material_id + [0-1]] - base color transform
+ // [gltf_material_id + [2-3]] - normal transform
+ // [gltf_material_id + [4-5]] - metallic roughness transform
+ // [gltf_material_id + [6-7]] - emissive transform
+ // [gltf_material_id + [8-9]] - occlusion transform
+ // [gltf_material_id + 10] - emissive factor
+ // [gltf_material_id + 11] - .r unused, .g roughness, .b metalness, .a minimum alpha
+
+ // Transforms are packed as follows
+ // packed[0] = vec4(scale.x, scale.y, rotation, offset.x)
+ // packed[1] = vec4(mScale.y, texcoord, 0, 0)
+ vec4 gltf_material_data[MAX_UBO_VEC4S];
+};
+
+vec4[2] texture_base_color_transform;
+vec4[2] texture_normal_transform;
+vec4[2] texture_metallic_roughness_transform;
+vec4[2] texture_emissive_transform;
+vec4[2] texture_occlusion_transform;
+
+void unpackTextureTransforms()
+{
+ if (gltf_material_id != -1)
+ {
+ int idx = gltf_material_id*12;
+
+ texture_base_color_transform[0] = gltf_material_data[idx+0];
+ texture_base_color_transform[1] = gltf_material_data[idx+1];
+
+ texture_normal_transform[0] = gltf_material_data[idx+2];
+ texture_normal_transform[1] = gltf_material_data[idx+3];
+
+ texture_metallic_roughness_transform[0] = gltf_material_data[idx+4];
+ texture_metallic_roughness_transform[1] = gltf_material_data[idx+5];
+
+ texture_emissive_transform[0] = gltf_material_data[idx+6];
+ texture_emissive_transform[1] = gltf_material_data[idx+7];
+
+ texture_occlusion_transform[0] = gltf_material_data[idx+8];
+ texture_occlusion_transform[1] = gltf_material_data[idx+9];
+
+#ifdef MULTI_UV
+ base_color_texcoord = int(gltf_material_data[idx+1].g);
+ emissive_texcoord = int(gltf_material_data[idx+7].g);
+#ifndef UNLIT
+ normal_texcoord = int(gltf_material_data[idx+3].g);
+ metallic_roughness_texcoord = int(gltf_material_data[idx+5].g);
+ occlusion_texcoord = int(gltf_material_data[idx+9].g);
#endif
+#endif
+ }
+ else
+ {
+ texture_base_color_transform[0] = vec4(1.0, 1.0, 0.0, 0.0);
+ texture_base_color_transform[1] = vec4(0.0, 0.0, 0.0, 0.0);
+
+ texture_normal_transform[0] = vec4(1.0, 1.0, 0.0, 0.0);
+ texture_normal_transform[1] = vec4(0.0, 0.0, 0.0, 0.0);
+
+ texture_metallic_roughness_transform[0] = vec4(1.0, 1.0, 0.0, 0.0);
+ texture_metallic_roughness_transform[1] = vec4(0.0, 0.0, 0.0, 0.0);
+
+ texture_emissive_transform[0] = vec4(1.0, 1.0, 0.0, 0.0);
+ texture_emissive_transform[1] = vec4(0.0, 0.0, 0.0, 0.0);
+
+ texture_occlusion_transform[0] = vec4(1.0, 1.0, 0.0, 0.0);
+ texture_occlusion_transform[1] = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+}
-uniform vec4[2] texture_base_color_transform;
-uniform vec4[2] texture_normal_transform;
-uniform vec4[2] texture_metallic_roughness_transform;
-uniform vec4[2] texture_emissive_transform;
-uniform vec4[2] texture_occlusion_transform;
in vec3 position;
in vec4 diffuse_color;
@@ -59,17 +135,6 @@ flat out float vary_sign;
out vec3 vary_normal;
#endif
-#ifdef MULTI_UV
-in vec2 texcoord1;
-uniform int base_color_texcoord;
-uniform int emissive_texcoord;
-#ifndef UNLIT
-uniform int normal_texcoord;
-uniform int metallic_roughness_texcoord;
-uniform int occlusion_texcoord;
-#endif
-#endif
-
vec2 gltf_texture_transform(vec2 texcoord, vec4[2] p)
{
texcoord.y = 1.0 - texcoord.y;
@@ -124,23 +189,22 @@ vec3 gltf_tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[
}
#endif
-
-
#ifdef ALPHA_BLEND
out vec3 vary_fragcoord;
#endif
#ifdef HAS_SKIN
-in uvec4 joint;
-in vec4 weight4;
layout (std140) uniform GLTFJoints
{
- // list of OBBs for user override probes
- mat3x4 gltf_joints[MAX_JOINTS_PER_GLTF_OBJECT];
+ mat3x4 gltf_joints[MAX_NODES_PER_GLTF_OBJECT];
};
-mat4 getGLTFSkinTransform()
+
+in uvec4 joint;
+in vec4 weight4;
+
+mat4 getGLTFTransform()
{
int i;
@@ -169,21 +233,37 @@ mat4 getGLTFSkinTransform()
ret[3] = vec4(trans, 1.0);
return ret;
+}
-#ifdef IS_AMD_CARD
- // If it's AMD make sure the GLSL compiler sees the arrays referenced once by static index. Otherwise it seems to optimise the storage awawy which leads to unfun crashes and artifacts.
- mat3x4 dummy1 = gltf_joints[0];
- mat3x4 dummy2 = gltf_joints[MAX_JOINTS_PER_GLTF_OBJECT-1];
-#endif
+#else
+
+layout (std140) uniform GLTFNodes
+{
+ mat3x4 gltf_nodes[MAX_NODES_PER_GLTF_OBJECT];
+};
+
+uniform int gltf_node_id = 0;
+
+mat4 getGLTFTransform()
+{
+ mat4 ret;
+ mat3x4 src = gltf_nodes[gltf_node_id];
+
+ ret[0] = vec4(src[0].xyz, 0);
+ ret[1] = vec4(src[1].xyz, 0);
+ ret[2] = vec4(src[2].xyz, 0);
+
+ ret[3] = vec4(src[0].w, src[1].w, src[2].w, 1);
+ return ret;
}
#endif
void main()
{
-#ifdef HAS_SKIN
- mat4 mat = getGLTFSkinTransform();
+ unpackTextureTransforms();
+ mat4 mat = getGLTFTransform();
mat = modelview_matrix * mat;
@@ -193,13 +273,6 @@ void main()
vec4 vert = projection_matrix * vec4(pos, 1.0);
gl_Position = vert;
-#else
- vary_position = (modelview_matrix*vec4(position.xyz, 1.0)).xyz;
- //transform vertex
- vec4 vert = modelview_projection_matrix * vec4(position.xyz, 1.0);
- gl_Position = vert;
-#endif
-
vec2 bcuv;
vec2 emuv;
@@ -237,13 +310,8 @@ void main()
#endif
#ifndef UNLIT
-#ifdef HAS_SKIN
vec3 n = (mat*vec4(normal.xyz+position.xyz,1.0)).xyz-pos.xyz;
vec3 t = (mat*vec4(tangent.xyz+position.xyz,1.0)).xyz-pos.xyz;
-#else //HAS_SKIN
- vec3 n = normal_matrix * normal;
- vec3 t = normal_matrix * tangent.xyz;
-#endif
n = normalize(n);
vary_tangent = normalize(gltf_tangent_space_transform(vec4(t, tangent.w), n, texture_normal_transform));
diff --git a/indra/newview/featuretable.txt b/indra/newview/featuretable.txt
index 8950770172..03849a0326 100644
--- a/indra/newview/featuretable.txt
+++ b/indra/newview/featuretable.txt
@@ -111,7 +111,7 @@ RenderReflectionProbeLevel 1 0
RenderMirrors 1 0
RenderHeroProbeResolution 1 256
RenderHeroProbeDistance 1 4
-RenderHeroProbeUpdateRate 1 4
+RenderHeroProbeUpdateRate 1 6
RenderHeroProbeConservativeUpdateMultiplier 1 16
//
diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt
index 8c71235f37..7aa8504eaa 100644
--- a/indra/newview/featuretable_mac.txt
+++ b/indra/newview/featuretable_mac.txt
@@ -109,7 +109,7 @@ RenderReflectionProbeLevel 1 0
RenderMirrors 1 0
RenderHeroProbeResolution 1 256
RenderHeroProbeDistance 1 4
-RenderHeroProbeUpdateRate 1 4
+RenderHeroProbeUpdateRate 1 6
RenderHeroProbeConservativeUpdateMultiplier 1 16
//
diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp
index 8b85eba3e5..3dff67d746 100644
--- a/indra/newview/gltf/animation.cpp
+++ b/indra/newview/gltf/animation.cpp
@@ -70,6 +70,14 @@ bool Animation::prep(Asset& asset)
}
}
+ for (auto& channel : mScaleChannels)
+ {
+ if (!channel.prep(asset, mSamplers[channel.mSampler]))
+ {
+ return false;
+ }
+ }
+
return true;
}
@@ -82,18 +90,37 @@ void Animation::update(Asset& asset, F32 dt)
void Animation::apply(Asset& asset, float time)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+
// convert time to animation loop time
time = fmod(time, mMaxTime - mMinTime) + mMinTime;
// apply each channel
- for (auto& channel : mRotationChannels)
{
- channel.apply(asset, mSamplers[channel.mSampler], time);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - rotation");
+
+ for (auto& channel : mRotationChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - translation");
+
+ for (auto& channel : mTranslationChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
}
- for (auto& channel : mTranslationChannels)
{
- channel.apply(asset, mSamplers[channel.mSampler], time);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - scale");
+
+ for (auto& channel : mScaleChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
}
};
@@ -178,7 +205,8 @@ const Animation::Channel& Animation::Channel::operator=(const Value& src)
void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F32& t)
{
- LL_PROFILE_ZONE_SCOPED;
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ llassert(mFrameTimes.size() > 1); // if there is only one frame, there is no need to interpolate
if (time < mMinTime)
{
@@ -187,32 +215,33 @@ void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F
return;
}
- if (mFrameTimes.size() > 1)
+ frameIndex = U32(mFrameTimes.size()) - 2;
+ t = 1.f;
+
+ if (time > mMaxTime)
{
- llassert(mFrameTimes.size() <= size_t(U32_MAX));
- frameIndex = U32(mFrameTimes.size()) - 2;
- t = 1.f;
+ return;
+ }
- if (time > mMaxTime)
- {
- return;
- }
+ if (time < mLastFrameTime)
+ {
+ mLastFrameIndex = 0;
+ }
- for (U32 i = 0; i < (U32)mFrameTimes.size() - 1; i++)
+ mLastFrameTime = time;
+
+ U32 idx = mLastFrameIndex;
+
+ for (U32 i = idx; i < (U32)mFrameTimes.size() - 1; i++)
+ {
+ if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1])
{
- if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1])
- {
- frameIndex = i;
- t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]);
- return;
- }
+ frameIndex = i;
+ t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]);
+ mLastFrameIndex = frameIndex;
+ return;
}
}
- else
- {
- frameIndex = 0;
- t = 0.0f;
- }
}
bool Animation::RotationChannel::prep(Asset& asset, Animation::Sampler& sampler)
@@ -231,14 +260,14 @@ void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time)
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setRotation(mRotations[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
quat qf = glm::slerp(mRotations[frameIndex], mRotations[frameIndex + 1], t);
@@ -264,14 +293,14 @@ void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 ti
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setTranslation(mTranslations[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
const vec3& v0 = mTranslations[frameIndex];
const vec3& v1 = mTranslations[frameIndex + 1];
@@ -298,14 +327,14 @@ void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time)
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setScale(mScales[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
const vec3& v0 = mScales[frameIndex];
const vec3& v1 = mScales[frameIndex + 1];
@@ -373,6 +402,7 @@ Skin::~Skin()
void Skin::uploadMatrixPalette(Asset& asset)
{
// prepare matrix palette
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
U32 max_joints = LLSkinningUtil::getMaxGLTFJointCount();
diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h
index d5426fd4ce..ab8839470a 100644
--- a/indra/newview/gltf/animation.h
+++ b/indra/newview/gltf/animation.h
@@ -49,6 +49,9 @@ namespace LL
S32 mOutput = INVALID_INDEX;
std::string mInterpolation;
+ F32 mLastFrameTime = 0.f;
+ U32 mLastFrameIndex = 0;
+
bool prep(Asset& asset);
void serialize(boost::json::object& dst) const;
diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp
index 21be69aae2..a454e68a92 100644
--- a/indra/newview/gltf/asset.cpp
+++ b/indra/newview/gltf/asset.cpp
@@ -35,6 +35,7 @@
#include "buffer_util.h"
#include <boost/url.hpp>
#include "llimagejpeg.h"
+#include "../llskinningutil.h"
using namespace LL::GLTF;
using namespace boost::json;
@@ -86,7 +87,6 @@ namespace LL
}
}
-
void Scene::updateTransforms(Asset& asset)
{
mat4 identity = glm::identity<mat4>();
@@ -98,26 +98,6 @@ void Scene::updateTransforms(Asset& asset)
}
}
-void Scene::updateRenderTransforms(Asset& asset, const mat4& modelview)
-{
- for (auto& nodeIndex : mNodes)
- {
- Node& node = asset.mNodes[nodeIndex];
- node.updateRenderTransforms(asset, modelview);
- }
-}
-
-void Node::updateRenderTransforms(Asset& asset, const mat4& modelview)
-{
- mRenderMatrix = modelview * mMatrix;
-
- for (auto& childIndex : mChildren)
- {
- Node& child = asset.mNodes[childIndex];
- child.updateRenderTransforms(asset, mRenderMatrix);
- }
-}
-
void Node::updateTransforms(Asset& asset, const mat4& parentMatrix)
{
makeMatrixValid();
@@ -137,19 +117,119 @@ void Node::updateTransforms(Asset& asset, const mat4& parentMatrix)
void Asset::updateTransforms()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
for (auto& scene : mScenes)
{
scene.updateTransforms(*this);
}
+
+ uploadTransforms();
}
-void Asset::updateRenderTransforms(const mat4& modelview)
+void Asset::uploadTransforms()
{
- // use mAssetMatrix to update render transforms from node list
- for (auto& node : mNodes)
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ // prepare matrix palette
+ U32 max_nodes = LLSkinningUtil::getMaxGLTFJointCount();
+
+ size_t node_count = llmin<size_t>(max_nodes, mNodes.size());
+
+ std::vector<mat4> t_mp;
+
+ t_mp.resize(node_count);
+
+ for (U32 i = 0; i < node_count; ++i)
{
- node.mRenderMatrix = modelview * node.mAssetMatrix;
+ Node& node = mNodes[i];
+ // build matrix palette in asset space
+ t_mp[i] = node.mAssetMatrix;
}
+
+ std::vector<F32> glmp;
+
+ glmp.resize(node_count * 12);
+
+ F32* mp = glmp.data();
+
+ for (U32 i = 0; i < node_count; ++i)
+ {
+ F32* m = glm::value_ptr(t_mp[i]);
+
+ U32 idx = i * 12;
+
+ mp[idx + 0] = m[0];
+ mp[idx + 1] = m[1];
+ mp[idx + 2] = m[2];
+ mp[idx + 3] = m[12];
+
+ mp[idx + 4] = m[4];
+ mp[idx + 5] = m[5];
+ mp[idx + 6] = m[6];
+ mp[idx + 7] = m[13];
+
+ mp[idx + 8] = m[8];
+ mp[idx + 9] = m[9];
+ mp[idx + 10] = m[10];
+ mp[idx + 11] = m[14];
+ }
+
+ if (mNodesUBO == 0)
+ {
+ glGenBuffers(1, &mNodesUBO);
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, mNodesUBO);
+ glBufferData(GL_UNIFORM_BUFFER, glmp.size() * sizeof(F32), glmp.data(), GL_STREAM_DRAW);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
+}
+
+void Asset::uploadMaterials()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ // see pbrmetallicroughnessV.glsl for the layout of the material UBO
+ std::vector<vec4> md;
+
+ U32 material_size = sizeof(vec4) * 12;
+ U32 max_materials = gGLManager.mMaxUniformBlockSize / material_size;
+
+ U32 mat_count = (U32)mMaterials.size();
+ mat_count = llmin(mat_count, max_materials);
+
+ md.resize(mat_count * 12);
+
+ for (U32 i = 0; i < mat_count*12; i += 12)
+ {
+ Material& material = mMaterials[i/12];
+
+ // add texture transforms and UV indices
+ material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(&md[i+0]);
+ md[i + 1].g = (F32)material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord();
+ material.mNormalTexture.mTextureTransform.getPacked(&md[i + 2]);
+ md[i + 3].g = (F32)material.mNormalTexture.getTexCoord();
+ material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(&md[i+4]);
+ md[i + 5].g = (F32)material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord();
+ material.mEmissiveTexture.mTextureTransform.getPacked(&md[i + 6]);
+ md[i + 7].g = (F32)material.mEmissiveTexture.getTexCoord();
+ material.mOcclusionTexture.mTextureTransform.getPacked(&md[i + 8]);
+ md[i + 9].g = (F32)material.mOcclusionTexture.getTexCoord();
+
+ // add material properties
+ F32 min_alpha = material.mAlphaMode == Material::AlphaMode::MASK ? material.mAlphaCutoff : -1.0f;
+ md[i + 10] = vec4(material.mEmissiveFactor, 1.f);
+ md[i + 11] = vec4(0.f,
+ material.mPbrMetallicRoughness.mRoughnessFactor,
+ material.mPbrMetallicRoughness.mMetallicFactor,
+ min_alpha);
+ }
+
+ if (mMaterialsUBO == 0)
+ {
+ glGenBuffers(1, &mMaterialsUBO);
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, mMaterialsUBO);
+ glBufferData(GL_UNIFORM_BUFFER, md.size() * sizeof(vec4), md.data(), GL_STREAM_DRAW);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
@@ -363,6 +443,7 @@ const Image& Image::operator=(const Value& src)
void Asset::update()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
F32 dt = gFrameTimeSeconds - mLastUpdateTime;
if (dt > 0.f)
@@ -383,11 +464,27 @@ void Asset::update()
{
skin.uploadMatrixPalette(*this);
}
+
+ uploadMaterials();
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltf - addTextureStats");
+
+ for (auto& image : mImages)
+ {
+ if (image.mTexture.notNull())
+ { // HACK - force texture to be loaded full rez
+ // TODO: calculate actual vsize
+ image.mTexture->addTextureStats(2048.f * 2048.f);
+ }
+ }
+ }
}
}
bool Asset::prep()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
// check required extensions and fail if not supported
bool unsupported = false;
for (auto& extension : mExtensionsRequired)
@@ -445,6 +542,127 @@ bool Asset::prep()
}
}
+ // prepare vertex buffers
+
+ // material count is number of materials + 1 for default material
+ U32 mat_count = (U32) mMaterials.size() + 1;
+
+ if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
+ { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
+ gDebugProgram.bind();
+ }
+
+ for (S32 double_sided = 0; double_sided < 2; ++double_sided)
+ {
+ RenderData& rd = mRenderData[double_sided];
+ for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
+ {
+ rd.mBatches[i].resize(mat_count);
+ }
+
+ // for each material
+ for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
+ {
+ // for each shader variant
+ U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+ U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+
+ S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
+ if (ds_mat != double_sided)
+ {
+ continue;
+ }
+
+ for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
+ {
+ U32 attribute_mask = 0;
+ // for each mesh
+ for (auto& mesh : mMeshes)
+ {
+ // for each primitive
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ // accumulate vertex and index counts
+ primitive.mVertexOffset = vertex_count[variant];
+ primitive.mIndexOffset = index_count[variant];
+
+ vertex_count[variant] += primitive.getVertexCount();
+ index_count[variant] += primitive.getIndexCount();
+
+ // all primitives of a given variant and material should all have the same attribute mask
+ llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
+ attribute_mask |= primitive.mAttributeMask;
+ }
+ }
+ }
+
+ // allocate vertex buffer and pack it
+ if (vertex_count[variant] > 0)
+ {
+ U32 mat_idx = mat_id + 1;
+ LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
+
+ rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
+ vb->allocateBuffer(vertex_count[variant],
+ index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
+ vb->setBuffer();
+
+ for (auto& mesh : mMeshes)
+ {
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ primitive.upload(vb);
+ }
+ }
+ }
+
+ vb->unmapBuffer();
+
+ vb->unbind();
+ }
+ }
+ }
+ }
+
+ // sanity check that all primitives have a vertex buffer
+ for (auto& mesh : mMeshes)
+ {
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ llassert(primitive.mVertexBuffer.notNull());
+ }
+ }
+
+ // build render batches
+ for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
+ {
+ Node& node = mNodes[node_id];
+
+ if (node.mMesh != INVALID_INDEX)
+ {
+ auto& mesh = mMeshes[node.mMesh];
+
+ S32 mat_idx = mesh.mPrimitives[0].mMaterial + 1;
+
+ S32 double_sided = mat_idx == 0 ? 0 : mMaterials[mat_idx - 1].mDoubleSided;
+
+ for (S32 j = 0; j < mesh.mPrimitives.size(); ++j)
+ {
+ auto& primitive = mesh.mPrimitives[j];
+
+ S32 variant = primitive.mShaderVariant;
+
+ RenderData& rd = mRenderData[double_sided];
+ RenderBatch& rb = rd.mBatches[variant][mat_idx];
+
+ rb.mPrimitives.push_back({ j, node_id });
+ }
+ }
+ }
return true;
}
@@ -455,6 +673,7 @@ Asset::Asset(const Value& src)
bool Asset::load(std::string_view filename)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
mFilename = filename;
std::string ext = gDirUtilp->getExtension(mFilename);
@@ -903,14 +1122,14 @@ bool Image::save(Asset& asset, const std::string& folder)
return true;
}
-void Material::TextureInfo::serialize(object& dst) const
+void TextureInfo::serialize(object& dst) const
{
write(mIndex, "index", dst, INVALID_INDEX);
write(mTexCoord, "texCoord", dst, 0);
write_extensions(dst, &mTextureTransform, "KHR_texture_transform");
}
-S32 Material::TextureInfo::getTexCoord() const
+S32 TextureInfo::getTexCoord() const
{
if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX)
{
@@ -928,7 +1147,7 @@ bool Material::isMultiUV() const
mEmissiveTexture.getTexCoord() != 0;
}
-const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src)
+const TextureInfo& TextureInfo::operator=(const Value& src)
{
if (src.is_object())
{
@@ -940,23 +1159,23 @@ const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src)
return *this;
}
-bool Material::TextureInfo::operator==(const Material::TextureInfo& rhs) const
+bool TextureInfo::operator==(const TextureInfo& rhs) const
{
return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord;
}
-bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const
+bool TextureInfo::operator!=(const TextureInfo& rhs) const
{
return !(*this == rhs);
}
-void Material::OcclusionTextureInfo::serialize(object& dst) const
+void OcclusionTextureInfo::serialize(object& dst) const
{
TextureInfo::serialize(dst);
write(mStrength, "strength", dst, 1.f);
}
-const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src)
+const OcclusionTextureInfo& OcclusionTextureInfo::operator=(const Value& src)
{
TextureInfo::operator=(src);
@@ -968,13 +1187,13 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(
return *this;
}
-void Material::NormalTextureInfo::serialize(object& dst) const
+void NormalTextureInfo::serialize(object& dst) const
{
TextureInfo::serialize(dst);
write(mScale, "scale", dst, 1.f);
}
-const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const Value& src)
+const NormalTextureInfo& NormalTextureInfo::operator=(const Value& src)
{
TextureInfo::operator=(src);
if (src.is_object())
@@ -1035,18 +1254,12 @@ void Material::Unlit::serialize(object& dst) const
// no members and object has already been created, nothing to do
}
-void TextureTransform::getPacked(F32* packed) const
+void TextureTransform::getPacked(vec4* packed) const
{
- packed[0] = mScale.x;
- packed[1] = mScale.y;
- packed[2] = mRotation;
- packed[3] = mOffset.x;
- packed[4] = mOffset.y;
-
- packed[5] = packed[6] = packed[7] = 0.f;
+ packed[0] = vec4(mScale.x, mScale.y, mRotation, mOffset.x);
+ packed[1] = vec4(mOffset.y, 0.f, 0.f, 0.f);
}
-
const TextureTransform& TextureTransform::operator=(const Value& src)
{
mPresent = true;
diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h
index ea3f7d480a..27821659db 100644
--- a/indra/newview/gltf/asset.h
+++ b/indra/newview/gltf/asset.h
@@ -34,6 +34,7 @@
#include "boost/json.hpp"
#include "common.h"
#include "../llviewertexture.h"
+#include "llglslshader.h"
extern F32SecondsImplicit gFrameTimeSeconds;
@@ -65,14 +66,51 @@ namespace LL
vec2 mScale = vec2(1.f, 1.f);
S32 mTexCoord = INVALID_INDEX;
- // get the texture transform as a packed array of floats
- // dst MUST point to at least 8 floats
- void getPacked(F32* dst) const;
+ // get the texture transform as a packed array of vec4's
+ // dst MUST point to at least 2 vec4's
+ void getPacked(vec4* dst) const;
const TextureTransform& operator=(const Value& src);
void serialize(boost::json::object& dst) const;
};
+ class TextureInfo
+ {
+ public:
+ S32 mIndex = INVALID_INDEX;
+ S32 mTexCoord = 0;
+
+ TextureTransform mTextureTransform;
+
+ bool operator==(const TextureInfo& rhs) const;
+ bool operator!=(const TextureInfo& rhs) const;
+
+ // get the UV channel that should be used for sampling this texture
+ // returns mTextureTransform.mTexCoord if present and valid, otherwise mTexCoord
+ S32 getTexCoord() const;
+
+ const TextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ class NormalTextureInfo : public TextureInfo
+ {
+ public:
+ F32 mScale = 1.0f;
+
+ const NormalTextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ class OcclusionTextureInfo : public TextureInfo
+ {
+ public:
+ F32 mStrength = 1.0f;
+
+ const OcclusionTextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
class Material
{
public:
@@ -91,42 +129,6 @@ namespace LL
BLEND
};
- class TextureInfo
- {
- public:
- S32 mIndex = INVALID_INDEX;
- S32 mTexCoord = 0;
-
- TextureTransform mTextureTransform;
-
- bool operator==(const TextureInfo& rhs) const;
- bool operator!=(const TextureInfo& rhs) const;
-
- // get the UV channel that should be used for sampling this texture
- // returns mTextureTransform.mTexCoord if present and valid, otherwise mTexCoord
- S32 getTexCoord() const;
-
- const TextureInfo& operator=(const Value& src);
- void serialize(boost::json::object& dst) const;
- };
-
- class NormalTextureInfo : public TextureInfo
- {
- public:
- F32 mScale = 1.0f;
-
- const NormalTextureInfo& operator=(const Value& src);
- void serialize(boost::json::object& dst) const;
- };
-
- class OcclusionTextureInfo : public TextureInfo
- {
- public:
- F32 mStrength = 1.0f;
-
- const OcclusionTextureInfo& operator=(const Value& src);
- void serialize(boost::json::object& dst) const;
- };
class PbrMetallicRoughness
{
@@ -179,7 +181,6 @@ namespace LL
{
public:
mat4 mMatrix = glm::identity<mat4>(); //local transform
- mat4 mRenderMatrix; //transform for rendering
mat4 mAssetMatrix; //transform from local to asset space
mat4 mAssetMatrixInv; //transform from asset to local space
@@ -206,10 +207,6 @@ namespace LL
const Node& operator=(const Value& src);
void serialize(boost::json::object& dst) const;
- // Set mRenderMatrix to a transform that can be used for the current render pass
- // modelview -- parent's render matrix
- void updateRenderTransforms(Asset& asset, const mat4& modelview);
-
// update mAssetMatrix and mAssetMatrixInv
void updateTransforms(Asset& asset, const mat4& parentMatrix);
@@ -322,6 +319,31 @@ namespace LL
bool prep(Asset& asset);
};
+ // Render Batch -- vertex buffer and list of primitives to render using
+ // said vertex buffer
+ class RenderBatch
+ {
+ public:
+ struct PrimitiveData
+ {
+ S32 mPrimitiveIndex = INVALID_INDEX;
+ S32 mNodeIndex = INVALID_INDEX;
+ };
+
+ LLPointer<LLVertexBuffer> mVertexBuffer;
+ std::vector<PrimitiveData> mPrimitives;
+ };
+
+ class RenderData
+ {
+ public:
+ // list of render batches
+ // indexed by [material index + 1](0 is reserved for default material)
+ // there should be exactly one render batch per material per variant
+ std::vector<RenderBatch> mBatches[LLGLSLShader::NUM_GLTF_VARIANTS];
+ };
+
+
// C++ representation of a GLTF Asset
class Asset
{
@@ -359,6 +381,16 @@ namespace LL
// the last time update() was called according to gFrameTimeSeconds
F32 mLastUpdateTime = gFrameTimeSeconds;
+ // data used for rendering
+ // 0 - single sided
+ // 1 - double sided
+ RenderData mRenderData[2];
+
+ // UBO for storing node transforms
+ U32 mNodesUBO = 0;
+
+ // UBO for storing material data
+ U32 mMaterialsUBO = 0;
// prepare for first time use
bool prep();
@@ -373,8 +405,11 @@ namespace LL
// update asset-to-node and node-to-asset transforms
void updateTransforms();
- // update node render transforms
- void updateRenderTransforms(const mat4& modelview);
+ // upload matrices to UBO
+ void uploadTransforms();
+
+ // upload materils to UBO
+ void uploadMaterials();
// return the index of the node that the line segment intersects with, or -1 if no hit
// input and output values must be in this asset's local coordinate frame
diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h
index 40f9448aaf..89cedf4a47 100644
--- a/indra/newview/gltf/buffer_util.h
+++ b/indra/newview/gltf/buffer_util.h
@@ -181,6 +181,16 @@ namespace LL
}
template<>
+ inline void copyVec4<U8, U64>(U8* src, U64& dst)
+ {
+ U8* data = (U8*)&dst;
+ data[0] = src[0];
+ data[1] = src[1];
+ data[2] = src[2];
+ data[3] = src[3];
+ }
+
+ template<>
inline void copyVec4<U16, LLColor4U>(U16* src, LLColor4U& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h
index 4f660d7cfc..b9698d4017 100644
--- a/indra/newview/gltf/common.h
+++ b/indra/newview/gltf/common.h
@@ -64,6 +64,9 @@ namespace LL
class Asset;
class Material;
+ class TextureInfo;
+ class NormalTextureInfo;
+ class OcclusionTextureInfo;
class Mesh;
class Node;
class Scene;
@@ -78,6 +81,17 @@ namespace LL
class Accessor;
class BufferView;
class Buffer;
+
+ enum class TextureType : U8
+ {
+ BASE_COLOR = 0,
+ NORMAL,
+ METALLIC_ROUGHNESS,
+ OCCLUSION,
+ EMISSIVE
+ };
+
+ constexpr U32 TEXTURE_TYPE_COUNT = 5;
}
}
diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp
index 2280c7004e..e1579374d4 100644
--- a/indra/newview/gltf/primitive.cpp
+++ b/indra/newview/gltf/primitive.cpp
@@ -380,11 +380,22 @@ bool Primitive::prep(Asset& asset)
}
}
}
+ else
+ { //everything must be indexed at runtime
+ mIndexArray.resize(mPositions.size());
+ for (U32 i = 0; i < mPositions.size(); ++i)
+ {
+ mIndexArray[i] = i;
+ }
+ }
U32 mask = LLVertexBuffer::MAP_VERTEX;
+ mShaderVariant = 0;
+
if (!mWeights.empty())
{
+ mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED;
mask |= LLVertexBuffer::MAP_WEIGHT4;
mask |= LLVertexBuffer::MAP_JOINT;
}
@@ -406,9 +417,6 @@ bool Primitive::prep(Asset& asset)
mColors.resize(mPositions.size(), LLColor4U::white);
}
- mShaderVariant = 0;
-
- // TODO: support colorless vertex buffers
mask |= LLVertexBuffer::MAP_COLOR;
bool unlit = false;
@@ -506,69 +514,79 @@ bool Primitive::prep(Asset& asset)
mask |= LLVertexBuffer::MAP_TANGENT;
}
- if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
- { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
- gDebugProgram.bind();
+ mAttributeMask = mask;
+
+ if (mMaterial != INVALID_INDEX)
+ {
+ Material& material = asset.mMaterials[mMaterial];
+ if (material.mAlphaMode == Material::AlphaMode::BLEND)
+ {
+ mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND;
+ }
}
- mVertexBuffer = new LLVertexBuffer(mask);
+ createOctree();
+
+ return true;
+}
+
+void Primitive::upload(LLVertexBuffer* buffer)
+{
+ mVertexBuffer = buffer;
// we store these buffer sizes as S32 elsewhere
llassert(mPositions.size() <= size_t(S32_MAX));
llassert(mIndexArray.size() <= size_t(S32_MAX / 2));
- mVertexBuffer->allocateBuffer(U32(mPositions.size()), U32(mIndexArray.size() * 2)); // double the size of the index buffer for 32-bit indices
- mVertexBuffer->setBuffer();
- mVertexBuffer->setPositionData(mPositions.data());
- mVertexBuffer->setColorData(mColors.data());
+ llassert(mVertexBuffer != nullptr);
+
+ // assert that buffer can hold this primitive
+ llassert(mVertexBuffer->getNumVerts() >= mPositions.size() + mVertexOffset);
+ llassert(mVertexBuffer->getNumIndices() >= mIndexArray.size() + mIndexOffset);
+ llassert(mVertexBuffer->getTypeMask() == mAttributeMask);
+
+ U32 offset = mVertexOffset;
+ U32 count = getVertexCount();
+
+ mVertexBuffer->setPositionData(mPositions.data(), offset, count);
+ mVertexBuffer->setColorData(mColors.data(), offset, count);
if (!mNormals.empty())
{
- mVertexBuffer->setNormalData(mNormals.data());
+ mVertexBuffer->setNormalData(mNormals.data(), offset, count);
}
if (!mTangents.empty())
{
- mVertexBuffer->setTangentData(mTangents.data());
+ mVertexBuffer->setTangentData(mTangents.data(), offset, count);
}
if (!mWeights.empty())
{
- mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED;
- mVertexBuffer->setWeight4Data(mWeights.data());
- mVertexBuffer->setJointData(mJoints.data());
+ mVertexBuffer->setWeight4Data(mWeights.data(), offset, count);
+ mVertexBuffer->setJointData(mJoints.data(), offset, count);
}
// flip texcoord y, upload, then flip back (keep the off-spec data in vram only)
vertical_flip(mTexCoords0);
- mVertexBuffer->setTexCoord0Data(mTexCoords0.data());
+ mVertexBuffer->setTexCoord0Data(mTexCoords0.data(), offset, count);
vertical_flip(mTexCoords0);
if (!mTexCoords1.empty())
{
vertical_flip(mTexCoords1);
- mVertexBuffer->setTexCoord1Data(mTexCoords1.data());
+ mVertexBuffer->setTexCoord1Data(mTexCoords1.data(), offset, count);
vertical_flip(mTexCoords1);
}
-
if (!mIndexArray.empty())
{
- mVertexBuffer->setIndexData(mIndexArray.data());
- }
-
- createOctree();
-
- mVertexBuffer->unbind();
-
- if (mMaterial != INVALID_INDEX)
- {
- Material& material = asset.mMaterials[mMaterial];
- if (material.mAlphaMode == Material::AlphaMode::BLEND)
+ std::vector<U32> index_array;
+ index_array.resize(mIndexArray.size());
+ for (U32 i = 0; i < mIndexArray.size(); ++i)
{
- mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND;
+ index_array[i] = mIndexArray[i] + mVertexOffset;
}
+ mVertexBuffer->setIndexData(index_array.data(), mIndexOffset, getIndexCount());
}
-
- return true;
}
void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2)
@@ -616,7 +634,7 @@ void Primitive::createOctree()
if (mMode == Mode::TRIANGLES)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() / 3;
+ const U32 num_triangles = getIndexCount() / 3;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
@@ -640,7 +658,7 @@ void Primitive::createOctree()
}
else if (mMode == Mode::TRIANGLE_STRIP)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() - 2;
+ const U32 num_triangles = getIndexCount() - 2;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
@@ -664,7 +682,7 @@ void Primitive::createOctree()
}
else if (mMode == Mode::TRIANGLE_FAN)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() - 2;
+ const U32 num_triangles = getIndexCount() - 2;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h
index 7cc05cf831..304eb26432 100644
--- a/indra/newview/gltf/primitive.h
+++ b/indra/newview/gltf/primitive.h
@@ -54,10 +54,7 @@ namespace LL
~Primitive();
- // GPU copy of mesh data
- LLPointer<LLVertexBuffer> mVertexBuffer;
-
- // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code
+ // CPU copy of mesh data
std::vector<LLVector2> mTexCoords0;
std::vector<LLVector2> mTexCoords1;
std::vector<LLVector4a> mNormals;
@@ -80,6 +77,17 @@ namespace LL
// shader variant according to LLGLSLShader::GLTFVariant flags
U8 mShaderVariant = 0;
+ // vertex attribute mask
+ U32 mAttributeMask = 0;
+
+ // backpointer to vertex buffer (owned by Asset)
+ LLPointer<LLVertexBuffer> mVertexBuffer;
+ U32 mVertexOffset = 0;
+ U32 mIndexOffset = 0;
+
+ U32 getVertexCount() const { return (U32) mPositions.size(); }
+ U32 getIndexCount() const { return (U32) mIndexArray.size(); }
+
std::unordered_map<std::string, S32> mAttributes;
// create octree based on vertex buffer
@@ -100,6 +108,11 @@ namespace LL
const Primitive& operator=(const Value& src);
bool prep(Asset& asset);
+
+ // upload geometry to given vertex buffer
+ // asserts that buffer is bound
+ // asserts that buffer is valid for this primitive
+ void upload(LLVertexBuffer* buffer);
};
}
}
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
index e01aec0497..b161ec8492 100644
--- a/indra/newview/gltfscenemanager.cpp
+++ b/indra/newview/gltfscenemanager.cpp
@@ -42,6 +42,7 @@
#include "llviewertexturelist.h"
#include "llimagej2c.h"
#include "llfloaterperms.h"
+#include "llfloaterreg.h"
#include "llagentbenefits.h"
#include "llfilesystem.h"
#include "boost/json.hpp"
@@ -144,7 +145,7 @@ void GLTFSceneManager::uploadSelection()
}
else
{
- raw = image.mTexture->getCachedRawImage();
+ raw = image.mTexture->getRawImage();
}
if (raw.notNull())
@@ -314,6 +315,7 @@ void GLTFSceneManager::load(const std::string& filename)
{
mObjects.push_back(obj);
}
+ LLFloaterReg::showInstance("gltf_asset_editor");
}
}
else
@@ -339,6 +341,7 @@ void GLTFSceneManager::renderAlpha()
void GLTFSceneManager::addGLTFObject(LLViewerObject* obj, LLUUID gltf_id)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
llassert(obj->getVolume()->getParams().getSculptID() == gltf_id);
llassert(obj->getVolume()->getParams().getSculptType() == LL_SCULPT_TYPE_GLTF);
@@ -437,6 +440,8 @@ void GLTFSceneManager::onGLTFLoadComplete(const LLUUID& id, LLAssetType::EType a
void GLTFSceneManager::update()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+
for (U32 i = 0; i < mObjects.size(); ++i)
{
if (mObjects[i]->isDead() || mObjects[i]->mGLTFAsset == nullptr)
@@ -552,6 +557,7 @@ void GLTFSceneManager::render(bool opaque, bool rigged, bool unlit)
void GLTFSceneManager::render(U8 variant)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
// just render the whole scene by traversing the whole scenegraph
// Assumes camera transform is already set and appropriate shader is already bound.
// Eventually we'll want a smarter render pipe that has pre-sorted the scene graph
@@ -563,8 +569,6 @@ void GLTFSceneManager::render(U8 variant)
render((U8) (variant | LLGLSLShader::GLTFVariant::MULTI_UV));
}
- gGL.matrixMode(LLRender::MM_MODELVIEW);
-
bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
for (U32 i = 0; i < mObjects.size(); ++i)
@@ -581,19 +585,11 @@ void GLTFSceneManager::render(U8 variant)
LLMatrix4a mat = mObjects[i]->getGLTFAssetToAgentTransform();
- LLMatrix4a modelview;
- modelview.loadu(gGLModelView);
-
- matMul(mat, modelview, modelview);
-
- mat4 mdv = glm::make_mat4(modelview.getF32ptr());
- asset->updateRenderTransforms(mdv);
+ // provide a modelview matrix that goes from asset to camera space
+ // (matrix palettes are in asset space)
+ gGL.loadMatrix(gGLModelView);
+ gGL.multMatrix(mat.getF32ptr());
- if (rigged)
- { // provide a modelview matrix that goes from asset to camera space for rigged render passes
- // (matrix palettes are in asset space)
- gGL.loadMatrix(glm::value_ptr(mdv));
- }
render(*asset, variant);
gGL.popMatrix();
@@ -602,176 +598,185 @@ void GLTFSceneManager::render(U8 variant)
void GLTFSceneManager::render(Asset& asset, U8 variant)
{
- bool opaque = !(variant & LLGLSLShader::GLTFVariant::ALPHA_BLEND);
- bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
- if (opaque)
+ for (U32 ds = 0; ds < 2; ++ds)
{
- gGLTFPBRMetallicRoughnessProgram.bind(variant);
- }
- else
- { // alpha shaders need all the shadow map setup etc
- gPipeline.bindDeferredShader(gGLTFPBRMetallicRoughnessProgram.mGLTFVariants[variant]);
- }
+ RenderData& rd = asset.mRenderData[ds];
+ auto& batches = rd.mBatches[variant];
- for (auto& node : asset.mNodes)
- {
- if (node.mSkin != INVALID_INDEX)
+ if (batches.empty())
{
- if (rigged)
- {
- Skin& skin = asset.mSkins[node.mSkin];
- glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_JOINTS, skin.mUBO);
- }
+ return;
}
- if (node.mMesh != INVALID_INDEX)
+ LLGLDisable cull_face(ds == 1 ? GL_CULL_FACE : 0);
+
+ bool opaque = !(variant & LLGLSLShader::GLTFVariant::ALPHA_BLEND);
+ bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+
+ bool shader_bound = false;
+
+ for (U32 i = 0; i < batches.size(); ++i)
{
- Mesh& mesh = asset.mMeshes[node.mMesh];
- for (auto& primitive : mesh.mPrimitives)
+ if (batches[i].mPrimitives.empty() || batches[i].mVertexBuffer.isNull())
{
- if (primitive.mShaderVariant != variant)
+ continue;
+ }
+
+ if (!shader_bound)
+ { // don't bind the shader until we know we have somthing to render
+ if (opaque)
{
- continue;
+ gGLTFPBRMetallicRoughnessProgram.bind(variant);
+ }
+ else
+ { // alpha shaders need all the shadow map setup etc
+ gPipeline.bindDeferredShader(gGLTFPBRMetallicRoughnessProgram.mGLTFVariants[variant]);
}
if (!rigged)
{
- gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_NODES, asset.mNodesUBO);
}
- bool cull = true;
- if (primitive.mMaterial != INVALID_INDEX)
- {
- Material& material = asset.mMaterials[primitive.mMaterial];
- bind(asset, material);
- cull = !material.mDoubleSided;
- }
- else
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_MATERIALS, asset.mMaterialsUBO);
+
+ for (U32 i = 0; i < TEXTURE_TYPE_COUNT; ++i)
{
- LLFetchedGLTFMaterial::sDefault.bind();
+ mLastTexture[i] = -2;
}
- LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0);
+ gGL.syncMatrices();
+ shader_bound = true;
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - set vb");
+ batches[i].mVertexBuffer->setBuffer();
+ }
+
+ S32 mat_idx = i - 1;
+ if (mat_idx != INVALID_INDEX)
+ {
+ Material& material = asset.mMaterials[mat_idx];
+ bind(asset, material);
+ }
+ else
+ {
+ LLFetchedGLTFMaterial::sDefault.bind();
+ LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::GLTF_MATERIAL_ID, -1);
+ }
+
+ for (auto& pdata : batches[i].mPrimitives)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("GLTF draw call");
+ Node& node = asset.mNodes[pdata.mNodeIndex];
+ Mesh& mesh = asset.mMeshes[node.mMesh];
+ Primitive& primitive = mesh.mPrimitives[pdata.mPrimitiveIndex];
- primitive.mVertexBuffer->setBuffer();
- if (primitive.mVertexBuffer->getNumIndices() > 0)
+ if (rigged)
{
- primitive.mVertexBuffer->draw(primitive.mGLMode, primitive.mVertexBuffer->getNumIndices(), 0);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - bind skin");
+ llassert(node.mSkin != INVALID_INDEX);
+ Skin& skin = asset.mSkins[node.mSkin];
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_JOINTS, skin.mUBO);
}
else
{
- primitive.mVertexBuffer->drawArrays(primitive.mGLMode, 0, primitive.mVertexBuffer->getNumVerts());
+ LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::GLTF_NODE_ID, pdata.mNodeIndex);
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - push vb");
+
+ primitive.mVertexBuffer->drawRangeFast(primitive.mGLMode, primitive.mVertexOffset, primitive.mVertexOffset + primitive.getVertexCount() - 1, primitive.getIndexCount(), primitive.mIndexOffset);
}
}
}
}
}
-static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback)
+void GLTFSceneManager::bindTexture(Asset& asset, TextureType texture_type, TextureInfo& info, LLViewerTexture* fallback)
{
- if (info.mIndex != INVALID_INDEX)
+ U8 type_idx = (U8)texture_type;
+
+ if (info.mIndex == mLastTexture[type_idx])
+ { //already bound
+ return;
+ }
+
+ S32 uniform[] =
{
- Texture& texture = asset.mTextures[info.mIndex];
+ LLShaderMgr::DIFFUSE_MAP,
+ LLShaderMgr::NORMAL_MAP,
+ LLShaderMgr::METALLIC_ROUGHNESS_MAP,
+ LLShaderMgr::OCCLUSION_MAP,
+ LLShaderMgr::EMISSIVE_MAP
+ };
- LLViewerTexture* tex = asset.mImages[texture.mSource].mTexture;
- if (tex)
+ S32 channel = LLGLSLShader::sCurBoundShaderPtr->getTextureChannel(uniform[(U8)type_idx]);
+
+ if (channel > -1)
+ {
+ glActiveTexture(GL_TEXTURE0 + channel);
+
+ if (info.mIndex != INVALID_INDEX)
{
- tex->addTextureStats(2048.f * 2048.f);
- S32 channel = LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex);
+ Texture& texture = asset.mTextures[info.mIndex];
+
+ LLViewerTexture* tex = asset.mImages[texture.mSource].mTexture;
+ if (tex)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gl bind texture");
+ glBindTexture(GL_TEXTURE_2D, tex->getTexName());
- if (channel != -1 && texture.mSampler != -1)
- { // set sampler state
- Sampler& sampler = asset.mSamplers[texture.mSampler];
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sampler.mWrapS);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, sampler.mWrapT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sampler.mMagFilter);
+ if (channel != -1 && texture.mSampler != -1)
+ { // set sampler state
+ Sampler& sampler = asset.mSamplers[texture.mSampler];
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sampler.mWrapS);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, sampler.mWrapT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sampler.mMagFilter);
- // NOTE: do not set min filter. Always respect client preference for min filter
+ // NOTE: do not set min filter. Always respect client preference for min filter
+ }
+ else
+ {
+ // set default sampler state
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
}
else
{
- // set default sampler state
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindTexture(GL_TEXTURE_2D, fallback->getTexName());
}
}
else
{
- LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
+ glBindTexture(GL_TEXTURE_2D, fallback->getTexName());
}
}
- else
- {
- LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
- }
}
void GLTFSceneManager::bind(Asset& asset, Material& material)
{
- // bind for rendering (derived from LLFetchedGLTFMaterial::bind)
- // glTF 2.0 Specification 3.9.4. Alpha Coverage
- // mAlphaCutoff is only valid for LLGLTFMaterial::ALPHA_MODE_MASK
- F32 min_alpha = -1.0;
-
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
- if (!LLPipeline::sShadowRender || (material.mAlphaMode == Material::AlphaMode::BLEND))
- {
- if (material.mAlphaMode == Material::AlphaMode::MASK)
- {
- // dividing the alpha cutoff by transparency here allows the shader to compare against
- // the alpha value of the texture without needing the transparency value
- if (material.mPbrMetallicRoughness.mBaseColorFactor.a > 0.f)
- {
- min_alpha = material.mAlphaCutoff / material.mPbrMetallicRoughness.mBaseColorFactor.a;
- }
- else
- {
- min_alpha = 1024.f;
- }
- }
- shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha);
- }
-
- bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep);
-
- F32 tf[8];
- material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::BASE_COLOR_TEXCOORD, material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord());
+ bindTexture(asset, TextureType::BASE_COLOR, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep);
if (!LLPipeline::sShadowRender)
{
- bindTexture(asset, LLShaderMgr::NORMAL_MAP, material.mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep);
- bindTexture(asset, LLShaderMgr::METALLIC_ROUGHNESS_MAP, material.mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep);
- bindTexture(asset, LLShaderMgr::OCCLUSION_MAP, material.mOcclusionTexture, LLViewerFetchedTexture::sWhiteImagep);
- bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, material.mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep);
-
- // NOTE: base color factor is baked into vertex stream
-
- shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, material.mPbrMetallicRoughness.mRoughnessFactor);
- shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, material.mPbrMetallicRoughness.mMetallicFactor);
- shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(material.mEmissiveFactor));
-
- material.mNormalTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::NORMAL_TEXCOORD, material.mNormalTexture.getTexCoord());
-
- material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::METALLIC_ROUGHNESS_TEXCOORD, material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord());
-
- material.mOcclusionTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_OCCLUSION_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::OCCLUSION_TEXCOORD, material.mOcclusionTexture.getTexCoord());
-
- material.mEmissiveTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::EMISSIVE_TEXCOORD, material.mEmissiveTexture.getTexCoord());
+ bindTexture(asset, TextureType::NORMAL, material.mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep);
+ bindTexture(asset, TextureType::METALLIC_ROUGHNESS, material.mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, TextureType::OCCLUSION, material.mOcclusionTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, TextureType::EMISSIVE, material.mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep);
}
+
+ shader->uniform1i(LLShaderMgr::GLTF_MATERIAL_ID, &material - &asset.mMaterials[0]);
}
LLMatrix4a inverse(const LLMatrix4a& mat)
@@ -931,10 +936,11 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
// assumes modelview matrix is already set
gGL.pushMatrix();
-
// get raycast in asset space
LLMatrix4a agent_to_asset = obj->getAgentToGLTFAssetTransform();
+ gGL.multMatrix(agent_to_asset.getF32ptr());
+
vec4 start;
vec4 end;
@@ -952,7 +958,8 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
if (node.mMesh != INVALID_INDEX)
{
- gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+ gGL.pushMatrix();
+ gGL.multMatrix((F32*)glm::value_ptr(node.mAssetMatrix));
// draw bounding box of mesh primitives
if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES))
@@ -995,6 +1002,7 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
+ gGL.popMatrix();
}
}
@@ -1013,35 +1021,15 @@ void GLTFSceneManager::renderDebug()
gDebugProgram.bind();
+ gGL.pushMatrix();
+ gGL.loadMatrix(gGLModelView);
+
LLGLDisable cullface(GL_CULL_FACE);
LLGLEnable blend(GL_BLEND);
gGL.setSceneBlendType(LLRender::BT_ALPHA);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gPipeline.disableLights();
- // force update all mRenderMatrix, not just nodes with meshes
- for (auto& obj : mObjects)
- {
- if (obj->isDead() || obj->mGLTFAsset == nullptr)
- {
- continue;
- }
-
- mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
-
- mat4 modelview = glm::make_mat4(gGLModelView);
-
-
- modelview = modelview * mat;
-
- Asset* asset = obj->mGLTFAsset.get();
-
- for (auto& node : asset->mNodes)
- {
- node.mRenderMatrix = modelview * node.mAssetMatrix;
- }
- }
-
for (auto& obj : mObjects)
{
if (obj->isDead() || obj->mGLTFAsset == nullptr)
@@ -1062,9 +1050,6 @@ void GLTFSceneManager::renderDebug()
LLGLDepthTest depth(GL_TRUE, i == 0 ? GL_FALSE : GL_TRUE, i == 0 ? GL_GREATER : GL_LEQUAL);
LLGLState blend(GL_BLEND, i == 0 ? GL_TRUE : GL_FALSE);
-
- gGL.pushMatrix();
-
for (auto& obj : mObjects)
{
if (obj->isDead() || obj->mGLTFAsset == nullptr)
@@ -1072,20 +1057,16 @@ void GLTFSceneManager::renderDebug()
continue;
}
- mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
+ gGL.pushMatrix();
- mat4 modelview = glm::make_mat4(gGLModelView);
-
- modelview = modelview * mat;
+ gGL.multMatrix(obj->getGLTFAssetToAgentTransform().getF32ptr());
Asset* asset = obj->mGLTFAsset.get();
for (auto& node : asset->mNodes)
{
- // force update all mRenderMatrix, not just nodes with meshes
- node.mRenderMatrix = modelview * node.mAssetMatrix;
-
- gGL.loadMatrix(glm::value_ptr(node.mRenderMatrix));
+ gGL.pushMatrix();
+ gGL.multMatrix(glm::value_ptr(node.mAssetMatrix));
// render x-axis red, y-axis green, z-axis blue
gGL.color4f(1.f, 0.f, 0.f, 0.5f);
gGL.begin(LLRender::LINES);
@@ -1121,12 +1102,12 @@ void GLTFSceneManager::renderDebug()
}
gGL.end();
gGL.flush();
+ gGL.popMatrix();
}
- }
- gGL.popMatrix();
+ gGL.popMatrix();
+ }
}
-
}
@@ -1140,28 +1121,36 @@ void GLTFSceneManager::renderDebug()
if (drawable)
{
- gGL.pushMatrix();
- Asset* asset = drawable->getVObj()->mGLTFAsset.get();
- Node* node = &asset->mNodes[node_hit];
- Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit];
- gGL.flush();
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- gGL.color3f(1, 0, 1);
- drawBoxOutline(intersection, LLVector4a(0.1f, 0.1f, 0.1f, 0.f));
+ LLViewerObject* obj = drawable->getVObj();
+ if (obj)
+ {
+ gGL.pushMatrix();
+ gGL.multMatrix(obj->getGLTFAssetToAgentTransform().getF32ptr());
+ Asset* asset = obj->mGLTFAsset.get();
+ Node* node = &asset->mNodes[node_hit];
+ Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit];
- gGL.loadMatrix(glm::value_ptr(node->mRenderMatrix));
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ gGL.color3f(1, 0, 1);
+ drawBoxOutline(intersection, LLVector4a(0.1f, 0.1f, 0.1f, 0.f));
+ gGL.multMatrix(glm::value_ptr(node->mAssetMatrix));
- auto* listener = (LLVolumeOctreeListener*) primitive->mOctree->getListener(0);
- drawBoxOutline(listener->mBounds[0], listener->mBounds[1]);
+ auto* listener = (LLVolumeOctreeListener*)primitive->mOctree->getListener(0);
+ drawBoxOutline(listener->mBounds[0], listener->mBounds[1]);
- gGL.flush();
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- gGL.popMatrix();
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ gGL.popMatrix();
+ }
}
}
+
+ gGL.popMatrix();
gDebugProgram.unbind();
+
}
diff --git a/indra/newview/gltfscenemanager.h b/indra/newview/gltfscenemanager.h
index 7da413e8b2..853bca59d0 100644
--- a/indra/newview/gltfscenemanager.h
+++ b/indra/newview/gltfscenemanager.h
@@ -59,7 +59,7 @@ namespace LL
// bind the given material for rendering
void bind(LL::GLTF::Asset& asset, LL::GLTF::Material& material);
-
+ void bindTexture(LL::GLTF::Asset& asset, LL::GLTF::TextureType texture_type, LL::GLTF::TextureInfo& info, LLViewerTexture* fallback);
void renderOpaque();
void renderAlpha();
@@ -94,6 +94,11 @@ namespace LL
U32 mPendingGLTFUploads = 0;
U32 mJointUBO = 0;
+
+
+ // render loop state
+ S32 mLastTexture[GLTF::TEXTURE_TYPE_COUNT] = { -2, -2, -2, -2, -2 };
+
};
}
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index 759b73e486..2f5b0f04e3 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -310,7 +310,7 @@ bool LLAgent::isActionAllowed(const LLSD& sdname)
}
else
{
- allow_agent_voice = channel->isActive() && channel->callStarted();
+ allow_agent_voice = channel->isActive();
}
}
@@ -4080,10 +4080,6 @@ bool LLAgent::teleportCore(bool is_local)
}
make_ui_sound("UISndTeleportOut");
- // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel.
- // This was breaking the case of teleporting within a single sim. Backing it out for now.
-// LLVoiceClient::getInstance()->leaveChannel();
-
return true;
}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 3b3345e8b2..85ede793e2 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3360,14 +3360,14 @@ LLSD LLAppViewer::getViewerInfo() const
LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion();
const std::string build_version = version.mBuildVersion;
std::ostringstream version_string;
- if (std::equal(build_version.begin(), build_version.begin() + version.serverVersion.size(),
+ if (std::equal(version.mBuildVersion.begin(), version.mBuildVersion.begin() + version.serverVersion.size(),
version.serverVersion.begin()))
{ // Normal case: Show type and build version.
- version_string << version.serverType << " " << build_version << std::endl;
+ version_string << version.voiceServerType << " " << version.mBuildVersion << std::endl;
}
else
{ // Mismatch: Show both versions.
- version_string << version.serverVersion << "/" << build_version << std::endl;
+ version_string << version.voiceServerType << " " << version.serverVersion << "/" << version.mBuildVersion << std::endl;
}
info["VOICE_VERSION"] = version_string.str();
}
@@ -5149,7 +5149,7 @@ void LLAppViewer::sendLogoutRequest()
if(LLVoiceClient::instanceExists())
{
- LLVoiceClient::getInstance()->leaveChannel();
+ LLVoiceClient::getInstance()->setVoiceEnabled(false);
}
}
}
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index a9fcd82ba6..08fbbaa390 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -43,6 +43,7 @@
#ifndef LL_LLAPPVIEWER_H
#define LL_LLAPPVIEWER_H
+#include "llapp.h"
#include "llallocator.h"
#include "llapr.h"
#include "llcontrol.h"
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index 5ae1e2f43c..3cdae041fc 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -77,6 +77,7 @@
#include "BugSplat.h"
#include "boost/json.hpp" // Boost.Json
#include "llagent.h" // for agent location
+#include "llstartup.h"
#include "llviewerregion.h"
#include "llvoavatarself.h" // for agent name
@@ -138,8 +139,7 @@ namespace
// We don't have an email address for any user. Hijack this
// metadata field for the platform identifier.
sBugSplatSender->setDefaultUserEmail(
- WCSTR(STRINGIZE(LLOSInfo::instance().getOSStringSimple() << " ("
- << ADDRESS_SIZE << "-bit)")));
+ WCSTR(LLOSInfo::instance().getOSStringSimple()));
if (gAgentAvatarp)
{
@@ -152,6 +152,8 @@ namespace
// LL_ERRS message, when there is one
sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage()));
+ // App state
+ sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString()));
if (gAgent.getRegion())
{
diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp
index f3cdd366b3..038030a1f6 100644
--- a/indra/newview/llavataractions.cpp
+++ b/indra/newview/llavataractions.cpp
@@ -241,7 +241,7 @@ static void on_avatar_name_cache_start_call(const LLUUID& agent_id,
const LLAvatarName& av_name)
{
std::string name = av_name.getDisplayName();
- LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, true);
+ LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, LLSD());
if (session_id != LLUUID::null)
{
gIMMgr->startCall(session_id);
@@ -277,8 +277,7 @@ void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floate
// create the new ad hoc voice session
const std::string title = LLTrans::getString("conference-title");
- LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START,
- ids[0], id_array, true, floater_id);
+ LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id);
if (session_id == LLUUID::null)
{
return;
@@ -322,7 +321,7 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float
id_array.push_back(*it);
}
const std::string title = LLTrans::getString("conference-title");
- LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, false, floater_id);
+ LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, LLSD(), floater_id);
if (session_id == LLUUID::null)
{
diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp
index a5b2ee29c7..eac1ee2a3c 100644
--- a/indra/newview/llconversationview.cpp
+++ b/indra/newview/llconversationview.cpp
@@ -57,7 +57,7 @@ public:
: conversation(conv)
{}
- virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal)
+ virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
{
conversation->showVoiceIndicator(conversation
&& status != STATUS_JOINING
diff --git a/indra/newview/lldebugview.cpp b/indra/newview/lldebugview.cpp
index b88d11886a..0596a8fe7d 100644
--- a/indra/newview/lldebugview.cpp
+++ b/indra/newview/lldebugview.cpp
@@ -105,10 +105,7 @@ void LLDebugView::init()
addChild(gSceneMonitorView);
gSceneMonitorView->setRect(rect);
- r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f),
- (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f));
-
- r.set(150, rect.getHeight() - 50, 820, 100);
+ r.set(150, rect.getHeight() - 60, 820, 110);
LLTextureView::Params tvp;
tvp.name("gTextureView");
tvp.rect(r);
@@ -116,7 +113,6 @@ void LLDebugView::init()
tvp.visible(false);
gTextureView = LLUICtrlFactory::create<LLTextureView>(tvp);
addChild(gTextureView);
- //gTextureView->reshape(r.getWidth(), r.getHeight(), true);
}
void LLDebugView::draw()
diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 9ba4f7f300..739975eab4 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -756,9 +756,12 @@ void LLRenderPass::pushGLTFBatch(LLDrawInfo& params)
{
auto& mat = params.mGLTFMaterial;
- mat->bind(params.mTexture);
+ if (mat.notNull())
+ {
+ mat->bind(params.mTexture);
+ }
- LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0);
+ LLGLDisable cull_face(mat.notNull() && mat->mDoubleSided ? GL_CULL_FACE : 0);
setup_texture_matrix(params);
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 93fea899f3..34da5b29d4 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -257,7 +257,7 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
mAlphaDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // }
gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor);
- if (rigged)
+ if (rigged && mType == LLDrawPool::POOL_ALPHA_POST_WATER)
{ // draw GLTF scene to depth buffer before rigged alpha
LL::GLTFSceneManager::instance().render(false, false);
LL::GLTFSceneManager::instance().render(false, true);
diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp
index 055f99d764..7289e95b6e 100644
--- a/indra/newview/lldrawpoolbump.cpp
+++ b/indra/newview/lldrawpoolbump.cpp
@@ -79,11 +79,6 @@ static S32 diffuse_channel = -1;
static S32 bump_channel = -1;
static bool shiny = false;
-// Enabled after changing LLViewerTexture::mNeedsCreateTexture to an
-// LLAtomicBool; this should work just fine, now. HB
-#define LL_BUMPLIST_MULTITHREADED 1
-
-
// static
void LLStandardBumpmap::shutdown()
{
@@ -764,24 +759,21 @@ LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedText
LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
llassert( (bump_code == BE_BRIGHTNESS) || (bump_code == BE_DARKNESS) );
- LLViewerTexture* bump = NULL;
+ LLViewerTexture* bump = nullptr;
- bump_image_map_t* entries_list = NULL;
- void (*callback_func)( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) = NULL;
+ bump_image_map_t* entries_list = nullptr;
switch( bump_code )
{
case BE_BRIGHTNESS:
entries_list = &mBrightnessEntries;
- callback_func = LLBumpImageList::onSourceBrightnessLoaded;
break;
case BE_DARKNESS:
entries_list = &mDarknessEntries;
- callback_func = LLBumpImageList::onSourceDarknessLoaded;
break;
default:
llassert(0);
- return NULL;
+ return nullptr;
}
bump_image_map_t::iterator iter = entries_list->find(src_image->getID());
@@ -789,51 +781,18 @@ LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedText
{
bump = iter->second;
}
- else
- {
- (*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( true );
- bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image
- }
-
- if (!src_image->hasCallbacks())
- { //if image has no callbacks but resolutions don't match, trigger raw image loaded callback again
- if (src_image->getWidth() != bump->getWidth() ||
- src_image->getHeight() != bump->getHeight())// ||
- //(LLPipeline::sRenderDeferred && bump->getComponents() != 4))
- {
- src_image->setBoostLevel(LLGLTexture::BOOST_BUMP) ;
- src_image->setLoadedCallback( callback_func, 0, true, false, new LLUUID(src_image->getID()), NULL );
- src_image->forceToSaveRawImage(0) ;
- }
- }
-
- return bump;
-}
-
-// static
-void LLBumpImageList::onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata )
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
- LLUUID* source_asset_id = (LLUUID*)userdata;
- LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_BRIGHTNESS );
- if( final )
+ if (bump == nullptr ||
+ src_image->getWidth() != bump->getWidth() ||
+ src_image->getHeight() != bump->getHeight())
{
- delete source_asset_id;
+ onSourceUpdated(src_image, (EBumpEffect) bump_code);
}
-}
-// static
-void LLBumpImageList::onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata )
-{
- LLUUID* source_asset_id = (LLUUID*)userdata;
- LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_DARKNESS );
- if( final )
- {
- delete source_asset_id;
- }
+ return (*entries_list)[src_image->getID()];
}
+
void LLBumpImageList::onSourceStandardLoaded( bool success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata)
{
if (success && LLPipeline::sRenderDeferred)
@@ -909,289 +868,108 @@ void LLBumpImageList::generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nr
}
// static
-void LLBumpImageList::onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump_code )
+void LLBumpImageList::onSourceUpdated(LLViewerTexture* src, EBumpEffect bump_code)
{
- LL_PROFILE_ZONE_SCOPED;
-
- if( success )
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
-
- LLImageDataSharedLock lock(src);
-
- bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries );
- bump_image_map_t::iterator iter = entries_list.find(source_asset_id);
-
- {
- if (iter == entries_list.end() ||
- iter->second.isNull() ||
- iter->second->getWidth() != src->getWidth() ||
- iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
- { //make sure an entry exists for this image
- entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture(true);
- iter = entries_list.find(src_vi->getID());
- }
- }
-
- if (iter->second->getWidth() != src->getWidth() ||
- iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
- {
- LLPointer<LLImageRaw> dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1);
- U8* dst_data = dst_image->getData();
- S32 dst_data_size = dst_image->getDataSize();
-
- const U8* src_data = src->getData();
- S32 src_data_size = src->getDataSize();
-
- S32 src_components = src->getComponents();
-
- // Convert to luminance and then scale and bias that to get ready for
- // embossed bump mapping. (0-255 maps to 127-255)
-
- // Convert to fixed point so we don't have to worry about precision/clamping.
- const S32 FIXED_PT = 8;
- const S32 R_WEIGHT = S32(0.2995f * (1<<FIXED_PT));
- const S32 G_WEIGHT = S32(0.5875f * (1<<FIXED_PT));
- const S32 B_WEIGHT = S32(0.1145f * (1<<FIXED_PT));
-
- S32 minimum = 255;
- S32 maximum = 0;
-
- switch( src_components )
- {
- case 1:
- case 2:
- {
- if( src_data_size == dst_data_size * src_components )
- {
- for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
- {
- dst_data[i] = src_data[j];
- if( dst_data[i] < minimum )
- {
- minimum = dst_data[i];
- }
- if( dst_data[i] > maximum )
- {
- maximum = dst_data[i];
- }
- }
- }
- else
- {
- llassert(0);
- dst_image->clear();
- }
- }
- break;
- case 3:
- case 4:
- {
- if( src_data_size == dst_data_size * src_components )
- {
- for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components )
- {
- // RGB to luminance
- dst_data[i] = (R_WEIGHT * src_data[j] + G_WEIGHT * src_data[j+1] + B_WEIGHT * src_data[j+2]) >> FIXED_PT;
- //llassert( dst_data[i] <= 255 );true because it's 8bit
- if( dst_data[i] < minimum )
- {
- minimum = dst_data[i];
- }
- if( dst_data[i] > maximum )
- {
- maximum = dst_data[i];
- }
- }
- }
- else
- {
- llassert(0);
- dst_image->clear();
- }
- }
- break;
- default:
- llassert(0);
- dst_image->clear();
- break;
- }
-
- if( maximum > minimum )
- {
- U8 bias_and_scale_lut[256];
- F32 twice_one_over_range = 2.f / (maximum - minimum);
- S32 i;
-
- const F32 ARTIFICIAL_SCALE = 2.f; // Advantage: exaggerates the effect in midrange. Disadvantage: clamps at the extremes.
- if (BE_DARKNESS == bump_code)
- {
- for( i = minimum; i <= maximum; i++ )
- {
- F32 minus_one_to_one = F32(maximum - i) * twice_one_over_range - 1.f;
- bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
- }
- }
- else
- {
- for( i = minimum; i <= maximum; i++ )
- {
- F32 minus_one_to_one = F32(i - minimum) * twice_one_over_range - 1.f;
- bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128));
- }
- }
-
- for( i = 0; i < dst_data_size; i++ )
- {
- dst_data[i] = bias_and_scale_lut[dst_data[i]];
- }
- }
-
- //---------------------------------------------------
- // immediately assign bump to a smart pointer in case some local smart pointer
- // accidentally releases it.
- LLPointer<LLViewerTexture> bump = iter->second;
-
- if (!LLPipeline::sRenderDeferred)
- {
- bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA);
-
-#if LL_BUMPLIST_MULTITHREADED
- auto tex_queue = LLImageGLThread::sEnabledTextures ? sTexUpdateQueue.lock() : nullptr;
-
- if (tex_queue)
- { //dispatch creation to background thread
- LLImageRaw* dst_ptr = dst_image;
- LLViewerTexture* bump_ptr = bump;
- dst_ptr->ref();
- bump_ptr->ref();
- tex_queue->post(
- [=]()
- {
- LL_PROFILE_ZONE_NAMED("bil - create texture");
- bump_ptr->createGLTexture(0, dst_ptr);
- bump_ptr->unref();
- dst_ptr->unref();
- });
-
- }
- else
-#endif
- {
- bump->createGLTexture(0, dst_image);
- }
- }
- else
- { //convert to normal map
- LL_PROFILE_ZONE_NAMED("bil - create normal map");
- LLImageGL* img = bump->getGLTexture();
- LLImageRaw* dst_ptr = dst_image.get();
- LLGLTexture* bump_ptr = bump.get();
-
- dst_ptr->ref();
- img->ref();
- bump_ptr->ref();
- auto create_func = [=]()
- {
- img->setUseMipMaps(true);
- // upload dst_image to GPU (greyscale in red channel)
- img->setExplicitFormat(GL_RED, GL_RED);
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
- bump_ptr->createGLTexture(0, dst_ptr);
- dst_ptr->unref();
- };
+ const LLUUID& src_id = src->getID();
- auto generate_func = [=]()
- {
- // Allocate an empty RGBA texture at "tex_name" the same size as bump
- // Note: bump will still point at GPU copy of dst_image
- bump_ptr->setExplicitFormat(GL_RGBA, GL_RGBA);
- LLGLuint tex_name;
- img->createGLTexture(0, nullptr, false, 0, true, &tex_name);
+ bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries);
+ bump_image_map_t::iterator iter = entries_list.find(src_id);
- // point render target at empty buffer
- sRenderTarget.setColorAttachment(img, tex_name);
+ if (iter == entries_list.end())
+ { //make sure an entry exists for this image
+ entries_list[src_id] = LLViewerTextureManager::getLocalTexture(true);
+ iter = entries_list.find(src_id);
+ }
- // generate normal map in empty texture
- {
- sRenderTarget.bindTarget();
+ //---------------------------------------------------
+ // immediately assign bump to a smart pointer in case some local smart pointer
+ // accidentally releases it.
+ LLPointer<LLViewerTexture> bump = iter->second;
- LLGLDepthTest depth(GL_FALSE);
- LLGLDisable cull(GL_CULL_FACE);
- LLGLDisable blend(GL_BLEND);
- gGL.setColorMask(true, true);
+ if (bump->getWidth() != src->getWidth() ||
+ bump->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution
+ {
+ //convert to normal map
+ LL_PROFILE_ZONE_NAMED("bil - create normal map");
- gNormalMapGenProgram.bind();
+ bump->setExplicitFormat(GL_RGBA, GL_RGBA);
- static LLStaticHashedString sNormScale("norm_scale");
- static LLStaticHashedString sStepX("stepX");
- static LLStaticHashedString sStepY("stepY");
+ LLImageGL* src_img = src->getGLTexture();
+ LLImageGL* dst_img = bump->getGLTexture();
+ dst_img->setSize(src->getWidth(), src->getHeight(), 4, 0);
+ dst_img->setUseMipMaps(true);
+ dst_img->setDiscardLevel(0);
+ dst_img->createGLTexture();
- gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale"));
- gNormalMapGenProgram.uniform1f(sStepX, 1.f / bump_ptr->getWidth());
- gNormalMapGenProgram.uniform1f(sStepY, 1.f / bump_ptr->getHeight());
+ gGL.getTexUnit(0)->bind(bump);
- gGL.getTexUnit(0)->bind(bump_ptr);
+ LLImageGL::setManualImage(GL_TEXTURE_2D, 0, dst_img->getPrimaryFormat(), dst_img->getWidth(), dst_img->getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
- gGL.begin(LLRender::TRIANGLE_STRIP);
- gGL.texCoord2f(0, 0);
- gGL.vertex2f(0, 0);
+ LLGLuint tex_name = dst_img->getTexName();
+ // point render target at empty buffer
+ sRenderTarget.setColorAttachment(bump->getGLTexture(), tex_name);
- gGL.texCoord2f(0, 1);
- gGL.vertex2f(0, 1);
+ // generate normal map in empty texture
+ {
+ sRenderTarget.bindTarget();
- gGL.texCoord2f(1, 0);
- gGL.vertex2f(1, 0);
+ LLGLDepthTest depth(GL_FALSE);
+ LLGLDisable cull(GL_CULL_FACE);
+ LLGLDisable blend(GL_BLEND);
+ gGL.setColorMask(true, true);
- gGL.texCoord2f(1, 1);
- gGL.vertex2f(1, 1);
+ LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+ gNormalMapGenProgram.bind();
- gGL.end();
+ static LLStaticHashedString sNormScale("norm_scale");
+ static LLStaticHashedString sStepX("stepX");
+ static LLStaticHashedString sStepY("stepY");
+ static LLStaticHashedString sBumpCode("bump_code");
- gGL.flush();
+ gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale"));
+ gNormalMapGenProgram.uniform1f(sStepX, 1.f / bump->getWidth());
+ gNormalMapGenProgram.uniform1f(sStepY, 1.f / bump->getHeight());
+ gNormalMapGenProgram.uniform1i(sBumpCode, bump_code);
- gNormalMapGenProgram.unbind();
+ gGL.getTexUnit(0)->bind(src);
- sRenderTarget.flush();
- sRenderTarget.releaseColorAttachment();
- }
+ gGL.begin(LLRender::TRIANGLE_STRIP);
+ gGL.texCoord2f(0, 0);
+ gGL.vertex2f(0, 0);
- // point bump at normal map and free gpu copy of dst_image
- img->syncTexName(tex_name);
+ gGL.texCoord2f(0, 1);
+ gGL.vertex2f(0, 1);
- // generate mipmap
- gGL.getTexUnit(0)->bind(img);
- glGenerateMipmap(GL_TEXTURE_2D);
- gGL.getTexUnit(0)->disable();
+ gGL.texCoord2f(1, 0);
+ gGL.vertex2f(1, 0);
- bump_ptr->unref();
- img->unref();
- };
+ gGL.texCoord2f(1, 1);
+ gGL.vertex2f(1, 1);
-#if LL_BUMPLIST_MULTITHREADED
- auto main_queue = LLImageGLThread::sEnabledTextures ? sMainQueue.lock() : nullptr;
+ gGL.end();
- if (main_queue)
- { //dispatch texture upload to background thread, issue GPU commands to generate normal map on main thread
- main_queue->postTo(
- sTexUpdateQueue,
- create_func,
- generate_func);
- }
- else
-#endif
- { // immediate upload texture and generate normal map
- create_func();
- generate_func();
- }
+ gGL.flush();
+ sRenderTarget.flush();
+ sRenderTarget.releaseColorAttachment();
+ if (shader)
+ {
+ shader->bind();
}
-
- iter->second = bump; // derefs (and deletes) old image
- //---------------------------------------------------
}
+
+ // generate mipmap
+ gGL.getTexUnit(0)->bind(bump);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
}
+
+ iter->second = bump; // derefs (and deletes) old image
+ //---------------------------------------------------
+
}
void LLDrawPoolBump::pushBumpBatches(U32 type)
diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h
index 65cb9af015..15976884ca 100644
--- a/indra/newview/lldrawpoolbump.h
+++ b/indra/newview/lldrawpoolbump.h
@@ -140,14 +140,13 @@ public:
LLViewerTexture* getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code);
void addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size);
- static void onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata );
- static void onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata );
static void onSourceStandardLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata );
static void generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image);
private:
- static void onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump );
+ // should be called whenever resolution of src_vi changes compared to the current entry
+ static void onSourceUpdated( LLViewerTexture *src_vi, EBumpEffect bump );
private:
typedef std::unordered_map<LLUUID, LLPointer<LLViewerTexture> > bump_image_map_t;
diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp
index 5eb10fe335..4ea23412e6 100644
--- a/indra/newview/lldrawpoolpbropaque.cpp
+++ b/indra/newview/lldrawpoolpbropaque.cpp
@@ -54,7 +54,10 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass)
{
llassert(!LLPipeline::sRenderingHUDs);
- LL::GLTFSceneManager::instance().renderOpaque();
+ if (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK)
+ {
+ LL::GLTFSceneManager::instance().renderOpaque();
+ }
gDeferredPBROpaqueProgram.bind();
pushGLTFBatches(mRenderType);
diff --git a/indra/newview/llfloateravatar.cpp b/indra/newview/llfloateravatar.cpp
index 6a38d7549c..404316275d 100644
--- a/indra/newview/llfloateravatar.cpp
+++ b/indra/newview/llfloateravatar.cpp
@@ -25,11 +25,6 @@
* $/LicenseInfo$
*/
-/**
- * Floater that appears when buying an object, giving a preview
- * of its contents and their permissions.
- */
-
#include "llviewerprecompiledheaders.h"
#include "llfloateravatar.h"
diff --git a/indra/newview/llfloaterchangeitemthumbnail.cpp b/indra/newview/llfloaterchangeitemthumbnail.cpp
index 3e2e7cb7a3..fb31361fd9 100644
--- a/indra/newview/llfloaterchangeitemthumbnail.cpp
+++ b/indra/newview/llfloaterchangeitemthumbnail.cpp
@@ -951,8 +951,8 @@ void LLFloaterChangeItemThumbnail::onTexturePickerCommit()
|| texturep->getFullWidth() == 0)
{
if (texturep->isFullyLoaded()
- && (texturep->getCachedRawImageLevel() == 0 || texturep->getRawImageLevel() == 0)
- && (texturep->isCachedRawImageReady() || texturep->isRawImageValid()))
+ && (texturep->getRawImageLevel() == 0)
+ && (texturep->isRawImageValid()))
{
LLUUID task_id = mTaskId;
uuid_set_t inventory_ids = mItemList;
@@ -962,20 +962,10 @@ void LLFloaterChangeItemThumbnail::onTexturePickerCommit()
{
onUploadComplete(asset_id, task_id, inventory_ids, handle);
};
- if (texturep->isRawImageValid())
- {
- LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getRawImage(),
- *mItemList.begin(),
- mTaskId,
- callback);
- }
- else
- {
- LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getCachedRawImage(),
- *mItemList.begin(),
- mTaskId,
- callback);
- }
+ LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getRawImage(),
+ *mItemList.begin(),
+ mTaskId,
+ callback);
}
else
{
diff --git a/indra/newview/llfloaterdestinations.cpp b/indra/newview/llfloaterdestinations.cpp
index 93cf02e835..fad9693e8f 100644
--- a/indra/newview/llfloaterdestinations.cpp
+++ b/indra/newview/llfloaterdestinations.cpp
@@ -25,11 +25,6 @@
* $/LicenseInfo$
*/
-/**
- * Floater that appears when buying an object, giving a preview
- * of its contents and their permissions.
- */
-
#include "llviewerprecompiledheaders.h"
#include "llfloaterdestinations.h"
diff --git a/indra/newview/llfloaterfonttest.cpp b/indra/newview/llfloaterfonttest.cpp
index 95d08cb9ce..d39b061d40 100644
--- a/indra/newview/llfloaterfonttest.cpp
+++ b/indra/newview/llfloaterfonttest.cpp
@@ -25,11 +25,6 @@
* $/LicenseInfo$
*/
-/**
- * Floater that appears when buying an object, giving a preview
- * of its contents and their permissions.
- */
-
#include "llviewerprecompiledheaders.h"
#include "llfloaterfonttest.h"
diff --git a/indra/newview/llfloatergltfasseteditor.cpp b/indra/newview/llfloatergltfasseteditor.cpp
new file mode 100644
index 0000000000..f21b8032d0
--- /dev/null
+++ b/indra/newview/llfloatergltfasseteditor.cpp
@@ -0,0 +1,527 @@
+/**
+ * @file llfloatergltfasseteditor.cpp
+ * @author Andrii Kleshchev
+ * @brief LLFloaterGltfAssetEditor class implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloatergltfasseteditor.h"
+
+#include "gltf/asset.h"
+#include "llcallbacklist.h"
+#include "llmenubutton.h"
+#include "llselectmgr.h"
+#include "llspinctrl.h"
+#include "llviewerobject.h"
+
+const LLColor4U DEFAULT_WHITE(255, 255, 255);
+
+/// LLFloaterGLTFAssetEditor
+
+LLFloaterGLTFAssetEditor::LLFloaterGLTFAssetEditor(const LLSD& key)
+ : LLFloater(key)
+ , mUIColor(LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE))
+{
+ setTitle("GLTF Asset Editor (WIP)");
+
+ mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); });
+ mEnableCallbackRegistrar.add("PanelObject.menuEnable", [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); });
+}
+
+LLFloaterGLTFAssetEditor::~LLFloaterGLTFAssetEditor()
+{
+ gIdleCallbacks.deleteFunction(idle, this);
+
+ if (mScroller)
+ {
+ removeChild(mScroller);
+ delete mScroller;
+ mScroller = NULL;
+ }
+}
+
+bool LLFloaterGLTFAssetEditor::postBuild()
+{
+ // Position
+ mMenuClipboardPos = getChild<LLMenuButton>("clipboard_pos_btn");
+ mCtrlPosX = getChild<LLSpinCtrl>("Pos X", true);
+ mCtrlPosX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosY = getChild<LLSpinCtrl>("Pos Y", true);
+ mCtrlPosY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlPosZ = getChild<LLSpinCtrl>("Pos Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Scale
+ mMenuClipboardScale = getChild<LLMenuButton>("clipboard_size_btn");
+ mCtrlScaleX = getChild<LLSpinCtrl>("Scale X", true);
+ mCtrlScaleX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleY = getChild<LLSpinCtrl>("Scale Y", true);
+ mCtrlScaleY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlScaleZ = getChild<LLSpinCtrl>("Scale Z", true);
+ mCtrlScaleZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+
+ // Rotation
+ mMenuClipboardRot = getChild<LLMenuButton>("clipboard_rot_btn");
+ mCtrlRotX = getChild<LLSpinCtrl>("Rot X", true);
+ mCtrlRotX->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotY = getChild<LLSpinCtrl>("Rot Y", true);
+ mCtrlRotY->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ mCtrlRotZ = getChild<LLSpinCtrl>("Rot Z", true);
+ mCtrlPosZ->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTransform(); });
+ setTransformsEnabled(false);
+ // todo: do multiple panels based on selected element.
+ mTransformsPanel = getChild<LLPanel>("transform_panel", true);
+ mTransformsPanel->setVisible(false);
+
+ mItemListPanel = getChild<LLPanel>("item_list_panel", true);
+ initFolderRoot();
+
+ return true;
+}
+
+void LLFloaterGLTFAssetEditor::initFolderRoot()
+{
+ if (mScroller || mFolderRoot)
+ {
+ LL_ERRS() << "Folder root already initialized" << LL_ENDL;
+ return;
+ }
+
+ LLRect scroller_view_rect = mItemListPanel->getRect();
+ scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom);
+ LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>());
+ scroller_params.rect(scroller_view_rect);
+ scroller_params.name("folder_scroller");
+ mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params);
+ mScroller->setFollowsAll();
+
+ // Insert that scroller into the panel widgets hierarchy
+ mItemListPanel->addChild(mScroller);
+
+ // Create the root model and view for all conversation sessions
+ LLGLTFFolderItem* base_item = new LLGLTFFolderItem(mGLTFViewModel);
+
+ LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
+ p.name = "Root";
+ p.title = "Root";
+ p.rect = LLRect(0, 0, getRect().getWidth(), 0);
+ p.parent_panel = mItemListPanel;
+ p.tool_tip = p.name;
+ p.listener = base_item;
+ p.view_model = &mGLTFViewModel;
+ p.root = NULL;
+ p.use_ellipses = true;
+ p.options_menu = "menu_gltf.xml"; // *TODO : create this or fix to be optional
+ mFolderRoot = LLUICtrlFactory::create<LLFolderView>(p);
+ mFolderRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
+ mFolderRoot->setEnableRegistrar(&mEnableCallbackRegistrar);
+ // Attach root to the scroller
+ mScroller->addChild(mFolderRoot);
+ mFolderRoot->setScrollContainer(mScroller);
+ mFolderRoot->setFollowsAll();
+ mFolderRoot->setOpen(true);
+ mFolderRoot->setSelectCallback([this](const std::deque<LLFolderViewItem*>& items, bool user_action) { onFolderSelectionChanged(items, user_action); });
+ mScroller->setVisible(true);
+
+ gIdleCallbacks.addFunction(idle, this);
+}
+
+void LLFloaterGLTFAssetEditor::onOpen(const LLSD& key)
+{
+ loadFromSelection();
+}
+
+void LLFloaterGLTFAssetEditor::idle(void* user_data)
+{
+ LLFloaterGLTFAssetEditor* floater = (LLFloaterGLTFAssetEditor*)user_data;
+
+ if (floater->mFolderRoot)
+ {
+ floater->mFolderRoot->update();
+ }
+}
+
+void LLFloaterGLTFAssetEditor::loadItem(S32 id, const std::string& name, LLGLTFFolderItem::EType type, LLFolderViewFolder* parent)
+{
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(id, name, type, mGLTFViewModel);
+
+ LLFolderViewItem::Params params;
+ params.name(name);
+ params.creation_date(0);
+ params.root(mFolderRoot);
+ params.listener(listener);
+ params.rect(LLRect());
+ params.tool_tip = params.name;
+ params.font_color = mUIColor;
+ params.font_highlight_color = mUIColor;
+ LLFolderViewItem* view = LLUICtrlFactory::create<LLFolderViewItem>(params);
+
+ view->addToFolder(parent);
+ view->setVisible(true);
+}
+
+void LLFloaterGLTFAssetEditor::loadFromNode(S32 node_id, LLFolderViewFolder* parent)
+{
+ if (mAsset->mNodes.size() <= node_id)
+ {
+ return;
+ }
+
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ std::string name = node.mName;
+ if (node.mName.empty())
+ {
+ name = getString("node_tittle");
+ }
+ else
+ {
+ name = node.mName;
+ }
+
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(node_id, name, LLGLTFFolderItem::TYPE_NODE, mGLTFViewModel);
+
+ LLFolderViewFolder::Params p;
+ p.root = mFolderRoot;
+ p.listener = listener;
+ p.name = name;
+ p.tool_tip = name;
+ p.font_color = mUIColor;
+ p.font_highlight_color = mUIColor;
+ LLFolderViewFolder* view = LLUICtrlFactory::create<LLFolderViewFolder>(p);
+
+ view->addToFolder(parent);
+ view->setVisible(true);
+ view->setOpen(true);
+
+ for (S32& node_id : node.mChildren)
+ {
+ loadFromNode(node_id, view);
+ }
+
+ if (node.mMesh != LL::GLTF::INVALID_INDEX && mAsset->mMeshes.size() > node.mMesh)
+ {
+ std::string name = mAsset->mMeshes[node.mMesh].mName;
+ if (name.empty())
+ {
+ name = getString("mesh_tittle");
+ }
+ loadItem(node.mMesh, name, LLGLTFFolderItem::TYPE_MESH, view);
+ }
+
+ if (node.mSkin != LL::GLTF::INVALID_INDEX && mAsset->mSkins.size() > node.mSkin)
+ {
+ std::string name = mAsset->mSkins[node.mSkin].mName;
+ if (name.empty())
+ {
+ name = getString("skin_tittle");
+ }
+ loadItem(node.mSkin, name, LLGLTFFolderItem::TYPE_SKIN, view);
+ }
+
+ view->setChildrenInited(true);
+}
+
+void LLFloaterGLTFAssetEditor::loadFromSelection()
+{
+ if (!mFolderRoot || LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1)
+ {
+ return;
+ }
+
+ LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(NULL);
+ LLViewerObject* objectp = node->getObject();
+ if (!objectp)
+ {
+ return;
+ }
+
+ mAsset = objectp->mGLTFAsset;
+ if (!mAsset)
+ {
+ return;
+ }
+
+ if (node->mName.empty())
+ {
+ setTitle(getString("floater_title"));
+ }
+ else
+ {
+ setTitle(node->mName);
+ }
+
+ LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
+ for (S32 i = 0; i < mAsset->mScenes.size(); i++)
+ {
+ LL::GLTF::Scene& scene = mAsset->mScenes[i];
+ std::string name = scene.mName;
+ if (scene.mName.empty())
+ {
+ name = getString("scene_tittle");
+ }
+ else
+ {
+ name = scene.mName;
+ }
+
+ LLGLTFFolderItem* listener = new LLGLTFFolderItem(i, name, LLGLTFFolderItem::TYPE_SCENE, mGLTFViewModel);
+
+
+ LLFolderViewFolder::Params p;
+ p.name = name;
+ p.root = mFolderRoot;
+ p.listener = listener;
+ p.tool_tip = name;
+ p.font_color = mUIColor;
+ p.font_highlight_color = mUIColor;
+ LLFolderViewFolder* view = LLUICtrlFactory::create<LLFolderViewFolder>(p);
+
+ view->addToFolder(mFolderRoot);
+ view->setVisible(true);
+ view->setOpen(true);
+
+ for (S32& node_id : scene.mNodes)
+ {
+ loadFromNode(node_id, view);
+ }
+ view->setChildrenInited(true);
+ }
+
+ mGLTFViewModel.requestSortAll();
+ mFolderRoot->setChildrenInited(true);
+ mFolderRoot->arrangeAll();
+ mFolderRoot->update();
+}
+
+void LLFloaterGLTFAssetEditor::onFolderSelectionChanged(const std::deque<LLFolderViewItem*>& items, bool user_action)
+{
+ if (items.empty())
+ {
+ setTransformsEnabled(false);
+ return;
+ }
+
+ LLFolderViewItem* item = items.front();
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ switch (vmi->getType())
+ {
+ case LLGLTFFolderItem::TYPE_NODE:
+ {
+ setTransformsEnabled(true);
+ loadNodeTransforms(vmi->getItemId());
+ break;
+ }
+ default:
+ {
+ setTransformsEnabled(false);
+ break;
+ }
+ }
+}
+
+void LLFloaterGLTFAssetEditor::setTransformsEnabled(bool val)
+{
+ mMenuClipboardPos->setEnabled(val);
+ mCtrlPosX->setEnabled(val);
+ mCtrlPosY->setEnabled(val);
+ mCtrlPosZ->setEnabled(val);
+ mMenuClipboardScale->setEnabled(val);
+ mCtrlScaleX->setEnabled(val);
+ mCtrlScaleY->setEnabled(val);
+ mCtrlScaleZ->setEnabled(val);
+ mMenuClipboardRot->setEnabled(val);
+ mCtrlRotX->setEnabled(val);
+ mCtrlRotY->setEnabled(val);
+ mCtrlRotZ->setEnabled(val);
+}
+
+void LLFloaterGLTFAssetEditor::loadNodeTransforms(S32 node_id)
+{
+ if (node_id < 0 || node_id >= mAsset->mNodes.size())
+ {
+ LL_ERRS() << "Node id out of range: " << node_id << LL_ENDL;
+ return;
+ }
+
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ mCtrlPosX->set(node.mTranslation[0]);
+ mCtrlPosY->set(node.mTranslation[1]);
+ mCtrlPosZ->set(node.mTranslation[2]);
+
+ mCtrlScaleX->set(node.mScale[0]);
+ mCtrlScaleY->set(node.mScale[1]);
+ mCtrlScaleZ->set(node.mScale[2]);
+
+ LLQuaternion object_rot = LLQuaternion(node.mRotation[0], node.mRotation[1], node.mRotation[2], node.mRotation[3]);
+ object_rot.getEulerAngles(&(mLastEulerDegrees.mV[VX]), &(mLastEulerDegrees.mV[VY]), &(mLastEulerDegrees.mV[VZ]));
+ mLastEulerDegrees *= RAD_TO_DEG;
+ mLastEulerDegrees.mV[VX] = fmod(ll_round(mLastEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VY] = fmod(ll_round(mLastEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+ mLastEulerDegrees.mV[VZ] = fmod(ll_round(mLastEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f);
+
+ mCtrlRotX->set(mLastEulerDegrees.mV[VX]);
+ mCtrlRotY->set(mLastEulerDegrees.mV[VY]);
+ mCtrlRotZ->set(mLastEulerDegrees.mV[VZ]);
+}
+
+void LLFloaterGLTFAssetEditor::onCommitTransform()
+{
+ if (!mFolderRoot)
+ {
+ LL_ERRS() << "Folder root not initialized" << LL_ENDL;
+ return;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ LL_ERRS() << "Nothing selected" << LL_ENDL;
+ return;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ LL_ERRS() << "Only nodes implemented" << LL_ENDL;
+ return;
+ }
+ S32 node_id = vmi->getItemId();
+ LL::GLTF::Node& node = mAsset->mNodes[node_id];
+
+ LL::GLTF::vec3 tr(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get());
+ node.setTranslation(tr);
+
+ LL::GLTF::vec3 scale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get());
+ node.setScale(scale);
+
+ LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get());
+ new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION);
+ new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION);
+
+ // Note: must compare before conversion to radians, some value can go 'around' 360
+ LLVector3 delta = new_rot - mLastEulerDegrees;
+
+ if (delta.magVec() >= 0.0005f)
+ {
+ mLastEulerDegrees = new_rot;
+ new_rot *= DEG_TO_RAD;
+
+ LLQuaternion rotation;
+ rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]);
+ LL::GLTF::quat q;
+ q[0] = rotation.mQ[VX];
+ q[1] = rotation.mQ[VY];
+ q[2] = rotation.mQ[VZ];
+ q[3] = rotation.mQ[VW];
+
+ node.setRotation(q);
+ }
+
+ mAsset->updateTransforms();
+}
+
+void LLFloaterGLTFAssetEditor::onMenuDoToSelected(const LLSD& userdata)
+{
+ std::string command = userdata.asString();
+
+ if (command == "psr_paste")
+ {
+ // todo: implement
+ // onPastePos();
+ // onPasteSize();
+ // onPasteRot();
+ }
+ else if (command == "pos_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "size_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_paste")
+ {
+ // todo: implement
+ }
+ else if (command == "psr_copy")
+ {
+ // onCopyPos();
+ // onCopySize();
+ // onCopyRot();
+ }
+ else if (command == "pos_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "size_copy")
+ {
+ // todo: implement
+ }
+ else if (command == "rot_copy")
+ {
+ // todo: implement
+ }
+}
+
+bool LLFloaterGLTFAssetEditor::onMenuEnableItem(const LLSD& userdata)
+{
+ if (!mFolderRoot)
+ {
+ return false;
+ }
+
+ LLFolderViewItem* item = mFolderRoot->getCurSelectedItem();
+ if (!item)
+ {
+ return false;
+ }
+
+ LLGLTFFolderItem* vmi = static_cast<LLGLTFFolderItem*>(item->getViewModelItem());
+
+ if (!vmi || vmi->getType() != LLGLTFFolderItem::TYPE_NODE)
+ {
+ return false;
+ }
+
+ std::string command = userdata.asString();
+ if (command == "pos_paste" || command == "size_paste" || command == "rot_paste")
+ {
+ // todo: implement
+ return true;
+ }
+ if (command == "psr_copy")
+ {
+ // todo: implement
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/indra/newview/llfloatergltfasseteditor.h b/indra/newview/llfloatergltfasseteditor.h
new file mode 100644
index 0000000000..e35ed30ed0
--- /dev/null
+++ b/indra/newview/llfloatergltfasseteditor.h
@@ -0,0 +1,101 @@
+/**
+ * @file llfloatergltfasseteditor.h
+ * @author Andrii Kleshchev
+ * @brief LLFloaterGltfAssetEditor header file
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERGLTFASSETEDITOR_H
+#define LL_LLFLOATERGLTFASSETEDITOR_H
+
+#include "llfloater.h"
+
+#include "llgltffoldermodel.h"
+
+namespace LL
+{
+ namespace GLTF
+ {
+ class Asset;
+ }
+}
+
+class LLSpinCtrl;
+class LLMenuButton;
+
+class LLFloaterGLTFAssetEditor : public LLFloater
+{
+public:
+ LLFloaterGLTFAssetEditor(const LLSD& key);
+ ~LLFloaterGLTFAssetEditor();
+
+ bool postBuild() override;
+ void onOpen(const LLSD& key) override;
+ void initFolderRoot();
+
+ LLGLTFViewModel& getRootViewModel() { return mGLTFViewModel; }
+
+ static void idle(void* user_data);
+ void loadItem(S32 id, const std::string& name, LLGLTFFolderItem::EType type, LLFolderViewFolder* parent);
+ void loadFromNode(S32 node, LLFolderViewFolder* parent);
+ void loadFromSelection();
+
+protected:
+ void onFolderSelectionChanged(const std::deque<LLFolderViewItem*>& items, bool user_action);
+ void onCommitTransform();
+ void onMenuDoToSelected(const LLSD& userdata);
+ bool onMenuEnableItem(const LLSD& userdata);
+
+ void setTransformsEnabled(bool val);
+ void loadNodeTransforms(S32 id);
+
+private:
+
+ std::shared_ptr<LL::GLTF::Asset> mAsset;
+
+ // Folder view related
+ LLUIColor mUIColor;
+ LLGLTFViewModel mGLTFViewModel;
+ LLPanel* mItemListPanel = nullptr;
+ LLFolderView* mFolderRoot = nullptr;
+ LLScrollContainer* mScroller = nullptr;
+
+ // Transforms panel
+ LLVector3 mLastEulerDegrees;
+
+ LLPanel* mTransformsPanel = nullptr;
+ LLMenuButton* mMenuClipboardPos = nullptr;
+ LLSpinCtrl* mCtrlPosX = nullptr;
+ LLSpinCtrl* mCtrlPosY = nullptr;
+ LLSpinCtrl* mCtrlPosZ = nullptr;
+ LLMenuButton* mMenuClipboardScale = nullptr;
+ LLSpinCtrl* mCtrlScaleX = nullptr;
+ LLSpinCtrl* mCtrlScaleY = nullptr;
+ LLSpinCtrl* mCtrlScaleZ = nullptr;
+ LLMenuButton* mMenuClipboardRot = nullptr;
+ LLSpinCtrl* mCtrlRotX = nullptr;
+ LLSpinCtrl* mCtrlRotY = nullptr;
+ LLSpinCtrl* mCtrlRotZ = nullptr;
+};
+
+#endif
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index 9b08ecd059..5dd78fe1db 100644
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -94,6 +94,7 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id)
mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&LLFloaterIMSession::enableGearMenuItem, this, _2));
mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2));
mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&LLFloaterIMSession::checkGearMenuItem, this, _2));
+ mVoiceChannelChanged = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMSession::onVoiceChannelChanged, this, _1));
setDocked(true);
}
@@ -292,6 +293,8 @@ LLFloaterIMSession::~LLFloaterIMSession()
}
LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this);
+
+ mVoiceChannelChanged.disconnect();
}
@@ -521,6 +524,14 @@ void LLFloaterIMSession::sendParticipantsAddedNotification(const uuid_vec_t& uui
sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args));
}
+void LLFloaterIMSession::onVoiceChannelChanged(const LLUUID &session_id)
+{
+ if (session_id == mSessionID)
+ {
+ boundVoiceChannel();
+ }
+}
+
void LLFloaterIMSession::boundVoiceChannel()
{
LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
@@ -552,7 +563,7 @@ void LLFloaterIMSession::onCallButtonClicked()
}
}
-void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal)
+void LLFloaterIMSession::onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
{
if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL)
{
diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h
index 0c48aa7728..7bc54cf3da 100644
--- a/indra/newview/llfloaterimsession.h
+++ b/indra/newview/llfloaterimsession.h
@@ -114,8 +114,7 @@ public:
// Implements LLVoiceClientStatusObserver::onChange() to enable the call
// button when voice is available
- void onChange(EStatusType status, const std::string &channelURI,
- bool proximal);
+ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal);
virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; }
virtual void onVoiceChannelStateChanged(
@@ -162,6 +161,8 @@ private:
void onCallButtonClicked();
+ void onVoiceChannelChanged(const LLUUID &session_id);
+
void boundVoiceChannel();
// Add the "User is typing..." indicator.
@@ -196,6 +197,9 @@ private:
uuid_vec_t mInvitedParticipants;
uuid_vec_t mPendingParticipants;
+ // notification when the voice channel is swapped out from beneath us.
+ boost::signals2::connection mVoiceChannelChanged;
+
// connection to voice channel state change signal
boost::signals2::connection mVoiceChannelStateChangeConnection;
diff --git a/indra/newview/llgltffolderitem.cpp b/indra/newview/llgltffolderitem.cpp
new file mode 100644
index 0000000000..77a19c060d
--- /dev/null
+++ b/indra/newview/llgltffolderitem.cpp
@@ -0,0 +1,164 @@
+/**
+ * @file llgltffolderitem.cpp
+ * @author Andrey Kleshchev
+ * @brief LLGLTFFolderItem class implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llgltffolderitem.h"
+
+#include "llinventoryicon.h"
+
+/// LLGLTFItem
+
+LLGLTFFolderItem::LLGLTFFolderItem(S32 id, const std::string &display_name, EType type, LLFolderViewModelInterface& root_view_model)
+ : LLFolderViewModelItemCommon(root_view_model)
+ , mName(display_name)
+ , mItemType(type)
+ , mItemId(id)
+{
+ init();
+}
+
+LLGLTFFolderItem::LLGLTFFolderItem(LLFolderViewModelInterface& root_view_model)
+ : LLFolderViewModelItemCommon(root_view_model)
+{
+ init();
+}
+
+LLGLTFFolderItem::~LLGLTFFolderItem()
+{
+
+}
+
+void LLGLTFFolderItem::init()
+{
+ // using inventory icons as a placeholder.
+ // Todo: GLTF needs to have own icons
+ switch (mItemType)
+ {
+ case TYPE_SCENE:
+ pIcon = LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_OBJECT_MULTI);
+ break;
+ case TYPE_NODE:
+ pIcon = LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_OBJECT);
+ break;
+ case TYPE_MESH:
+ pIcon = LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_MESH);
+ break;
+ case TYPE_SKIN:
+ pIcon = LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_BODYPART_SKIN);
+ break;
+ default:
+ pIcon = LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_OBJECT);
+ break;
+ }
+}
+
+
+bool LLGLTFFolderItem::filterChildItem(LLFolderViewModelItem* item, LLFolderViewFilter& filter)
+{
+ S32 filter_generation = filter.getCurrentGeneration();
+
+ bool continue_filtering = true;
+ if (item)
+ {
+ if (item->getLastFilterGeneration() < filter_generation)
+ {
+ // Recursive application of the filter for child items (CHUI-849)
+ continue_filtering = item->filter(filter);
+ }
+
+ // Update latest generation to pass filter in parent and propagate up to root
+ if (item->passedFilter())
+ {
+ LLGLTFFolderItem* view_model = this;
+
+ while (view_model && view_model->mMostFilteredDescendantGeneration < filter_generation)
+ {
+ view_model->mMostFilteredDescendantGeneration = filter_generation;
+ view_model = static_cast<LLGLTFFolderItem*>(view_model->mParent);
+ }
+ }
+ }
+ return continue_filtering;
+}
+
+bool LLGLTFFolderItem::filter(LLFolderViewFilter& filter)
+{
+ const S32 filter_generation = filter.getCurrentGeneration();
+ const S32 must_pass_generation = filter.getFirstRequiredGeneration();
+
+ if (getLastFilterGeneration() >= must_pass_generation
+ && getLastFolderFilterGeneration() >= must_pass_generation
+ && !passedFilter(must_pass_generation))
+ {
+ // failed to pass an earlier filter that was a subset of the current one
+ // go ahead and flag this item as not pass
+ setPassedFilter(false, filter_generation);
+ setPassedFolderFilter(false, filter_generation);
+ return true;
+ }
+
+ bool is_folder = true;
+ const bool passed_filter_folder = is_folder ? filter.checkFolder(this) : true;
+ setPassedFolderFilter(passed_filter_folder, filter_generation);
+
+ bool continue_filtering = true;
+
+ if (!mChildren.empty()
+ && (getLastFilterGeneration() < must_pass_generation // haven't checked descendants against minimum required generation to pass
+ || descendantsPassedFilter(must_pass_generation))) // or at least one descendant has passed the minimum requirement
+ {
+ // now query children
+ for (child_list_t::iterator iter = mChildren.begin(), end_iter = mChildren.end(); iter != end_iter; ++iter)
+ {
+ continue_filtering = filterChildItem((*iter), filter);
+ if (!continue_filtering)
+ {
+ break;
+ }
+ }
+ }
+
+ // If we didn't use all the filter time that means we filtered all of our descendants so we can filter ourselves now
+ if (continue_filtering)
+ {
+ // This is where filter check on the item done (CHUI-849)
+ const bool passed_filter = filter.check(this);
+ if (passed_filter && mChildren.empty() && is_folder) // Update the latest filter generation for empty folders
+ {
+ LLGLTFFolderItem* view_model = this;
+ while (view_model && view_model->mMostFilteredDescendantGeneration < filter_generation)
+ {
+ view_model->mMostFilteredDescendantGeneration = filter_generation;
+ view_model = static_cast<LLGLTFFolderItem*>(view_model->mParent);
+ }
+ }
+ setPassedFilter(passed_filter, filter_generation, filter.getStringMatchOffset(this), filter.getFilterStringSize());
+ continue_filtering = !filter.isTimedOut();
+ }
+ return continue_filtering;
+}
diff --git a/indra/newview/llgltffolderitem.h b/indra/newview/llgltffolderitem.h
new file mode 100644
index 0000000000..89d90c81cc
--- /dev/null
+++ b/indra/newview/llgltffolderitem.h
@@ -0,0 +1,128 @@
+/**
+ * @file llgltffolderitem.h
+ * @author Andrey Kleshchev
+ * @brief LLGLTFFolderItem header file
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLGLTFFOLDERITEM_H
+#define LL_LLGLTFFOLDERITEM_H
+
+#include "llfloater.h"
+
+#include "llfolderviewmodel.h"
+
+class LLGLTFFolderItem : public LLFolderViewModelItemCommon
+{
+public:
+ enum EType
+ {
+ TYPE_ROOT,
+ TYPE_SCENE,
+ TYPE_NODE,
+ TYPE_MESH,
+ TYPE_SKIN,
+ };
+
+ LLGLTFFolderItem(S32 id, const std::string &display_name, EType type, LLFolderViewModelInterface& root_view_model);
+ LLGLTFFolderItem(LLFolderViewModelInterface& root_view_model);
+ virtual ~LLGLTFFolderItem();
+
+ void init();
+
+ const std::string& getName() const override { return mName; }
+ const std::string& getDisplayName() const override { return mName; }
+ const std::string& getSearchableName() const override { return mName; }
+
+ std::string getSearchableDescription() const override { return std::string(); }
+ std::string getSearchableCreatorName()const override { return std::string(); }
+ std::string getSearchableUUIDString() const override { return std::string(); }
+
+ LLPointer<LLUIImage> getIcon() const override { return pIcon; }
+ LLPointer<LLUIImage> getIconOpen() const override { return getIcon(); }
+ LLPointer<LLUIImage> getIconOverlay() const override { return NULL; }
+
+ LLFontGL::StyleFlags getLabelStyle() const override { return LLFontGL::NORMAL; }
+ std::string getLabelSuffix() const override { return std::string(); }
+
+ void openItem(void) override {}
+ void closeItem(void) override {}
+ void selectItem(void) override {}
+
+ void navigateToFolder(bool new_window = false, bool change_mode = false) override {}
+
+ bool isItemWearable() const override { return false; }
+
+ bool isItemRenameable() const override { return false; }
+ bool renameItem(const std::string& new_name) override { return false; }
+
+ bool isItemMovable(void) const override { return false; } // Can be moved to another folder
+ void move(LLFolderViewModelItem* parent_listener) override {}
+
+ bool isItemRemovable(bool check_worn = true) const override { return false; }
+ bool removeItem() override { return false; }
+ void removeBatch(std::vector<LLFolderViewModelItem*>& batch) override {}
+
+ bool isItemCopyable(bool can_copy_as_link = true) const override { return false; }
+ bool copyToClipboard() const override { return false; }
+ bool cutToClipboard() override { return false; }
+ bool isCutToClipboard() override { return false; }
+
+ bool isClipboardPasteable() const override { return false; }
+ void pasteFromClipboard() override {}
+ void pasteLinkFromClipboard() override {}
+
+ void buildContextMenu(LLMenuGL& menu, U32 flags) override {};
+
+ bool potentiallyVisible() override { return true; }; // is the item definitely visible or we haven't made up our minds yet?
+
+ bool hasChildren() const override { return mChildren.size() > 0; }
+
+ bool dragOrDrop(
+ MASK mask,
+ bool drop,
+ EDragAndDropType cargo_type,
+ void* cargo_data,
+ std::string& tooltip_msg) override
+ {
+ return false;
+ }
+
+ bool filterChildItem(LLFolderViewModelItem* item, LLFolderViewFilter& filter);
+ bool filter(LLFolderViewFilter& filter) override;
+
+ EType getType() const { return mItemType; }
+ S32 getItemId() const { return mItemId; }
+
+private:
+ LLUIImagePtr pIcon;
+ std::string mName;
+ EType mItemType = TYPE_ROOT;
+
+ // mItemId can be an id in a mesh vector, node vector or any other vector.
+ // mItemId is not nessesarily unique, ex: some nodes can reuse the same
+ // mesh or skin, so mesh-items can have the same id.
+ S32 mItemId = -1;
+};
+
+#endif
diff --git a/indra/newview/llgltffoldermodel.cpp b/indra/newview/llgltffoldermodel.cpp
new file mode 100644
index 0000000000..de2510dc4a
--- /dev/null
+++ b/indra/newview/llgltffoldermodel.cpp
@@ -0,0 +1,73 @@
+/**
+ * @file llgltffoldermodel.cpp
+ * @author Andrey Kleshchev
+ * @brief gltf model's folder structure related classes
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llgltffoldermodel.h"
+
+#include "llfolderviewitem.h"
+
+bool LLGLTFSort::operator()(const LLGLTFFolderItem* const& a, const LLGLTFFolderItem* const& b) const
+{
+ // Comparison operator: returns "true" is a comes before b, "false" otherwise
+ S32 compare = LLStringUtil::compareDict(a->getName(), b->getName());
+ return (compare < 0);
+}
+
+/// LLGLTFViewModel
+
+LLGLTFViewModel::LLGLTFViewModel()
+ : base_t(new LLGLTFSort(), new LLGLTFFilter())
+{}
+
+void LLGLTFViewModel::sort(LLFolderViewFolder* folder)
+{
+ base_t::sort(folder);
+}
+
+ /// LLGLTFNode
+// LLUICtrlFactory::create<LLGLTFNode>(params);
+class LLGLTFNode : public LLFolderViewItem
+{
+public:
+ struct Params : public LLInitParam::Block<Params, LLFolderViewItem::Params>
+ {
+ Params();
+ };
+ ~LLGLTFNode();
+protected:
+ LLGLTFNode(const Params& p);
+};
+
+LLGLTFNode::LLGLTFNode(const LLGLTFNode::Params& p)
+ : LLFolderViewItem(p)
+{
+}
+
+LLGLTFNode::~LLGLTFNode()
+{
+}
diff --git a/indra/newview/llgltffoldermodel.h b/indra/newview/llgltffoldermodel.h
new file mode 100644
index 0000000000..69b284aa31
--- /dev/null
+++ b/indra/newview/llgltffoldermodel.h
@@ -0,0 +1,91 @@
+/**
+ * @file llfloatergltfasseteditor.h
+ * @author Andrey Kleshchev
+ * @brief gltf model's folder structure related classes
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLGLTFFOLDERMODEL_H
+#define LL_LLGLTFFOLDERMODEL_H
+
+#include "llfolderviewmodel.h"
+#include "llgltffolderitem.h"
+
+class LLGLTFSort
+{
+public:
+ LLGLTFSort() { }
+ bool operator()(const LLGLTFFolderItem* const& a, const LLGLTFFolderItem* const& b) const;
+private:
+};
+
+class LLGLTFFilter : public LLFolderViewFilter
+{
+public:
+ LLGLTFFilter() { mEmpty = ""; }
+ ~LLGLTFFilter() {}
+
+ bool check(const LLFolderViewModelItem* item) { return true; }
+ bool checkFolder(const LLFolderViewModelItem* folder) const { return true; }
+ void setEmptyLookupMessage(const std::string& message) { }
+ std::string getEmptyLookupMessage(bool is_empty_folder = false) const { return mEmpty; }
+ bool showAllResults() const { return true; }
+ std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const { return std::string::npos; }
+ std::string::size_type getFilterStringSize() const { return 0; }
+
+ bool isActive() const { return false; }
+ bool isModified() const { return false; }
+ void clearModified() { }
+ const std::string& getName() const { return mEmpty; }
+ const std::string& getFilterText() { return mEmpty; }
+ void setModified(EFilterModified behavior = FILTER_RESTART) { }
+
+ void resetTime(S32 timeout) { }
+ bool isTimedOut() { return false; }
+
+ bool isDefault() const { return true; }
+ bool isNotDefault() const { return false; }
+ void markDefault() { }
+ void resetDefault() { }
+
+ S32 getCurrentGeneration() const { return 0; }
+ S32 getFirstSuccessGeneration() const { return 0; }
+ S32 getFirstRequiredGeneration() const { return 0; }
+private:
+ std::string mEmpty;
+};
+
+class LLGLTFViewModel
+ : public LLFolderViewModel<LLGLTFSort, LLGLTFFolderItem, LLGLTFFolderItem, LLGLTFFilter>
+{
+public:
+ typedef LLFolderViewModel< LLGLTFSort, LLGLTFFolderItem, LLGLTFFolderItem, LLGLTFFilter> base_t;
+ LLGLTFViewModel();
+
+ void sort(LLFolderViewFolder* folder);
+ bool startDrag(std::vector<LLFolderViewModelItem*>& items) { return false; }
+
+private:
+};
+
+#endif
diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp
index b86d4214d6..ba9c9fa13f 100644
--- a/indra/newview/llgroupactions.cpp
+++ b/indra/newview/llgroupactions.cpp
@@ -248,7 +248,7 @@ void LLGroupActions::startCall(const LLUUID& group_id)
return;
}
- LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, true);
+ LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, LLSD());
if (session_id == LLUUID::null)
{
LL_WARNS() << "Error adding session" << LL_ENDL;
diff --git a/indra/newview/llheroprobemanager.cpp b/indra/newview/llheroprobemanager.cpp
index f544b70329..66ccdd2b32 100644
--- a/indra/newview/llheroprobemanager.cpp
+++ b/indra/newview/llheroprobemanager.cpp
@@ -188,18 +188,8 @@ void LLHeroProbeManager::update()
LLVector3(0, 0, -1)
};
- // Iterate through each face of the cube
- for (int i = 0; i < 6; i++)
- {
- float cube_facing = fmax(-1, fmin(1.0f, cameraDirection * cubeFaces[i]));
-
- cube_facing = 1 - cube_facing;
-
- mFaceUpdateList[i] = ceilf(cube_facing * gPipeline.RenderHeroProbeConservativeUpdateMultiplier);
- }
-
-
mProbes[0]->mOrigin = probe_pos;
+ mProbes[0]->mRadius = mNearestHero->getScale().magVec() * 0.5f;
}
else
{
@@ -220,9 +210,10 @@ void LLHeroProbeManager::renderProbes()
static LLCachedControl<S32> sDetail(gSavedSettings, "RenderHeroReflectionProbeDetail", -1);
static LLCachedControl<S32> sLevel(gSavedSettings, "RenderHeroReflectionProbeLevel", 3);
+ static LLCachedControl<S32> sUpdateRate(gSavedSettings, "RenderHeroProbeUpdateRate", 0);
F32 near_clip = 0.01f;
- if (mNearestHero != nullptr && (gPipeline.RenderHeroProbeUpdateRate == 0 || (gFrameCount % gPipeline.RenderHeroProbeUpdateRate) == 0) &&
+ if (mNearestHero != nullptr &&
!gTeleportDisplay && !gDisconnected && !LLAppViewer::instance()->logoutRequestSent())
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("hpmu - realtime");
@@ -232,20 +223,36 @@ void LLHeroProbeManager::renderProbes()
gPipeline.mReflectionMapManager.mRadiancePass = true;
mRenderingMirror = true;
- doOcclusion();
+ S32 rate = sUpdateRate;
- for (U32 j = 0; j < mProbes.size(); j++)
+ // rate must be divisor of 6 (1, 2, 3, or 6)
+ if (rate < 1)
+ {
+ rate = 1;
+ }
+ else if (rate > 3)
{
+ rate = 6;
+ }
+
+ S32 face = gFrameCount % 6;
+
+ if (!mProbes.empty() && !mProbes[0].isNull() && !mProbes[0]->mOccluded)
+ {
+ LL_PROFILE_ZONE_NUM(gFrameCount % rate);
+ LL_PROFILE_ZONE_NUM(rate);
+
for (U32 i = 0; i < 6; ++i)
{
- if (mFaceUpdateList[i] > 0 && mCurrentProbeUpdateFrame % mFaceUpdateList[i] == 0)
- {
- updateProbeFace(mProbes[j], i, mNearestHero->getReflectionProbeIsDynamic() && sDetail > 0, near_clip);
- mCurrentProbeUpdateFrame = 0;
+ if ((gFrameCount % rate) == (i % rate))
+ { // update 6/rate faces per frame
+ LL_PROFILE_ZONE_NUM(i);
+ updateProbeFace(mProbes[0], i, mNearestHero->getReflectionProbeIsDynamic() && sDetail > 0, near_clip);
}
}
- generateRadiance(mProbes[j]);
+ generateRadiance(mProbes[0]);
}
+
mRenderingMirror = false;
gPipeline.mReflectionMapManager.mRadiancePass = radiance_pass;
@@ -253,8 +260,6 @@ void LLHeroProbeManager::renderProbes()
mProbes[0]->mViewerObject = mNearestHero;
mProbes[0]->autoAdjustOrigin();
}
-
- mCurrentProbeUpdateFrame++;
}
// Do the reflection map update render passes.
@@ -388,6 +393,7 @@ void LLHeroProbeManager::updateProbeFace(LLReflectionMap* probe, U32 face, bool
// Useful when we may not always be rendering a full set of faces of the probe.
void LLHeroProbeManager::generateRadiance(LLReflectionMap* probe)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
S32 sourceIdx = mReflectionProbeCount;
// Unlike the reflectionmap manager, all probes are considered "realtime" for hero probes.
@@ -594,7 +600,7 @@ void LLHeroProbeManager::doOcclusion()
for (auto& probe : mProbes)
{
- if (probe != nullptr && probe != mDefaultProbe)
+ if (probe != nullptr)
{
probe->doOcclusion(eye);
}
diff --git a/indra/newview/llheroprobemanager.h b/indra/newview/llheroprobemanager.h
index c8d505f4c3..28852770c3 100644
--- a/indra/newview/llheroprobemanager.h
+++ b/indra/newview/llheroprobemanager.h
@@ -147,9 +147,6 @@ private:
bool mReset = false;
bool mRenderingMirror = false;
- std::map<int, int> mFaceUpdateList;
-
- U32 mCurrentProbeUpdateFrame = 0;
std::vector<LLPointer<LLVOVolume>> mHeroVOList;
LLPointer<LLVOVolume> mNearestHero;
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index 7ed10b062c..05b1fec8e5 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -87,8 +87,24 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size()
/** Timeout of outgoing session initialization (in seconds) */
const static U32 SESSION_INITIALIZATION_TIMEOUT = 30;
-void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents);
-void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType);
+// This enum corresponds to the sim's and adds P2P_CHAT_SESSION,
+// as webrtc uses the multiagent chat mechanism for p2p calls,
+// instead of relying on vivox calling.
+// Don't change this without consulting a server developer.
+enum EMultiAgentChatSessionType
+{
+ GROUP_CHAT_SESSION = 0,
+ CONFERENCE_SESSION = 1,
+ P2P_CHAT_SESSION = 2,
+ SESSION_TYPE_COUNT
+};
+
+
+void startConferenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents);
+
+void startP2PVoiceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId);
+
+void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType, const LLSD& voiceChannelInfo);
void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp);
void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite);
@@ -137,7 +153,7 @@ void process_dnd_im(const LLSD& notification)
name,
IM_NOTHING_SPECIAL,
fromID,
- false,
+ LLSD(),
false); //will need slight refactor to retrieve whether offline message or not (assume online for now)
}
@@ -395,7 +411,7 @@ void on_new_message(const LLSD& msg)
notify_of_message(msg, false);
}
-void startConfrenceCoro(std::string url,
+void startConferenceCoro(std::string url,
LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents)
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
@@ -407,6 +423,16 @@ void startConfrenceCoro(std::string url,
postData["method"] = "start conference";
postData["session-id"] = tempSessionId;
postData["params"] = agents;
+ LLSD altParams;
+ std::string voice_server_type = gSavedSettings.getString("VoiceServerType");
+ if (voice_server_type.empty())
+ {
+ // default to the server type associated with the region we're on.
+ LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+ voice_server_type = versionInfo.internalVoiceServerType;
+ }
+ altParams["voice_server_type"] = voice_server_type;
+ postData["alt_params"] = altParams;
LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
@@ -436,7 +462,46 @@ void startConfrenceCoro(std::string url,
}
}
-void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType)
+void startP2PVoiceCoro(std::string url, LLUUID sessionID, LLUUID creatorId, LLUUID otherParticipantId)
+{
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("StartP2PVoiceCoro", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+
+ LLSD postData;
+ postData["method"] = "start p2p voice";
+ postData["session-id"] = sessionID;
+ postData["params"] = otherParticipantId;
+ LLSD altParams;
+ std::string voice_server_type = gSavedSettings.getString("VoiceServerType");
+ if (voice_server_type.empty())
+ {
+ // default to the server type associated with the region we're on.
+ LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+ voice_server_type = versionInfo.internalVoiceServerType;
+ }
+ altParams["voice_server_type"] = voice_server_type;
+ postData["alt_params"] = altParams;
+
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (!status)
+ {
+ LL_WARNS("LLIMModel") << "Failed to start p2p session:" << postData << "->" << result << LL_ENDL;
+ // try an "old school" way.
+ // *TODO: What about other error status codes? 4xx 5xx?
+ if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST))
+ {
+ static const std::string error_string("session_does_not_exist_error");
+ gIMMgr->showSessionStartError(error_string, sessionID);
+ }
+ }
+}
+
+void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType, const LLSD& voiceChannelInfo)
{
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
@@ -502,7 +567,7 @@ void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvit
if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType)
{
- gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL);
+ gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL, voiceChannelInfo);
}
if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE
@@ -648,7 +713,13 @@ LLIMModel::LLIMModel()
LLCallDialogManager::instance();
}
-LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)
+LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id,
+ const std::string& name,
+ const EInstantMessage& type,
+ const LLUUID& other_participant_id,
+ const LLSD& voice_channel_info,
+ const uuid_vec_t& ids,
+ bool has_offline_msg)
: mSessionID(session_id),
mName(name),
mType(type),
@@ -658,43 +729,30 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string&
mOtherParticipantID(other_participant_id),
mInitialTargetIDs(ids),
mVoiceChannel(NULL),
+ mP2PAsAdhocCall(false),
mSpeakers(NULL),
mSessionInitialized(false),
mCallBackEnabled(true),
mTextIMPossible(true),
mStartCallOnInitialize(false),
- mStartedAsIMCall(voice),
+ mStartedAsIMCall(!voice_channel_info.isUndefined()),
mIsDNDsend(false),
mAvatarNameCacheConnection()
{
// set P2P type by default
- mSessionType = P2P_SESSION;
+ mSessionType = P2P_SESSION;
if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType)
{
- mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id);
+ mP2PAsAdhocCall = (LLVoiceClient::getInstance()->getOutgoingCallInterface(voice_channel_info) == NULL);
}
else
{
- mVoiceChannel = new LLVoiceChannelGroup(session_id, name);
-
// determine whether it is group or conference session
- if (gAgent.isInGroup(mSessionID))
- {
- mSessionType = GROUP_SESSION;
- }
- else
- {
- mSessionType = ADHOC_SESSION;
- }
- }
-
- if(mVoiceChannel)
- {
- mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3));
+ mSessionType = gAgent.isInGroup(mSessionID) ? GROUP_SESSION : ADHOC_SESSION;
}
- mSpeakers = new LLIMSpeakerMgr(mVoiceChannel);
+ initVoiceChannel(voice_channel_info);
// All participants will be added to the list of people we've recently interacted with.
@@ -704,8 +762,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string&
//we need to wait for session initialization for outgoing ad-hoc and group chat session
//correct session id for initiated ad-hoc chat will be received from the server
- if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID,
- mInitialTargetIDs, mType))
+ if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, mInitialTargetIDs, mType, mP2PAsAdhocCall))
{
//we don't need to wait for any responses
//so we're already initialized
@@ -735,6 +792,73 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string&
}
}
+void LLIMModel::LLIMSession::initVoiceChannel(const LLSD& voiceChannelInfo)
+{
+
+ if (mVoiceChannel)
+ {
+ if (mVoiceChannel->isThisVoiceChannel(voiceChannelInfo))
+ {
+ return;
+ }
+ mVoiceChannelStateChangeConnection.disconnect();
+
+ mVoiceChannel->deactivate();
+
+ delete mVoiceChannel;
+ mVoiceChannel = NULL;
+ }
+ mP2PAsAdhocCall = false;
+ if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType)
+ {
+ LLVoiceP2POutgoingCallInterface *outgoingInterface = LLVoiceClient::getInstance()->getOutgoingCallInterface(voiceChannelInfo);
+
+ if (outgoingInterface)
+ {
+ // only use LLVoiceChannelP2P if the provider can handle the special P2P interface,
+ // which uses the voice server to relay calls and invites. Otherwise,
+ // we use the group voice provider.
+ mVoiceChannel = new LLVoiceChannelP2P(mSessionID, mName, mOtherParticipantID, outgoingInterface);
+ }
+ else
+ {
+ mP2PAsAdhocCall = true;
+ mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, true);
+ }
+ }
+ else
+ {
+ // determine whether it is group or conference session
+ if (mSessionType == GROUP_SESSION)
+ {
+ mSessionType = GROUP_SESSION;
+ mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, false);
+ }
+ else if (mSessionType == ADHOC_SESSION)
+ {
+ mSessionType = ADHOC_SESSION;
+ mVoiceChannel = new LLVoiceChannelGroup(mSessionID, mName, false);
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Invalid Session Type when initializing voice channel: " << mSessionType << LL_ENDL;
+ return;
+ }
+ }
+
+ mVoiceChannelStateChangeConnection =
+ mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3));
+
+ if (!mSpeakers)
+ {
+ mSpeakers = new LLIMSpeakerMgr(mVoiceChannel);
+ }
+ else
+ {
+ mSpeakers->setVoiceChannel(mVoiceChannel);
+ }
+}
+
void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name)
{
mAvatarNameCacheConnection.disconnect();
@@ -838,7 +962,7 @@ void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::ES
break;
}
// Update speakers list when connected
- if (LLVoiceChannel::STATE_CONNECTED == new_state)
+ if (mSpeakers && LLVoiceChannel::STATE_CONNECTED == new_state)
{
mSpeakers->update(true);
}
@@ -854,22 +978,6 @@ LLIMModel::LLIMSession::~LLIMSession()
delete mSpeakers;
mSpeakers = NULL;
- // End the text IM session if necessary
- if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull())
- {
- switch(mType)
- {
- case IM_NOTHING_SPECIAL:
- case IM_SESSION_P2P_INVITE:
- LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID);
- break;
-
- default:
- // Appease the linux compiler
- break;
- }
- }
-
mVoiceChannelStateChangeConnection.disconnect();
// HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point).
@@ -886,7 +994,10 @@ void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_
if (new_session_id != mSessionID)
{
mSessionID = new_session_id;
- mVoiceChannel->updateSessionID(new_session_id);
+ if (mVoiceChannel)
+ {
+ mVoiceChannel->updateSessionID(new_session_id);
+ }
}
}
@@ -1444,7 +1555,7 @@ void LLIMModel::testMessages()
//session name should not be empty
bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type,
- const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)
+ const LLUUID& other_participant_id, const uuid_vec_t& ids, const LLSD& voiceChannelInfo, bool has_offline_msg)
{
if (name.empty())
{
@@ -1458,7 +1569,7 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co
return false;
}
- LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg);
+ LLIMSession *session = new LLIMSession(session_id, name, type, other_participant_id, voiceChannelInfo, ids, has_offline_msg);
mId2SessionMap[session_id] = session;
// When notifying observer, name of session is used instead of "name", because they may not be the
@@ -1470,11 +1581,11 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co
}
-bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg)
+bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const LLSD& voiceChannelInfo, bool has_offline_msg)
{
uuid_vec_t ids;
ids.push_back(other_participant_id);
- return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg);
+ return newSession(session_id, name, type, other_participant_id, ids, voiceChannelInfo, has_offline_msg);
}
bool LLIMModel::clearSession(const LLUUID& session_id)
@@ -1722,7 +1833,7 @@ EInstantMessage LLIMModel::getType(const LLUUID& session_id) const
return session->mType;
}
-LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const
+LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id) const
{
LLIMSession* session = findIMSession(session_id);
if (!session)
@@ -2023,7 +2134,8 @@ bool LLIMModel::sendStartSession(
const LLUUID& temp_session_id,
const LLUUID& other_participant_id,
const uuid_vec_t& ids,
- EInstantMessage dialog)
+ EInstantMessage dialog,
+ bool p2p_as_adhoc_call)
{
if ( dialog == IM_SESSION_GROUP_START )
{
@@ -2039,7 +2151,7 @@ bool LLIMModel::sendStartSession(
return true;
}
- else if ( dialog == IM_SESSION_CONFERENCE_START )
+ else if (dialog == IM_SESSION_CONFERENCE_START )
{
LLSD agents;
for (int i = 0; i < (S32) ids.size(); i++)
@@ -2054,8 +2166,8 @@ bool LLIMModel::sendStartSession(
std::string url = region->getCapability(
"ChatSessionRequest");
- LLCoros::instance().launch("startConfrenceCoro",
- boost::bind(&startConfrenceCoro, url,
+ LLCoros::instance().launch("startConferenceCoro",
+ boost::bind(&startConferenceCoro, url,
temp_session_id, gAgent.getID(), other_participant_id, agents));
}
else
@@ -2070,7 +2182,16 @@ bool LLIMModel::sendStartSession(
//we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id)
return true;
}
-
+ else if (p2p_as_adhoc_call && ((dialog == IM_SESSION_P2P_INVITE) || (dialog == IM_NOTHING_SPECIAL)))
+ {
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region)
+ {
+ std::string url = region->getCapability("ChatSessionRequest");
+ LLCoros::instance().launch("startP2PVoiceCoro", boost::bind(&startP2PVoiceCoro, url, temp_session_id, gAgent.getID(), other_participant_id));
+ }
+ return true;
+ }
return false;
}
@@ -2314,6 +2435,12 @@ void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::ES
}
break;
+ case LLVoiceChannel::STATE_NO_CHANNEL_INFO :
+ // This will happen in p2p calls using the adhoc
+ // infrastructure, which marks the channel as no channel info
+ // after the call is closed, which forces a dialogue.
+ return;
+
case LLVoiceChannel::STATE_HUNG_UP:
// this state is coming before session is changed
break;
@@ -2656,15 +2783,15 @@ bool is_voice_call_type(const std::string &value)
}
LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) :
-LLCallDialog(payload),
-mAvatarNameCacheConnection()
+ LLCallDialog(payload),
+ mAvatarNameCacheConnection()
{
}
void LLIncomingCallDialog::onLifetimeExpired()
{
- std::string session_handle = mPayload["session_handle"].asString();
- if (LLVoiceClient::getInstance()->isValidChannel(session_handle))
+ LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(mPayload["voice_channel_info"]);
+ if (call)
{
// restart notification's timer if call is still valid
mLifetimeTimer.start();
@@ -2676,7 +2803,7 @@ void LLIncomingCallDialog::onLifetimeExpired()
LLUUID session_id = mPayload["session_id"].asUUID();
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
- closeFloater();
+ LLIncomingCallDialog::onReject(this);
}
}
@@ -2855,16 +2982,17 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
{
if (type == IM_SESSION_P2P_INVITE)
{
+ if (session_name.empty())
+ {
+ session_name = payload["caller_name"].asString();
+ }
// create a normal IM session
session_id = gIMMgr->addP2PSession(
- session_name,
- caller_id,
- payload["session_handle"].asString(),
- payload["session_uri"].asString());
+ session_name, caller_id, payload["voice_channel_info"]);
if (voice)
{
- gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL);
+ gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL, payload["voice_channel_info"]);
}
else
{
@@ -2910,7 +3038,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
}
}
- gIMMgr->addSession(correct_session_name, type, session_id, true);
+ gIMMgr->addSession(correct_session_name, type, session_id, payload["voice_channel_info"]);
std::string url = gAgent.getRegion()->getCapability(
"ChatSessionRequest");
@@ -2918,8 +3046,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
if (voice)
{
LLCoros::instance().launch("chatterBoxInvitationCoro",
- boost::bind(&chatterBoxInvitationCoro, url,
- session_id, inv_type));
+ boost::bind(&chatterBoxInvitationCoro, url, session_id, inv_type, payload["voice_channel_info"]));
// send notification message to the corresponding chat
if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc")
@@ -2940,114 +3067,48 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload
{
if (type == IM_SESSION_P2P_INVITE)
{
- if(LLVoiceClient::getInstance())
- {
- std::string s = payload["session_handle"].asString();
- LLVoiceClient::getInstance()->declineInvite(s);
- }
- }
- else
- {
- std::string url = gAgent.getRegion()->getCapability(
- "ChatSessionRequest");
-
- LLSD data;
- data["method"] = "decline invitation";
- data["session-id"] = session_id;
-
- LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
- "Invitation declined",
- "Invitation decline failed.");
- }
- }
-
- gIMMgr->clearPendingAgentListUpdates(session_id);
- gIMMgr->clearPendingInvitation(session_id);
- }
-}
-
-bool inviteUserResponse(const LLSD& notification, const LLSD& response)
-{
- if (!gIMMgr)
- return false;
-
- const LLSD& payload = notification["payload"];
- LLUUID session_id = payload["session_id"].asUUID();
- EInstantMessage type = (EInstantMessage)payload["type"].asInteger();
- LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger();
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- switch(option)
- {
- case 0: // accept
- {
- if (type == IM_SESSION_P2P_INVITE)
+ // decline p2p voice, either via the vivox-style call mechanism
+ // or via the webrtc-style "decline p2p" mechanism.
+ LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(payload["voice_channel_info"]);
+ if (call)
{
- // create a normal IM session
- session_id = gIMMgr->addP2PSession(
- payload["session_name"].asString(),
- payload["caller_id"].asUUID(),
- payload["session_handle"].asString(),
- payload["session_uri"].asString());
-
- gIMMgr->startCall(session_id);
-
- gIMMgr->clearPendingAgentListUpdates(session_id);
- gIMMgr->clearPendingInvitation(session_id);
+ call->declineInvite();
}
else
{
- gIMMgr->addSession(
- payload["session_name"].asString(),
- type,
- session_id, true);
+ // webrtc-style decline.
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region)
+ {
+ std::string url = region->getCapability("ChatSessionRequest");
- std::string url = gAgent.getRegion()->getCapability(
- "ChatSessionRequest");
+ LLSD data;
+ data["method"] = "decline p2p voice";
+ data["session-id"] = session_id;
- LLCoros::instance().launch("chatterBoxInvitationCoro",
- boost::bind(&chatterBoxInvitationCoro, url,
- session_id, inv_type));
+ LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "P2P declined", "P2P decline failed.");
+ }
}
}
- break;
- case 2: // mute (also implies ignore, so this falls through to the "ignore" case below)
- {
- // mute the sender of this invite
- if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID()))
- {
- LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT);
- LLMuteList::getInstance()->add(mute);
- }
- }
- /* FALLTHROUGH */
-
- case 1: // decline
- {
- if (type == IM_SESSION_P2P_INVITE)
- {
- std::string s = payload["session_handle"].asString();
- LLVoiceClient::getInstance()->declineInvite(s);
- }
else
{
- std::string url = gAgent.getRegion()->getCapability(
- "ChatSessionRequest");
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region)
+ {
+ std::string url = region->getCapability("ChatSessionRequest");
+
+ LLSD data;
+ data["method"] = "decline invitation";
+ data["session-id"] = session_id;
- LLSD data;
- data["method"] = "decline invitation";
- data["session-id"] = session_id;
- LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
- "Invitation declined.",
- "Invitation decline failed.");
+ LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, "Invitation declined", "Invitation decline failed.");
+ }
}
}
gIMMgr->clearPendingAgentListUpdates(session_id);
gIMMgr->clearPendingInvitation(session_id);
- break;
}
-
- return false;
}
//
@@ -3121,7 +3182,7 @@ void LLIMMgr::addMessage(
{
fixed_session_name = av_name.getDisplayName();
}
- LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg);
+ LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, LLSD(), is_offline_msg);
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id);
if (session)
@@ -3283,22 +3344,11 @@ void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id)
}
LLUUID LLIMMgr::addP2PSession(const std::string& name,
- const LLUUID& other_participant_id,
- const std::string& voice_session_handle,
- const std::string& caller_uri)
+ const LLUUID& other_participant_id,
+ const LLSD& voice_channel_info)
{
- LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true);
-
- LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id);
- if (speaker_mgr)
- {
- LLVoiceChannelP2P* voice_channel = dynamic_cast<LLVoiceChannelP2P*>(speaker_mgr->getVoiceChannel());
- if (voice_channel)
- {
- voice_channel->setSessionHandle(voice_session_handle, caller_uri);
- }
- }
- return session_id;
+ LL_DEBUGS("Voice") << "Add p2p voice channel info: " << voice_channel_info << LL_ENDL;
+ return addSession(name, IM_NOTHING_SPECIAL, other_participant_id, voice_channel_info);
}
// This adds a session to the talk view. The name is the local name of
@@ -3308,11 +3358,12 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name,
LLUUID LLIMMgr::addSession(
const std::string& name,
EInstantMessage dialog,
- const LLUUID& other_participant_id, bool voice)
+ const LLUUID& other_participant_id,
+ const LLSD& voiceChannelInfo)
{
std::vector<LLUUID> ids;
ids.push_back(other_participant_id);
- LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice);
+ LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voiceChannelInfo);
return session_id;
}
@@ -3322,7 +3373,8 @@ LLUUID LLIMMgr::addSession(
const std::string& name,
EInstantMessage dialog,
const LLUUID& other_participant_id,
- const std::vector<LLUUID>& ids, bool voice,
+ const std::vector<LLUUID>& ids,
+ const LLSD& voiceChannelInfo,
const LLUUID& floater_id)
{
if (ids.empty())
@@ -3351,28 +3403,32 @@ LLUUID LLIMMgr::addSession(
im_floater->reloadMessages();
}
}
+ LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id);
- bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL);
+ bool new_session = (session == NULL);
//works only for outgoing ad-hoc sessions
- if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size())
+ if (new_session &&
+ ((IM_NOTHING_SPECIAL == dialog) || (IM_SESSION_P2P_INVITE == dialog) || (IM_SESSION_CONFERENCE_START == dialog)) &&
+ ids.size())
{
- LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids);
- if (ad_hoc_found)
+ session = LLIMModel::getInstance()->findAdHocIMSession(ids);
+ if (session)
{
new_session = false;
- session_id = ad_hoc_found->mSessionID;
+ session_id = session->mSessionID;
}
}
//Notify observers that a session was added
if (new_session)
{
- LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice);
+ LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voiceChannelInfo);
}
//Notifies observers that the session was already added
else
{
+ session->initVoiceChannel(voiceChannelInfo);
std::string session_name = LLIMModel::getInstance()->getName(session_id);
LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id);
}
@@ -3429,9 +3485,15 @@ void LLIMMgr::inviteToSession(
const std::string& caller_name,
EInstantMessage type,
EInvitationType inv_type,
- const std::string& session_handle,
- const std::string& session_uri)
+ const LLSD& voice_channel_info)
{
+
+ if (caller_id == gAgentID)
+ {
+ // ignore invites from ourself.
+ return;
+ }
+
std::string notify_box_type;
// voice invite question is different from default only for group call (EXT-7118)
std::string question_type = "VoiceInviteQuestionDefault";
@@ -3472,11 +3534,11 @@ void LLIMMgr::inviteToSession(
payload["caller_name"] = caller_name;
payload["type"] = type;
payload["inv_type"] = inv_type;
- payload["session_handle"] = session_handle;
- payload["session_uri"] = session_uri;
payload["notify_box_type"] = notify_box_type;
payload["question_type"] = question_type;
+ LL_WARNS("Voice") << "INVITE PAYLOAD: " << payload << LL_ENDL;
+
//ignore invites from muted residents
if (!is_linden)
{
@@ -3501,7 +3563,6 @@ void LLIMMgr::inviteToSession(
LLIncomingCallDialog::processCallResponse(0, payload);
return;
}
-
if (voice_invite)
{
bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup"));
@@ -3525,7 +3586,7 @@ void LLIMMgr::inviteToSession(
fixed_session_name = av_name.getDisplayName();
}
}
- LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false);
+ LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, LLSD(), false);
}
LLSD args;
@@ -3540,6 +3601,9 @@ void LLIMMgr::inviteToSession(
if ( !mPendingInvitations.has(session_id.asString()) )
{
+ // we're throwing up a dialogue, so we're using the voice channel passed to us,
+ // save it in the payload.
+ payload["voice_channel_info"] = voice_channel_info;
if (caller_name.empty())
{
LLAvatarNameCache::get(caller_id,
@@ -3593,6 +3657,40 @@ void LLIMMgr::clearPendingInvitation(const LLUUID& session_id)
void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body)
{
+ if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap())
+ {
+ LLSD::map_const_iterator update_it;
+ for (update_it = body["agent_updates"].beginMap(); update_it != body["agent_updates"].endMap(); ++update_it)
+ {
+ LLUUID agent_id = LLUUID(update_it->first);
+ LLSD agent_data = update_it->second;
+ if (agent_data.has("transition") && agent_data["transition"].asString() == "LEAVE")
+ {
+ // ignore actual leaves as those will be handled separately.
+ continue;
+ }
+
+ if (agent_id != gAgentID && agent_data.isMap() && agent_data.has("info") && agent_data["info"].isMap())
+ {
+ // Is one of the participants leaving a P2P Chat?
+ if (agent_data["info"].has("can_voice_chat") && !agent_data["info"]["can_voice_chat"].asBoolean())
+ {
+ LLVoiceChannelGroup *channelp = dynamic_cast < LLVoiceChannelGroup*>(LLVoiceChannel::getChannelByID(session_id));
+ if (channelp && channelp->isP2P())
+ {
+ // it's an adhoc-style P2P channel, and the peer has declined voice. notify the user
+ // and shut down the voice channel.
+ LLSD notifyArgs = LLSD::emptyMap();
+ notifyArgs["VOICE_CHANNEL_NAME"] = channelp->getSessionName();
+ LLNotificationsUtil::add("P2PCallDeclined", notifyArgs);
+ endCall(session_id);
+ break;
+ }
+ }
+ }
+ }
+ }
+
LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id);
if ( im_floater )
{
@@ -3750,11 +3848,14 @@ void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer)
mSessionObservers.remove(observer);
}
-bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction)
+bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction, const LLSD& voice_channel_info)
{
LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id);
if (!voice_channel) return false;
-
+ if (voice_channel_info.isDefined() && voice_channel_info.isMap() && voice_channel_info.size() > 0)
+ {
+ voice_channel->setChannelInfo(voice_channel_info);
+ }
voice_channel->setCallDirection(direction);
voice_channel->activate();
return true;
@@ -4062,6 +4163,15 @@ public:
{
im_mgr->processSessionUpdate(input["body"]["info"]);
}
+ if (input["body"]["info"].has("voice_channel_info"))
+ {
+ LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id);
+ if (session)
+ {
+ session->initVoiceChannel(input["body"]["info"]["voice_channel_info"]);
+ session->mVoiceChannel->activate();
+ }
+ }
}
};
@@ -4146,7 +4256,7 @@ public:
{
LLCoros::instance().launch("chatterBoxInvitationCoro",
boost::bind(&chatterBoxInvitationCoro, url,
- session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE));
+ session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE, LLSD()));
}
} //end if invitation has instant message
else if ( input["body"].has("voice") )
@@ -4157,13 +4267,16 @@ public:
return;
}
+ bool session_type_p2p = input["body"]["voice"].get("invitation_type").asInteger() == EMultiAgentChatSessionType::P2P_CHAT_SESSION;
+ LL_DEBUGS("Voice") << "Received voice information from the server: " << input["body"]<< LL_ENDL;
gIMMgr->inviteToSession(
input["body"]["session_id"].asUUID(),
input["body"]["session_name"].asString(),
input["body"]["from_id"].asUUID(),
input["body"]["from_name"].asString(),
- IM_SESSION_INVITE,
- LLIMMgr::INVITATION_TYPE_VOICE);
+ session_type_p2p ? IM_SESSION_P2P_INVITE : IM_SESSION_INVITE,
+ LLIMMgr::INVITATION_TYPE_VOICE,
+ input["body"]["voice"]);
}
else if ( input["body"].has("immediate") )
{
diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h
index 7b76cc8c68..99b19c9fa9 100644
--- a/indra/newview/llimview.h
+++ b/indra/newview/llimview.h
@@ -80,9 +80,11 @@ public:
} SType;
LLIMSession(const LLUUID& session_id, const std::string& name,
- const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg);
+ const EInstantMessage& type, const LLUUID& other_participant_id, const LLSD& voiceChannelInfo, const uuid_vec_t& ids, bool has_offline_msg);
virtual ~LLIMSession();
+ void initVoiceChannel(const LLSD &voiceChannelInfo = LLSD());
+
void sessionInitReplyReceived(const LLUUID& new_session_id);
void addMessagesFromHistoryCache(const std::list<LLSD>& history); // From local file
void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp); // From chat server
@@ -141,6 +143,7 @@ public:
LLVoiceChannel* mVoiceChannel;
LLIMSpeakerMgr* mSpeakers;
+ bool mP2PAsAdhocCall;
bool mSessionInitialized;
@@ -199,10 +202,10 @@ public:
* @param name session name should not be empty, will return false if empty
*/
bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id,
- const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false);
+ const uuid_vec_t& ids, const LLSD& voiceChannelInfo = LLSD(), bool has_offline_msg = false);
- bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type,
- const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false);
+ bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID &other_participant_id,
+ const LLSD &voiceChannelInfo = LLSD(), bool has_offline_msg = false);
/**
* Remove all session data associated with a session specified by session_id
@@ -296,7 +299,7 @@ public:
static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id);
static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id,
- const uuid_vec_t& ids, EInstantMessage dialog);
+ const uuid_vec_t& ids, EInstantMessage dialog, bool p2p_as_adhoc_call);
static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing);
static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id,
const LLUUID& other_participant_id, EInstantMessage dialog);
@@ -379,7 +382,8 @@ public:
// session.
LLUUID addSession(const std::string& name,
EInstantMessage dialog,
- const LLUUID& other_participant_id, bool voice = false);
+ const LLUUID& other_participant_id,
+ const LLSD& voiceChannelInfo = LLSD());
// Adds a session using a specific group of starting agents
// the dialog type is assumed correct. Returns the uuid of the session.
@@ -387,7 +391,8 @@ public:
LLUUID addSession(const std::string& name,
EInstantMessage dialog,
const LLUUID& other_participant_id,
- const std::vector<LLUUID>& ids, bool voice = false,
+ const std::vector<LLUUID> &ids,
+ const LLSD& voiceChannelInfo = LLSD(),
const LLUUID& floater_id = LLUUID::null);
/**
@@ -397,10 +402,7 @@ public:
* @param caller_uri - sip URI of caller. It should be always be passed into the method to avoid
* incorrect working of LLVoiceChannel instances. See EXT-2985.
*/
- LLUUID addP2PSession(const std::string& name,
- const LLUUID& other_participant_id,
- const std::string& voice_session_handle,
- const std::string& caller_uri);
+ LLUUID addP2PSession(const std::string &name, const LLUUID &other_participant_id, const LLSD &voice_call_info);
/**
* Leave the session with session id. Send leave session notification
@@ -416,8 +418,8 @@ public:
const std::string& caller_name,
EInstantMessage type,
EInvitationType inv_type,
- const std::string& session_handle = LLStringUtil::null,
- const std::string& session_uri = LLStringUtil::null);
+ const LLSD &voice_channel_info = LLSD()
+ );
void processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type);
void processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type);
@@ -465,7 +467,7 @@ public:
* Start call in a session
* @return false if voice channel doesn't exist
**/
- bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL);
+ bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL, const LLSD& voice_channel_info = LLSD());
/**
* End call in a session
diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp
index 6ab5e05b7d..0e2b3cb409 100644
--- a/indra/newview/lllocalbitmaps.cpp
+++ b/indra/newview/lllocalbitmaps.cpp
@@ -215,7 +215,6 @@ bool LLLocalBitmap::updateSelf(EUpdateType optional_firstupdate)
("file://"+mFilename, FTT_LOCAL_FILE, mWorldID, LL_LOCAL_USE_MIPMAPS);
texture->createGLTexture(LL_LOCAL_DISCARD_LEVEL, raw_image);
- texture->setCachedRawImage(LL_LOCAL_DISCARD_LEVEL, raw_image);
texture->ref();
gTextureList.addImage(texture, TEX_LIST_STANDARD);
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index b9430a6165..b850ade0bb 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -533,7 +533,7 @@ public:
// Inherited from LLCore::HttpHandler
virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
- LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material);
+ static LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material);
private:
LLHandle<LLWholeModelFeeObserver> mFeeObserverHandle;
diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp
index 7ecd7cdff9..b1a48db0ee 100644
--- a/indra/newview/llmodelpreview.cpp
+++ b/indra/newview/llmodelpreview.cpp
@@ -543,6 +543,32 @@ void LLModelPreview::rebuildUploadData()
}
instance.mTransform = mat;
mUploadData.push_back(instance);
+
+ // if uploading textures, make sure textures are present
+ if (mFMP->childGetValue("upload_textures").asBoolean()) // too early to cheack if still loading
+ {
+ for (auto& mat_pair : instance.mMaterial)
+ {
+ LLImportMaterial& material = mat_pair.second;
+
+ if (material.mDiffuseMapFilename.size())
+ {
+ LLViewerFetchedTexture* texture = LLMeshUploadThread::FindViewerTexture(material);
+ if (texture && texture->isMissingAsset())
+ {
+ // in case user provided a missing file later
+ texture->setIsMissingAsset(false);
+ texture->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, this, NULL, false);
+ texture->forceToSaveRawImage(0, F32_MAX);
+ texture->updateFetch();
+ if (mModelLoader)
+ {
+ mModelLoader->mNumOfFetchingTextures++;
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index 1410232a0f..fd968d9027 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -296,8 +296,6 @@ void LLNetMap::draw()
gGL.color4f(1.f, 0.5f, 0.5f, 1.f);
}
-
-
// Draw using texture.
gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture());
gGL.begin(LLRender::QUADS);
@@ -311,24 +309,6 @@ void LLNetMap::draw()
gGL.vertex2f(right, top);
gGL.end();
- // Draw water
- gGL.flush();
- {
- if (regionp->getLand().getWaterTexture())
- {
- gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture());
- gGL.begin(LLRender::QUADS);
- gGL.texCoord2f(0.f, 1.f);
- gGL.vertex2f(left, top);
- gGL.texCoord2f(0.f, 0.f);
- gGL.vertex2f(left, bottom);
- gGL.texCoord2f(1.f, 0.f);
- gGL.vertex2f(right, bottom);
- gGL.texCoord2f(1.f, 1.f);
- gGL.vertex2f(right, top);
- gGL.end();
- }
- }
gGL.flush();
}
diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp
index db9f1078fe..519f157973 100644
--- a/indra/newview/llpanelgroup.cpp
+++ b/indra/newview/llpanelgroup.cpp
@@ -292,7 +292,7 @@ void LLPanelGroup::changed(LLGroupChange gc)
}
// virtual
-void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, bool proximal)
+void LLPanelGroup::onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
{
if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL)
{
diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h
index f85408698a..ede8118720 100644
--- a/indra/newview/llpanelgroup.h
+++ b/indra/newview/llpanelgroup.h
@@ -62,7 +62,7 @@ public:
// Implements LLVoiceClientStatusObserver::onChange() to enable the call
// button when voice is available
- /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
+ /*virtual*/ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal);
void showNotice(const std::string& subject,
const std::string& message,
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp
index 42cf06424e..7f64cfca08 100644
--- a/indra/newview/llpanelpeople.cpp
+++ b/indra/newview/llpanelpeople.cpp
@@ -738,7 +738,7 @@ bool LLPanelPeople::postBuild()
}
// virtual
-void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal)
+void LLPanelPeople::onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
{
if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL)
{
diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h
index 4edba2543e..768ba1ef49 100644
--- a/indra/newview/llpanelpeople.h
+++ b/indra/newview/llpanelpeople.h
@@ -55,7 +55,7 @@ public:
bool notifyChildren(const LLSD& info) override;
// Implements LLVoiceClientStatusObserver::onChange() to enable call buttons
// when voice is available
- void onChange(EStatusType status, const std::string &channelURI, bool proximal) override;
+ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) override;
// internals
class Updater;
diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp
index 19e2d5de99..fcf0757073 100644
--- a/indra/newview/llpanelprofile.cpp
+++ b/indra/newview/llpanelprofile.cpp
@@ -1020,7 +1020,7 @@ void LLPanelProfileSecondLife::fillCommonData(const LLAvatarData* avatar_data)
setDescriptionText(avatar_data->about_text);
- mSecondLifePic->setValue(avatar_data->image_id);
+ mSecondLifePic->setValue(avatar_data->image_id);
if (getSelfProfile())
{
@@ -1168,10 +1168,10 @@ void LLPanelProfileSecondLife::fillAgeData(const LLAvatarData* avatar_data)
}
else
{
- std::string register_date = getString("age_format");
- LLSD args_age;
+ std::string register_date = getString("age_format");
+ LLSD args_age;
args_age["[AGE]"] = LLDateUtil::ageFromDate(avatar_data->born_on, LLDate::now());
- LLStringUtil::format(register_date, args_age);
+ LLStringUtil::format(register_date, args_age);
userAgeCtrl->setValue(register_date);
}
@@ -1218,7 +1218,7 @@ void LLPanelProfileSecondLife::changed(U32 mask)
}
// virtual, called by LLVoiceClient
-void LLPanelProfileSecondLife::onChange(EStatusType status, const std::string &channelURI, bool proximal)
+void LLPanelProfileSecondLife::onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
{
if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL)
{
@@ -1614,12 +1614,12 @@ void LLPanelProfileSecondLife::onShowInSearchCallback()
if (value == mAllowPublish)
return;
- mAllowPublish = value;
+ mAllowPublish = value;
saveAgentUserInfoCoro("allow_publish", value);
-}
+ }
void LLPanelProfileSecondLife::onHideAgeCallback()
-{
+ {
bool value = mHideAgeCombo->getValue().asInteger();
if (value == mHideAge)
return;
@@ -1768,35 +1768,35 @@ void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id)
if (mSecondLifePic->getImageAssetId() == id)
return;
- std::function<void(bool)> callback = [id](bool result)
- {
- if (result)
+ std::function<void(bool)> callback = [id](bool result)
{
- LLAvatarIconIDCache::getInstance()->add(gAgentID, id);
+ if (result)
+ {
+ LLAvatarIconIDCache::getInstance()->add(gAgentID, id);
// Should trigger callbacks in icon controls (or request Legacy)
- LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID);
- }
- };
+ LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID);
+ }
+ };
if (!saveAgentUserInfoCoro("sl_image_id", id, callback))
return;
mSecondLifePic->setValue(id);
- LLFloater *floater = mFloaterProfileTextureHandle.get();
- if (floater)
- {
- LLFloaterProfileTexture * texture_view = dynamic_cast<LLFloaterProfileTexture*>(floater);
- if (id == LLUUID::null)
- {
- texture_view->resetAsset();
- }
- else
+ LLFloater *floater = mFloaterProfileTextureHandle.get();
+ if (floater)
{
+ LLFloaterProfileTexture * texture_view = dynamic_cast<LLFloaterProfileTexture*>(floater);
+ if (id == LLUUID::null)
+ {
+ texture_view->resetAsset();
+ }
+ else
+ {
texture_view->loadAsset(id);
+ }
}
}
-}
//////////////////////////////////////////////////////////////////////////
// LLPanelProfileWeb
diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h
index 7fa9690530..c207a4162a 100644
--- a/indra/newview/llpanelprofile.h
+++ b/indra/newview/llpanelprofile.h
@@ -91,7 +91,7 @@ public:
// Implements LLVoiceClientStatusObserver::onChange() to enable the call
// button when voice is available
- void onChange(EStatusType status, const std::string &channelURI, bool proximal) override;
+ void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) override;
void setAvatarId(const LLUUID& avatar_id) override;
diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp
index 9c3891b438..60877494e7 100644
--- a/indra/newview/llpanelvoicedevicesettings.cpp
+++ b/indra/newview/llpanelvoicedevicesettings.cpp
@@ -256,43 +256,46 @@ void LLPanelVoiceDeviceSettings::refresh()
if(mCtrlInputDevices)
{
- mCtrlInputDevices->removeall();
- mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM);
-
- for(device=LLVoiceClient::getInstance()->getCaptureDevices().begin();
- device != LLVoiceClient::getInstance()->getCaptureDevices().end();
- device++)
+ LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getCaptureDevices();
+ if (devices.size() > 0) // if zero, we've not received our devices yet
{
- mCtrlInputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM);
- }
+ mCtrlInputDevices->removeall();
+ mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM);
+ for (auto& device : devices)
+ {
+ mCtrlInputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM);
+ }
- // Fix invalid input audio device preference.
- if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, true))
- {
- mCtrlInputDevices->setValue(DEFAULT_DEVICE);
- gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE);
- mInputDevice = DEFAULT_DEVICE;
+ // Fix invalid input audio device preference.
+ if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, true))
+ {
+ mCtrlInputDevices->setValue(DEFAULT_DEVICE);
+ gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE);
+ mInputDevice = DEFAULT_DEVICE;
+ }
}
}
if(mCtrlOutputDevices)
{
- mCtrlOutputDevices->removeall();
- mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM);
-
- for(device = LLVoiceClient::getInstance()->getRenderDevices().begin();
- device != LLVoiceClient::getInstance()->getRenderDevices().end();
- device++)
+ LLVoiceDeviceList devices = LLVoiceClient::getInstance()->getRenderDevices();
+ if (devices.size() > 0) // if zero, we've not received our devices yet
{
- mCtrlOutputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM);
- }
+ mCtrlOutputDevices->removeall();
+ mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM);
- // Fix invalid output audio device preference.
- if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, true))
- {
- mCtrlOutputDevices->setValue(DEFAULT_DEVICE);
- gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE);
- mOutputDevice = DEFAULT_DEVICE;
+ for (auto& device : devices)
+ {
+ mCtrlOutputDevices->add(getLocalizedDeviceName(device.display_name), device.full_name, ADD_BOTTOM);
+ }
+
+ // Fix invalid output audio device preference.
+ if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, true))
+ {
+ mCtrlOutputDevices->setValue(DEFAULT_DEVICE);
+ gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE);
+ mOutputDevice = DEFAULT_DEVICE;
+ }
}
}
}
@@ -336,8 +339,11 @@ void LLPanelVoiceDeviceSettings::onCommitInputDevice()
if(LLVoiceClient::getInstance())
{
mInputDevice = mCtrlInputDevices->getValue().asString();
- LLVoiceClient::getInstance()->setRenderDevice(mInputDevice);
+ LLVoiceClient::getInstance()->setCaptureDevice(mInputDevice);
}
+ // the preferences floater stuff is a mess, hence apply will never
+ // be called when 'ok' is pressed, so just force it for now.
+ apply();
}
void LLPanelVoiceDeviceSettings::onCommitOutputDevice()
@@ -348,6 +354,9 @@ void LLPanelVoiceDeviceSettings::onCommitOutputDevice()
mOutputDevice = mCtrlOutputDevices->getValue().asString();
LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice);
}
+ // the preferences floater stuff is a mess, hence apply will never
+ // be called when 'ok' is pressed, so just force it for now.
+ apply();
}
void LLPanelVoiceDeviceSettings::onOutputDevicesClicked()
diff --git a/indra/newview/llpanelvolume.cpp b/indra/newview/llpanelvolume.cpp
index 16c38bf1f0..4e096ecc95 100644
--- a/indra/newview/llpanelvolume.cpp
+++ b/indra/newview/llpanelvolume.cpp
@@ -151,6 +151,7 @@ bool LLPanelVolume::postBuild()
{
childSetCommitCallback("Reflection Probe", onCommitIsReflectionProbe, this);
childSetCommitCallback("Probe Update Type", onCommitProbe, this);
+ childSetCommitCallback("Probe Dynamic", onCommitProbe, this);
childSetCommitCallback("Probe Volume Type", onCommitProbe, this);
childSetCommitCallback("Probe Ambiance", onCommitProbe, this);
childSetCommitCallback("Probe Near Clip", onCommitProbe, this);
@@ -412,6 +413,7 @@ void LLPanelVolume::getState( )
getChild<LLSpinCtrl>("Probe Ambiance", true)->clear();
getChild<LLSpinCtrl>("Probe Near Clip", true)->clear();
getChild<LLComboBox>("Probe Update Type", true)->clear();
+ getChild<LLUICtrl>("Probe Dynamic")->setValue(false);
}
else
{
@@ -446,6 +448,7 @@ void LLPanelVolume::getState( )
getChild<LLSpinCtrl>("Probe Ambiance", true)->setValue(volobjp->getReflectionProbeAmbiance());
getChild<LLSpinCtrl>("Probe Near Clip", true)->setValue(volobjp->getReflectionProbeNearClip());
getChild<LLComboBox>("Probe Update Type", true)->setValue(update_type);
+ getChild<LLUICtrl>("Probe Dynamic")->setValue(volobjp->getReflectionProbeIsDynamic());
}
// Animated Mesh
@@ -733,6 +736,7 @@ void LLPanelVolume::clearCtrls()
getChildView("Reflection Probe")->setEnabled(false);;
getChildView("Probe Volume Type")->setEnabled(false);
getChildView("Probe Update Type")->setEnabled(false);
+ getChildView("Probe Dynamic")->setEnabled(false);
getChildView("Probe Ambiance")->setEnabled(false);
getChildView("Probe Near Clip")->setEnabled(false);
getChildView("Animated Mesh Checkbox Ctrl")->setEnabled(false);
@@ -1428,15 +1432,26 @@ void LLPanelVolume::onCommitProbe(LLUICtrl* ctrl, void* userdata)
volobjp->setReflectionProbeAmbiance((F32)self->getChild<LLUICtrl>("Probe Ambiance")->getValue().asReal());
volobjp->setReflectionProbeNearClip((F32)self->getChild<LLUICtrl>("Probe Near Clip")->getValue().asReal());
- std::string update_type = self->getChild<LLUICtrl>("Probe Update Type")->getValue().asString();
+ bool mirrors_enabled = LLPipeline::RenderMirrors;
+ bool is_mirror = false;
- bool is_mirror = update_type.find("Mirror") != std::string::npos;
+ if (mirrors_enabled)
+ {
+ std::string update_type = self->getChild<LLUICtrl>("Probe Update Type")->getValue().asString();
- self->getChildView("Probe Volume Type")->setEnabled(!is_mirror);
+ is_mirror = update_type.find("Mirror") != std::string::npos;
- volobjp->setReflectionProbeIsDynamic(update_type.find("Dynamic") != std::string::npos);
- volobjp->setReflectionProbeIsMirror(is_mirror);
+ volobjp->setReflectionProbeIsDynamic(update_type.find("Dynamic") != std::string::npos);
+ volobjp->setReflectionProbeIsMirror(is_mirror);
+ }
+ else
+ {
+ is_mirror = volobjp->getReflectionProbeIsMirror();
+ bool is_dynamic = self->getChild<LLUICtrl>("Probe Dynamic")->getValue().asBoolean();
+ volobjp->setReflectionProbeIsDynamic(is_dynamic);
+ }
+ self->getChildView("Probe Volume Type")->setEnabled(!is_mirror);
self->getChildView("Probe Ambiance")->setEnabled(!is_mirror);
self->getChildView("Probe Near Clip")->setEnabled(!is_mirror);
diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp
index 79c07d8c09..31fd6f3f07 100644
--- a/indra/newview/llreflectionmap.cpp
+++ b/indra/newview/llreflectionmap.cpp
@@ -251,32 +251,27 @@ bool LLReflectionMap::getBox(LLMatrix4& box)
if (mViewerObject)
{
LLVolume* volume = mViewerObject->getVolume();
- if (volume)
+ if (volume && mViewerObject->getReflectionProbeIsBox())
{
- LLVOVolume* vobjp = (LLVOVolume*)mViewerObject;
-
- if (vobjp->getReflectionProbeIsBox())
+ glh::matrix4f mv(gGLModelView);
+ glh::matrix4f scale;
+ LLVector3 s = mViewerObject->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
+ mRadius = s.magVec();
+ scale.set_scale(glh::vec3f(s.mV));
+ if (mViewerObject->mDrawable != nullptr)
{
- glh::matrix4f mv(gGLModelView);
- glh::matrix4f scale;
- LLVector3 s = vobjp->getScale().scaledVec(LLVector3(0.5f, 0.5f, 0.5f));
- mRadius = s.magVec();
- scale.set_scale(glh::vec3f(s.mV));
- if (vobjp->mDrawable != nullptr)
- {
- // object to agent space (no scale)
- glh::matrix4f rm((F32*)vobjp->mDrawable->getWorldMatrix().mMatrix);
+ // object to agent space (no scale)
+ glh::matrix4f rm((F32*)mViewerObject->mDrawable->getWorldMatrix().mMatrix);
- // construct object to camera space (with scale)
- mv = mv * rm * scale;
+ // construct object to camera space (with scale)
+ mv = mv * rm * scale;
- // inverse is camera space to object unit cube
- mv = mv.inverse();
+ // inverse is camera space to object unit cube
+ mv = mv.inverse();
- box = LLMatrix4(mv.m);
+ box = LLMatrix4(mv.m);
- return true;
- }
+ return true;
}
}
}
diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp
index 0d1d2a73ed..81002139be 100644
--- a/indra/newview/llspeakers.cpp
+++ b/indra/newview/llspeakers.cpp
@@ -288,6 +288,10 @@ LLSpeakerMgr::~LLSpeakerMgr()
LLPointer<LLSpeaker> LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type)
{
+ if (!mVoiceChannel)
+ {
+ return NULL;
+ }
LLUUID session_id = getSessionID();
if (id.isNull() || (id == session_id))
{
@@ -490,7 +494,7 @@ void LLSpeakerMgr::updateSpeakerList()
(LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL));
}
}
- else
+ else if (mVoiceChannel)
{
// If not, check if the list is empty, except if it's Nearby Chat (session_id NULL).
LLUUID session_id = getSessionID();
@@ -618,7 +622,7 @@ void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, bool include_tex
const LLUUID LLSpeakerMgr::getSessionID()
{
- return mVoiceChannel->getSessionID();
+ return mVoiceChannel ? mVoiceChannel->getSessionID() : LLUUID();
}
bool LLSpeakerMgr::isSpeakerToBeRemoved(const LLUUID& speaker_id)
@@ -816,7 +820,7 @@ void LLIMSpeakerMgr::updateSpeakers(const LLSD& update)
void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id)
{
LLPointer<LLSpeaker> speakerp = findSpeaker(speaker_id);
- if (!speakerp) return;
+ if (!speakerp || !mVoiceChannel) return;
std::string url = gAgent.getRegionCapability("ChatSessionRequest");
LLSD data;
@@ -835,7 +839,7 @@ void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id)
void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute)
{
LLPointer<LLSpeaker> speakerp = findSpeaker(avatar_id);
- if (!speakerp) return;
+ if (!speakerp || !mVoiceChannel) return;
// *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for
// text chat via moderation (LLSpeaker::mModeratorMutedText == true)
diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h
index 4cf7b3aba7..ad2461f60f 100644
--- a/indra/newview/llspeakers.h
+++ b/indra/newview/llspeakers.h
@@ -241,6 +241,7 @@ public:
typedef std::vector<LLPointer<LLSpeaker> > speaker_list_t;
void getSpeakerList(speaker_list_t* speaker_list, bool include_text);
LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; }
+ void setVoiceChannel(LLVoiceChannel *voiceChannel) { mVoiceChannel = voiceChannel; }
const LLUUID getSessionID();
bool isSpeakerToBeRemoved(const LLUUID& speaker_id);
diff --git a/indra/newview/llsurface.cpp b/indra/newview/llsurface.cpp
index e6bced5c92..1826885069 100644
--- a/indra/newview/llsurface.cpp
+++ b/indra/newview/llsurface.cpp
@@ -54,6 +54,7 @@
#include "llglheaders.h"
#include "lldrawpoolterrain.h"
#include "lldrawable.h"
+#include "llworldmipmap.h"
extern LLPipeline gPipeline;
extern bool gShiftFrame;
@@ -74,7 +75,6 @@ LLSurface::LLSurface(U32 type, LLViewerRegion *regionp) :
mDetailTextureScale(0.f),
mOriginGlobal(0.0, 0.0, 0.0),
mSTexturep(NULL),
- mWaterTexturep(NULL),
mGridsPerPatchEdge(0),
mMetersPerGrid(1.0f),
mMetersPerEdge(1.0f),
@@ -129,14 +129,7 @@ LLSurface::~LLSurface()
{
gPipeline.removePool(poolp);
// Don't enable this until we blitz the draw pool for it as well. -- djs
- if (mSTexturep)
- {
- mSTexturep = NULL;
- }
- if (mWaterTexturep)
- {
- mWaterTexturep = NULL;
- }
+ mSTexturep = NULL;
}
else
{
@@ -216,62 +209,17 @@ LLViewerTexture* LLSurface::getSTexture()
return mSTexturep;
}
-LLViewerTexture* LLSurface::getWaterTexture()
-{
- if (mWaterTexturep.notNull() && !mWaterTexturep->hasGLTexture())
- {
- createWaterTexture();
- }
- return mWaterTexturep;
-}
-
void LLSurface::createSTexture()
{
if (!mSTexturep)
{
- // Fill with dummy gray data.
- // GL NOT ACTIVE HERE
- LLPointer<LLImageRaw> raw = new LLImageRaw(sTextureSize, sTextureSize, 3);
- U8 *default_texture = raw->getData();
- for (S32 i = 0; i < sTextureSize; i++)
- {
- for (S32 j = 0; j < sTextureSize; j++)
- {
- *(default_texture + (i*sTextureSize + j)*3) = 128;
- *(default_texture + (i*sTextureSize + j)*3 + 1) = 128;
- *(default_texture + (i*sTextureSize + j)*3 + 2) = 128;
- }
- }
+ U64 handle = mRegionp->getHandle();
- mSTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false);
- mSTexturep->dontDiscard();
- gGL.getTexUnit(0)->bind(mSTexturep);
- mSTexturep->setAddressMode(LLTexUnit::TAM_CLAMP);
- }
-}
+ U32 grid_x, grid_y;
-void LLSurface::createWaterTexture()
-{
- if (!mWaterTexturep)
- {
- // Create the water texture
- LLPointer<LLImageRaw> raw = new LLImageRaw(sTextureSize/2, sTextureSize/2, 4);
- U8 *default_texture = raw->getData();
- for (S32 i = 0; i < sTextureSize/2; i++)
- {
- for (S32 j = 0; j < sTextureSize/2; j++)
- {
- *(default_texture + (i*sTextureSize/2 + j)*4) = MAX_WATER_COLOR.mV[0];
- *(default_texture + (i*sTextureSize/2 + j)*4 + 1) = MAX_WATER_COLOR.mV[1];
- *(default_texture + (i*sTextureSize/2 + j)*4 + 2) = MAX_WATER_COLOR.mV[2];
- *(default_texture + (i*sTextureSize/2 + j)*4 + 3) = MAX_WATER_COLOR.mV[3];
- }
- }
+ grid_from_region_handle(handle, &grid_x, &grid_y);
- mWaterTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false);
- mWaterTexturep->dontDiscard();
- gGL.getTexUnit(0)->bind(mWaterTexturep);
- mWaterTexturep->setAddressMode(LLTexUnit::TAM_CLAMP);
+ mSTexturep = LLWorldMipmap::loadObjectsTile(grid_x, grid_y, 1);
}
}
@@ -285,11 +233,10 @@ void LLSurface::initTextures()
///////////////////////
//
- // Water texture
+ // Water object
//
if (gSavedSettings.getBOOL("RenderWater") )
{
- createWaterTexture();
mWaterObjp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, mRegionp);
gPipeline.createObject(mWaterObjp);
LLVector3d water_pos_global = from_region_handle(mRegionp->getHandle());
@@ -683,11 +630,8 @@ bool LLSurface::idleUpdate(F32 max_update_time)
}
}
- if (did_update)
- {
- // some patches changed, update region reflection probes
- mRegionp->updateReflectionProbes();
- }
+ // some patches changed, update region reflection probes
+ mRegionp->updateReflectionProbes(did_update);
return did_update;
}
@@ -1221,98 +1165,3 @@ F32 LLSurface::getWaterHeight() const
}
}
-
-bool LLSurface::generateWaterTexture(const F32 x, const F32 y,
- const F32 width, const F32 height)
-{
- LL_PROFILE_ZONE_SCOPED
- if (!getWaterTexture())
- {
- return false;
- }
-
- S32 tex_width = mWaterTexturep->getWidth();
- S32 tex_height = mWaterTexturep->getHeight();
- S32 tex_comps = mWaterTexturep->getComponents();
- S32 tex_stride = tex_width * tex_comps;
- LLPointer<LLImageRaw> raw = new LLImageRaw(tex_width, tex_height, tex_comps);
- U8 *rawp = raw->getData();
-
- F32 scale = 256.f * getMetersPerGrid() / (F32)tex_width;
- F32 scale_inv = 1.f / scale;
-
- S32 x_begin, y_begin, x_end, y_end;
-
- x_begin = ll_round(x * scale_inv);
- y_begin = ll_round(y * scale_inv);
- x_end = ll_round((x + width) * scale_inv);
- y_end = ll_round((y + width) * scale_inv);
-
- if (x_end > tex_width)
- {
- x_end = tex_width;
- }
- if (y_end > tex_width)
- {
- y_end = tex_width;
- }
-
- // OK, for now, just have the composition value equal the height at the point.
- LLVector3 location;
- LLColor4U coloru;
-
- const F32 WATER_HEIGHT = getWaterHeight();
-
- S32 i, j, offset;
- for (j = y_begin; j < y_end; j++)
- {
- for (i = x_begin; i < x_end; i++)
- {
- //F32 nv[2];
- //nv[0] = i/256.f;
- //nv[1] = j/256.f;
- // const S32 modulation = noise2(nv)*40;
- offset = j*tex_stride + i*tex_comps;
- location.mV[VX] = i*scale;
- location.mV[VY] = j*scale;
-
- // Sample multiple points
- const F32 height = resolveHeightRegion(location);
-
- if (height > WATER_HEIGHT)
- {
- // Above water...
- coloru = MAX_WATER_COLOR;
- coloru.mV[3] = ABOVE_WATERLINE_ALPHA;
- *(rawp + offset++) = coloru.mV[0];
- *(rawp + offset++) = coloru.mV[1];
- *(rawp + offset++) = coloru.mV[2];
- *(rawp + offset++) = coloru.mV[3];
- }
- else
- {
- // Want non-linear curve for transparency gradient
- coloru = MAX_WATER_COLOR;
- const F32 frac = 1.f - 2.f/(2.f - (height - WATER_HEIGHT));
- S32 alpha = 64 + ll_round((255-64)*frac);
-
- alpha = llmin(ll_round((F32)MAX_WATER_COLOR.mV[3]), alpha);
- alpha = llmax(64, alpha);
-
- coloru.mV[3] = alpha;
- *(rawp + offset++) = coloru.mV[0];
- *(rawp + offset++) = coloru.mV[1];
- *(rawp + offset++) = coloru.mV[2];
- *(rawp + offset++) = coloru.mV[3];
- }
- }
- }
-
- if (!mWaterTexturep->hasGLTexture())
- {
- mWaterTexturep->createGLTexture(0, raw);
- }
-
- mWaterTexturep->setSubImage(raw, x_begin, y_begin, x_end - x_begin, y_end - y_begin);
- return true;
-}
diff --git a/indra/newview/llsurface.h b/indra/newview/llsurface.h
index 324296a4d3..68295225b6 100644
--- a/indra/newview/llsurface.h
+++ b/indra/newview/llsurface.h
@@ -128,7 +128,7 @@ public:
F32 getWaterHeight() const;
LLViewerTexture *getSTexture();
- LLViewerTexture *getWaterTexture();
+
bool hasZData() const { return mHasZData; }
void dirtyAllPatches(); // Use this to dirty all patches when changing terrain parameters
@@ -171,19 +171,11 @@ public:
protected:
void createSTexture();
- void createWaterTexture();
void initTextures();
- void initWater();
-
void createPatchData(); // Allocates memory for patches.
void destroyPatchData(); // Deallocates memory for patches.
- bool generateWaterTexture(const F32 x, const F32 y,
- const F32 width, const F32 height); // Generate texture from composition values.
-
- //F32 updateTexture(LLSurfacePatch *ppatch);
-
LLSurfacePatch *getPatch(const S32 x, const S32 y) const;
protected:
@@ -201,7 +193,6 @@ protected:
// The textures should never be directly initialized - use the setter methods!
LLPointer<LLViewerTexture> mSTexturep; // Texture for surface
- LLPointer<LLViewerTexture> mWaterTexturep; // Water texture
LLPointer<LLVOWater> mWaterObjp;
diff --git a/indra/newview/llsurfacepatch.cpp b/indra/newview/llsurfacepatch.cpp
index 042d770550..2a21170b07 100644
--- a/indra/newview/llsurfacepatch.cpp
+++ b/indra/newview/llsurfacepatch.cpp
@@ -906,15 +906,8 @@ void LLSurfacePatch::updateGL()
updateCompositionStats();
F32 tex_patch_size = meters_per_grid*grids_per_patch_edge;
- if (comp->generateMinimapTileLand((F32)origin_region[VX], (F32)origin_region[VY],
- tex_patch_size, tex_patch_size))
- {
- mSTexUpdate = false;
- // Also generate the water texture
- mSurfacep->generateWaterTexture((F32)origin_region.mdV[VX], (F32)origin_region.mdV[VY],
- tex_patch_size, tex_patch_size);
- }
+ mSTexUpdate = false;
}
void LLSurfacePatch::dirtyZ()
diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp
index f521293b96..9d7319d5b8 100644
--- a/indra/newview/lltextureview.cpp
+++ b/indra/newview/lltextureview.cpp
@@ -59,8 +59,6 @@
#include "llvoavatarself.h"
#include "lltexlayer.h"
-extern F32 texmem_lower_bound_scale;
-
LLTextureView *gTextureView = NULL;
#define HIGH_PRIORITY 100000000.f
@@ -463,7 +461,7 @@ public:
: texture_view("texture_view")
{
S32 line_height = LLFontGL::getFontMonospace()->getLineHeight();
- changeDefault(rect, LLRect(0,0,100,line_height * 4));
+ changeDefault(rect, LLRect(0,0,0,line_height * 7));
}
};
@@ -494,19 +492,51 @@ void LLGLTexMemBar::draw()
U32 total_objects = gObjectList.getNumObjects();
F32 x_right = 0.0;
+ U32 image_count = gTextureList.getNumImages();
+ U32 raw_image_count = 0;
+ U64 raw_image_bytes = 0;
+
+ U32 saved_raw_image_count = 0;
+ U64 saved_raw_image_bytes = 0;
+
+ U32 aux_raw_image_count = 0;
+ U64 aux_raw_image_bytes = 0;
+
+ for (auto& image : gTextureList)
+ {
+ const LLImageRaw* raw_image = image->getRawImage();
+
+ if (raw_image)
+ {
+ raw_image_count++;
+ raw_image_bytes += raw_image->getDataSize();
+ }
+
+ raw_image = image->getSavedRawImage();
+ if (raw_image)
+ {
+ saved_raw_image_count++;
+ saved_raw_image_bytes += raw_image->getDataSize();
+ }
+
+ raw_image = image->getAuxRawImage();
+ if (raw_image)
+ {
+ aux_raw_image_count++;
+ aux_raw_image_bytes += raw_image->getDataSize();
+ }
+ }
+
+ F64 raw_image_bytes_MB = raw_image_bytes / (1024.0 * 1024.0);
+ F64 saved_raw_image_bytes_MB = saved_raw_image_bytes / (1024.0 * 1024.0);
+ F64 aux_raw_image_bytes_MB = aux_raw_image_bytes / (1024.0 * 1024.0);
+
//----------------------------------------------------------------------------
LLGLSUIDefault gls_ui;
LLColor4 text_color(1.f, 1.f, 1.f, 0.75f);
LLColor4 color;
- // Gray background using completely magic numbers
- gGL.color4f(0.f, 0.f, 0.f, 0.25f);
- // const LLRect & rect(getRect());
- // gl_rect_2d(-4, v_offset, rect.mRight - rect.mLeft + 2, v_offset + line_height*4);
-
std::string text = "";
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6,
- text_color, LLFontGL::LEFT, LLFontGL::TOP);
LLTrace::Recording& recording = LLViewerStats::instance().getRecording();
@@ -527,6 +557,10 @@ void LLGLTexMemBar::draw()
U32 texFetchLatMed = U32(recording.getMean(LLTextureFetch::sTexFetchLatency).value() * 1000.0f);
U32 texFetchLatMax = U32(recording.getMax(LLTextureFetch::sTexFetchLatency).value() * 1000.0f);
+ // draw a background above first line.... no idea where the rest of the background comes from for the below text
+ gGL.color4f(0.f, 0.f, 0.f, 0.25f);
+ gl_rect_2d(-10, getRect().getHeight() + line_height + 1, getRect().getWidth()+2, getRect().getHeight()+2);
+
text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Bias: %.2f Cache: %.1f/%.1f MB",
(S32)LLViewerTexture::sFreeVRAMMegabytes,
LLMemory::getAvailableMemKB()/1024,
@@ -534,11 +568,15 @@ void LLGLTexMemBar::draw()
discard_bias,
cache_usage,
cache_max_usage);
- //, cache_entries, cache_max_entries
-
- LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6,
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*7,
text_color, LLFontGL::LEFT, LLFontGL::TOP);
+ text = llformat("Images: %d Raw: %d (%.2f MB) Saved: %d (%.2f MB) Aux: %d (%.2f MB)", image_count, raw_image_count, raw_image_bytes_MB,
+ saved_raw_image_count, saved_raw_image_bytes_MB,
+ aux_raw_image_count, aux_raw_image_bytes_MB);
+ LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height * 6,
+ text_color, LLFontGL::LEFT, LLFontGL::TOP);
+
U32 cache_read(0U), cache_write(0U), res_wait(0U);
LLAppViewer::getTextureFetch()->getStateStats(&cache_read, &cache_write, &res_wait);
@@ -551,7 +589,6 @@ void LLGLTexMemBar::draw()
cache_read,
cache_write,
res_wait);
-
LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5,
text_color, LLFontGL::LEFT, LLFontGL::TOP);
@@ -791,7 +828,7 @@ void LLTextureView::draw()
LL_INFOS() << "ID\tMEM\tBOOST\tPRI\tWIDTH\tHEIGHT\tDISCARD" << LL_ENDL;
}
- for (LLViewerTextureList::image_priority_list_t::iterator iter = gTextureList.mImageList.begin();
+ for (LLViewerTextureList::image_list_t::iterator iter = gTextureList.mImageList.begin();
iter != gTextureList.mImageList.end(); )
{
LLPointer<LLViewerFetchedTexture> imagep = *iter++;
diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp
index a070711727..ab7953846f 100644
--- a/indra/newview/llviewercamera.cpp
+++ b/indra/newview/llviewercamera.cpp
@@ -94,7 +94,13 @@ LLViewerCamera::LLViewerCamera() : LLCamera()
mZoomSubregion = 1;
mAverageSpeed = 0.f;
mAverageAngularSpeed = 0.f;
- gSavedSettings.getControl("CameraAngle")->getCommitSignal()->connect(boost::bind(&LLViewerCamera::updateCameraAngle, this, _2));
+
+ mCameraAngleChangedSignal = gSavedSettings.getControl("CameraAngle")->getCommitSignal()->connect(boost::bind(&LLViewerCamera::updateCameraAngle, this, _2));
+}
+
+LLViewerCamera::~LLViewerCamera()
+{
+ mCameraAngleChangedSignal.disconnect();
}
void LLViewerCamera::updateCameraLocation(const LLVector3 &center, const LLVector3 &up_direction, const LLVector3 &point_of_interest)
@@ -895,10 +901,8 @@ bool LLViewerCamera::isDefaultFOVChanged()
return false;
}
-// static
-void LLViewerCamera::updateCameraAngle( void* user_data, const LLSD& value)
+void LLViewerCamera::updateCameraAngle(const LLSD& value)
{
- LLViewerCamera* self=(LLViewerCamera*)user_data;
- self->setDefaultFOV(value.asReal());
+ setDefaultFOV(value.asReal());
}
diff --git a/indra/newview/llviewercamera.h b/indra/newview/llviewercamera.h
index 6d8fb2a520..a204b85d88 100644
--- a/indra/newview/llviewercamera.h
+++ b/indra/newview/llviewercamera.h
@@ -43,6 +43,7 @@ class alignas(16) LLViewerCamera : public LLCamera, public LLSimpleton<LLViewerC
LL_ALIGN_NEW
public:
LLViewerCamera();
+ ~LLViewerCamera();
typedef enum
{
@@ -65,7 +66,7 @@ public:
const LLVector3 &point_of_interest);
static void updateFrustumPlanes(LLCamera& camera, bool ortho = false, bool zflip = false, bool no_hacks = false);
- static void updateCameraAngle(void* user_data, const LLSD& value);
+ void updateCameraAngle(const LLSD& value);
void setPerspective(bool for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, bool limit_select_distance, F32 z_near = 0, F32 z_far = 0);
const LLMatrix4 &getProjection() const;
@@ -125,6 +126,8 @@ protected:
F32 mZoomFactor;
S16 mZoomSubregion;
+ boost::signals2::connection mCameraAngleChangedSignal;
+
public:
};
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index efed045b91..cd6e780aa8 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -873,6 +873,9 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged);
setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged);
setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged);
+ setting_setup_signal_listener(gSavedSettings, "VoiceEchoCancellation", handleVoiceClientPrefsChanged);
+ setting_setup_signal_listener(gSavedSettings, "VoiceAutomaticGainControl", handleVoiceClientPrefsChanged);
+ setting_setup_signal_listener(gSavedSettings, "VoiceNoiseSuppressionLevel", handleVoiceClientPrefsChanged);
setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged);
setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged);
setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged);
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index ceda2675d5..3e1705b8a1 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -76,6 +76,7 @@
#include "llfloaterfonttest.h"
#include "llfloaterforgetuser.h"
#include "llfloatergesture.h"
+#include "llfloatergltfasseteditor.h"
#include "llfloatergodtools.h"
#include "llfloatergridstatus.h"
#include "llfloatergroups.h"
@@ -372,6 +373,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("forget_username", "floater_forget_user.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterForgetUser>);
LLFloaterReg::add("gestures", "floater_gesture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGesture>);
+ LLFloaterReg::add("gltf_asset_editor", "floater_gltf_asset_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGLTFAssetEditor>);
LLFloaterReg::add("god_tools", "floater_god_tools.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGodTools>);
LLFloaterReg::add("grid_status", "floater_grid_status.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGridStatus>);
LLFloaterReg::add("group_picker", "floater_choose_group.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGroupPicker>);
diff --git a/indra/newview/llviewerhelp.cpp b/indra/newview/llviewerhelp.cpp
index e7b150965d..bca1c4166a 100644
--- a/indra/newview/llviewerhelp.cpp
+++ b/indra/newview/llviewerhelp.cpp
@@ -45,7 +45,7 @@ public:
// requests will be throttled from a non-trusted browser
LLHelpHandler() : LLCommandHandler("help", UNTRUSTED_CLICK_ONLY) {}
- bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web)
+ bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) override
{
LLViewerHelp* vhelp = LLViewerHelp::getInstance();
if (! vhelp)
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index 23931ecf03..f62d929e9a 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -1253,41 +1253,46 @@ void LLViewerMedia::getOpenIDCookieCoro(std::string url)
hostEnd = authority.size();
}
- LLViewerMedia* inst = getInstance();
if (url.length())
{
- LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild<LLMediaCtrl>("destination_guide_contents");
- if (media_instance)
- {
- std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart);
- std::string cookie_name = "";
- std::string cookie_value = "";
- std::string cookie_path = "";
- bool httponly = true;
- bool secure = true;
- if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) &&
- media_instance->getMediaPlugin())
+ LLAppViewer::instance()->postToMainCoro([=]()
{
- // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the
- // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked.
- // For now, we use the URL for the OpenID POST request since it will have the same authority
- // as the domain field.
- // (Feels like there must be a less dirty way to construct a URL from component LLURL parts)
- // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further
- // down.
- std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority));
-
- media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host,
- cookie_path, httponly, secure);
-
- // Now that we have parsed the raw cookie, we must store it so that each new media instance
- // can also get a copy and faciliate logging into internal SL sites.
- media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value,
- cookie_host, cookie_path, httponly, secure);
- }
- }
+ LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild<LLMediaCtrl>("destination_guide_contents");
+ if (media_instance)
+ {
+ LLViewerMedia* inst = getInstance();
+ std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart);
+ std::string cookie_name = "";
+ std::string cookie_value = "";
+ std::string cookie_path = "";
+ bool httponly = true;
+ bool secure = true;
+ if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) &&
+ media_instance->getMediaPlugin())
+ {
+ // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the
+ // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked.
+ // For now, we use the URL for the OpenID POST request since it will have the same authority
+ // as the domain field.
+ // (Feels like there must be a less dirty way to construct a URL from component LLURL parts)
+ // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further
+ // down.
+ std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority));
+
+ media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host,
+ cookie_path, httponly, secure);
+
+ // Now that we have parsed the raw cookie, we must store it so that each new media instance
+ // can also get a copy and faciliate logging into internal SL sites.
+ media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value,
+ cookie_host, cookie_path, httponly, secure);
+ }
+ }
+ });
}
+ LLViewerMedia* inst = getInstance();
+
// Note: Rider: MAINT-6392 - Some viewer code requires access to the my.sl.com openid cookie for such
// actions as posting snapshots to the feed. This is handled through HTTPCore rather than CEF and so
// we must learn to SHARE the cookies.
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 7e58a604f6..2687938b35 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -8139,6 +8139,15 @@ class LLAdvancedClickGLTFUpload: public view_listener_t
}
};
+class LLAdvancedClickGLTFEdit : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ LLFloaterReg::showInstance("gltf_asset_editor");
+ return true;
+ }
+};
+
class LLAdvancedClickResizeWindow : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@@ -9481,15 +9490,6 @@ class LLUpdateMembershipLabel : public view_listener_t
}
};
-void handle_voice_morphing_subscribe()
-{
- LLWeb::loadURL(LLTrans::getString("voice_morphing_url"));
-}
-
-void handle_premium_voice_morphing_subscribe()
-{
- LLWeb::loadURL(LLTrans::getString("premium_voice_morphing_url"));
-}
class LLToggleUIHints : public view_listener_t
{
@@ -9690,16 +9690,6 @@ void initialize_menus()
//Communicate Nearby chat
view_listener_t::addMenu(new LLCommunicateNearbyChat(), "Communicate.NearbyChat");
- // Communicate > Voice morphing > Subscribe...
- commit.add("Communicate.VoiceMorphing.Subscribe", boost::bind(&handle_voice_morphing_subscribe));
- // Communicate > Voice morphing > Premium perk...
- commit.add("Communicate.VoiceMorphing.PremiumPerk", boost::bind(&handle_premium_voice_morphing_subscribe));
- LLVivoxVoiceClient * voice_clientp = LLVivoxVoiceClient::getInstance();
- enable.add("Communicate.VoiceMorphing.NoVoiceMorphing.Check"
- , boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, voice_clientp, "NoVoiceMorphing"));
- commit.add("Communicate.VoiceMorphing.NoVoiceMorphing.Click"
- , boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, voice_clientp, "NoVoiceMorphing"));
-
// World menu
view_listener_t::addMenu(new LLWorldAlwaysRun(), "World.AlwaysRun");
view_listener_t::addMenu(new LLWorldCreateLandmark(), "World.CreateLandmark");
@@ -9813,6 +9803,7 @@ void initialize_menus()
view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen");
view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs");
view_listener_t::addMenu(new LLAdvancedClickGLTFUpload(), "Advanced.ClickGLTFUpload");
+ view_listener_t::addMenu(new LLAdvancedClickGLTFEdit(), "Advanced.ClickGLTFEdit");
view_listener_t::addMenu(new LLAdvancedClickResizeWindow(), "Advanced.ClickResizeWindow");
view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache");
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 8af0057c88..232b020d3d 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -3676,7 +3676,9 @@ bool LLViewerObject::updateLOD()
bool LLViewerObject::updateGeometry(LLDrawable *drawable)
{
- return false;
+ // return true means "update complete", return false means "try again next frame"
+ // default should be return true
+ return true;
}
void LLViewerObject::updateGL()
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index b96a1a6644..09584d22a8 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -266,6 +266,7 @@ public:
virtual bool isRiggedMesh() const { return false; }
virtual bool hasLightTexture() const { return false; }
virtual bool isReflectionProbe() const { return false; }
+ virtual bool getReflectionProbeIsBox() const { return false; }
// This method returns true if the object is over land owned by
// the agent, one of its groups, or it encroaches and
diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp
index e8a9f41855..39244c8246 100755
--- a/indra/newview/llviewerregion.cpp
+++ b/indra/newview/llviewerregion.cpp
@@ -1269,8 +1269,12 @@ U32 LLViewerRegion::getNumOfVisibleGroups() const
return mImpl ? static_cast<U32>(mImpl->mVisibleGroups.size()) : 0;
}
-void LLViewerRegion::updateReflectionProbes()
+void LLViewerRegion::updateReflectionProbes(bool full_update)
{
+ if (!full_update && mReflectionMaps.empty())
+ {
+ return;
+ }
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
const F32 probe_spacing = 32.f;
const F32 probe_radius = sqrtf((probe_spacing * 0.5f) * (probe_spacing * 0.5f) * 3.f);
@@ -2854,7 +2858,11 @@ bool LLViewerRegion::probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss
if(entry->isState(LLVOCacheEntry::ACTIVE))
{
- ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags);
+ LLDrawable* drawable = (LLDrawable*)entry->getEntry()->getDrawable();
+ if (drawable && drawable->getVObj())
+ {
+ drawable->getVObj()->loadFlags(flags);
+ }
return true;
}
@@ -3257,6 +3265,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
capabilityNames.append("ParcelVoiceInfoRequest");
capabilityNames.append("ProductInfoRequest");
capabilityNames.append("ProvisionVoiceAccountRequest");
+ capabilityNames.append("VoiceSignalingRequest");
capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite
capabilityNames.append("RegionObjects");
capabilityNames.append("RemoteParcelRequest");
diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h
index 1329bdf1d5..d0ec1fe877 100644
--- a/indra/newview/llviewerregion.h
+++ b/indra/newview/llviewerregion.h
@@ -43,6 +43,7 @@
#include "m4math.h" // LLMatrix4
#include "llframetimer.h"
#include "llreflectionmap.h"
+#include "llpointer.h"
// Surface id's
#define LAND 1
@@ -427,7 +428,7 @@ public:
static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;}
// rebuild reflection probe list
- void updateReflectionProbes();
+ void updateReflectionProbes(bool full_update);
private:
void addToVOCacheTree(LLVOCacheEntry* entry);
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index 37fbfccbbb..12d0aa4f8e 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -259,7 +259,18 @@ static bool make_gltf_variant(LLGLSLShader& shader, LLGLSLShader& variant, bool
variant.mDefines = shader.mDefines; // NOTE: Must come before addPermutation
- variant.addPermutation("MAX_JOINTS_PER_GLTF_OBJECT", std::to_string(LLSkinningUtil::getMaxGLTFJointCount()));
+ U32 node_size = 16 * 3;
+ U32 max_nodes = gGLManager.mMaxUniformBlockSize / node_size;
+ variant.addPermutation("MAX_NODES_PER_GLTF_OBJECT", std::to_string(max_nodes));
+
+ U32 material_size = 16 * 12;
+ U32 max_materials = gGLManager.mMaxUniformBlockSize / material_size;
+ LLGLSLShader::sMaxGLTFMaterials = max_materials;
+
+ variant.addPermutation("MAX_MATERIALS_PER_GLTF_OBJECT", std::to_string(max_materials));
+
+ U32 max_vec4s = gGLManager.mMaxUniformBlockSize / 16;
+ variant.addPermutation("MAX_UBO_VEC4S", std::to_string(max_vec4s));
if (rigged)
{
diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp
index c1062b9a01..b2ed86707c 100644
--- a/indra/newview/llviewertexture.cpp
+++ b/indra/newview/llviewertexture.cpp
@@ -101,6 +101,7 @@ U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA;
bool LLViewerTexture::sFreezeImageUpdates = false;
F32 LLViewerTexture::sCurrentTime = 0.0f;
+constexpr F32 MEMORY_CHECK_WAIT_TIME = 1.0f;
constexpr F32 MIN_VRAM_BUDGET = 768.f;
F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET;
@@ -413,8 +414,6 @@ void LLViewerTextureManager::init()
}
}
imagep->createGLTexture(0, image_raw);
- //cache the raw image
- imagep->setCachedRawImage(0, image_raw);
image_raw = NULL;
#else
LLViewerFetchedTexture::sDefaultImagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, true, LLGLTexture::BOOST_UI);
@@ -484,10 +483,6 @@ void LLViewerTexture::initClass()
LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture();
}
-// non-const (used externally
-F32 texmem_lower_bound_scale = 0.85f;
-F32 texmem_middle_bound_scale = 0.925f;
-
//static
void LLViewerTexture::updateClass()
{
@@ -519,14 +514,49 @@ void LLViewerTexture::updateClass()
sFreeVRAMMegabytes = target - used;
F32 over_pct = llmax((used-target) / target, 0.f);
- sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + over_pct);
- if (sDesiredDiscardBias > 1.f)
+ if (isSystemMemoryLow())
+ {
+ // System RAM is low -> ramp up discard bias over time to free memory
+ if (sEvaluationTimer.getElapsedTimeF32() > MEMORY_CHECK_WAIT_TIME)
+ {
+ static LLCachedControl<F32> low_mem_min_discard_increment(gSavedSettings, "RenderLowMemMinDiscardIncrement", .1f);
+ sDesiredDiscardBias += llmax(low_mem_min_discard_increment, over_pct);
+ sEvaluationTimer.reset();
+ }
+ }
+ else
+ {
+ sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + over_pct);
+
+ if (sDesiredDiscardBias > 1.f)
+ {
+ sDesiredDiscardBias -= gFrameIntervalSeconds * 0.01;
+ }
+ }
+
+ LLViewerTexture::sFreezeImageUpdates = false;
+}
+
+//static
+bool LLViewerTexture::isSystemMemoryLow()
+{
+ static LLFrameTimer timer;
+ static U32Megabytes physical_res = U32Megabytes(U32_MAX);
+
+ static LLCachedControl<U32> min_free_main_memory(gSavedSettings, "RenderMinFreeMainMemoryThreshold", 512);
+ const U32Megabytes MIN_FREE_MAIN_MEMORY(min_free_main_memory);
+
+ if (timer.getElapsedTimeF32() < MEMORY_CHECK_WAIT_TIME) //call this once per second.
{
- sDesiredDiscardBias -= gFrameIntervalSeconds * 0.01;
+ return physical_res < MIN_FREE_MAIN_MEMORY;
}
- LLViewerTexture::sFreezeImageUpdates = false; // sDesiredDiscardBias > (desired_discard_bias_max - 1.0f);
+ timer.reset();
+
+ LLMemory::updateMemoryInfo();
+ physical_res = LLMemory::getAvailableMemKB();
+ return physical_res < MIN_FREE_MAIN_MEMORY;
}
//end of static functions
@@ -696,9 +726,6 @@ bool LLViewerTexture::bindDefaultImage(S32 stage)
}
stop_glerror();
- //check if there is cached raw image and switch to it if possible
- switchToCachedImage();
-
LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName);
if (tester)
{
@@ -899,18 +926,6 @@ void LLViewerTexture::reorganizeVolumeList()
}
}
-//virtual
-void LLViewerTexture::switchToCachedImage()
-{
- //nothing here.
-}
-
-//virtual
-void LLViewerTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw)
-{
- //nothing here.
-}
-
bool LLViewerTexture::isLargeImage()
{
return (S32)mTexelsPerImage > LLViewerTexture::sMinLargeImageSize;
@@ -1045,10 +1060,6 @@ void LLViewerFetchedTexture::init(bool firstinit)
mIsFetched = false;
mInFastCacheList = false;
- mCachedRawImage = NULL;
- mCachedRawDiscardLevel = -1;
- mCachedRawImageReady = false;
-
mSavedRawImage = NULL;
mForceToSaveRawImage = false;
mSaveRawImage = false;
@@ -1106,9 +1117,6 @@ void LLViewerFetchedTexture::cleanup()
// Clean up image data
destroyRawImage();
- mCachedRawImage = NULL;
- mCachedRawDiscardLevel = -1;
- mCachedRawImageReady = false;
mSavedRawImage = NULL;
mSavedRawDiscardLevel = -1;
}
@@ -1188,13 +1196,17 @@ void LLViewerFetchedTexture::setForSculpt()
{
static const S32 MAX_INTERVAL = 8; //frames
+ forceToSaveRawImage(0, F32_MAX);
+
+ setBoostLevel(llmax((S32)getBoostLevel(),
+ (S32)LLGLTexture::BOOST_SCULPTED));
+
mForSculpt = true;
if(isForSculptOnly() && hasGLTexture() && !getBoundRecently())
{
destroyGLTexture(); //sculpt image does not need gl texture.
mTextureState = ACTIVE;
}
- checkCachedRawSculptImage();
setMaxVirtualSizeResetInterval(MAX_INTERVAL);
}
@@ -1307,10 +1319,6 @@ void LLViewerFetchedTexture::addToCreateTexture()
}
}
- //discard the cached raw image and the saved raw image
- mCachedRawImageReady = false;
- mCachedRawDiscardLevel = -1;
- mCachedRawImage = NULL;
mSavedRawDiscardLevel = -1;
mSavedRawImage = NULL;
}
@@ -2011,13 +2019,6 @@ bool LLViewerFetchedTexture::updateFetch()
LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min");
make_request = false;
}
- else if(mCachedRawImage.notNull() // can be empty
- && mCachedRawImageReady
- && (current_discard < 0 || current_discard > mCachedRawDiscardLevel))
- {
- make_request = false;
- switchToCachedImage(); //use the cached raw data first
- }
if (make_request)
{
@@ -2511,20 +2512,6 @@ bool LLViewerFetchedTexture::doLoadedCallbacks()
}
//
- // Do a readback if required, OR start off a texture decode
- //
- if (need_readback && (getMaxDiscardLevel() > gl_discard))
- {
- // Do a readback to get the GL data into the raw image
- // We have GL data.
-
- destroyRawImage();
- reloadRawImage(mLoadedCallbackDesiredDiscardLevel);
- llassert(mRawImage.notNull());
- llassert(!mNeedsAux || mAuxRawImage.notNull());
- }
-
- //
// Run raw/auxiliary data callbacks
//
if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= getMaxDiscardLevel()))
@@ -2631,61 +2618,6 @@ void LLViewerFetchedTexture::forceImmediateUpdate()
return;
}
-LLImageRaw* LLViewerFetchedTexture::reloadRawImage(S8 discard_level)
-{
- llassert(mGLTexturep.notNull());
- llassert(discard_level >= 0);
- llassert(mComponents > 0);
-
- if (mRawImage.notNull())
- {
- //mRawImage is in use by somebody else, do not delete it.
- return NULL;
- }
-
- if(mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= discard_level)
- {
- if (mSavedRawDiscardLevel != discard_level
- && mBoostLevel != BOOST_ICON
- && mBoostLevel != BOOST_THUMBNAIL)
- {
- mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents());
- mRawImage->copy(getSavedRawImage());
- }
- else
- {
- mRawImage = getSavedRawImage();
- }
- mRawDiscardLevel = discard_level;
- }
- else
- {
- //force to fetch raw image again if cached raw image is not good enough.
- if(mCachedRawDiscardLevel > discard_level)
- {
- mRawImage = mCachedRawImage;
- mRawDiscardLevel = mCachedRawDiscardLevel;
- }
- else //cached raw image is good enough, copy it.
- {
- if(mCachedRawDiscardLevel != discard_level)
- {
- mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents());
- mRawImage->copy(mCachedRawImage);
- }
- else
- {
- mRawImage = mCachedRawImage;
- }
- mRawDiscardLevel = discard_level;
- }
- }
- mIsRawImageValid = true;
- sRawCount++;
-
- return mRawImage;
-}
-
bool LLViewerFetchedTexture::needsToSaveRawImage()
{
return mForceToSaveRawImage || mSaveRawImage;
@@ -2710,7 +2642,6 @@ void LLViewerFetchedTexture::destroyRawImage()
{
saveRawImage();
}
- setCachedRawImage();
}
mRawImage = NULL;
@@ -2720,151 +2651,6 @@ void LLViewerFetchedTexture::destroyRawImage()
}
}
-//use the mCachedRawImage to (re)generate the gl texture.
-//virtual
-void LLViewerFetchedTexture::switchToCachedImage()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
- if(mCachedRawImage.notNull() &&
- !mNeedsCreateTexture) // <--- texture creation is pending, don't step on it
- {
- mRawImage = mCachedRawImage;
-
- if (getComponents() != mRawImage->getComponents())
- {
- // We've changed the number of components, so we need to move any
- // objects using this pool to a different pool.
- mComponents = mRawImage->getComponents();
- mGLTexturep->setComponents(mComponents);
- gTextureList.dirtyImage(this);
- }
-
- mIsRawImageValid = true;
- mRawDiscardLevel = mCachedRawDiscardLevel;
-
- scheduleCreateTexture();
- }
-}
-
-//cache the imageraw forcefully.
-//virtual
-void LLViewerFetchedTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw)
-{
- if(imageraw != mRawImage.get())
- {
- if (mBoostLevel == LLGLTexture::BOOST_ICON)
- {
- S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS;
- S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS;
- if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)
- {
- mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents());
- mCachedRawImage->copyScaled(imageraw);
- }
- else
- {
- mCachedRawImage = imageraw;
- }
- }
- else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL)
- {
- S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS;
- S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS;
- if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)
- {
- mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents());
- mCachedRawImage->copyScaled(imageraw);
- }
- else
- {
- mCachedRawImage = imageraw;
- }
- }
- else
- {
- mCachedRawImage = imageraw;
- }
- mCachedRawDiscardLevel = discard_level;
- mCachedRawImageReady = true;
- }
-}
-
-void LLViewerFetchedTexture::setCachedRawImage()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
- if(mRawImage == mCachedRawImage)
- {
- return;
- }
- if(!mIsRawImageValid)
- {
- return;
- }
-
- if(mCachedRawImageReady)
- {
- return;
- }
-
- if(mCachedRawDiscardLevel < 0 || mCachedRawDiscardLevel > mRawDiscardLevel)
- {
- S32 i = 0;
- S32 w = mRawImage->getWidth();
- S32 h = mRawImage->getHeight();
-
- S32 max_size = MAX_CACHED_RAW_IMAGE_AREA;
- if(LLGLTexture::BOOST_TERRAIN == mBoostLevel)
- {
- max_size = MAX_CACHED_RAW_TERRAIN_IMAGE_AREA;
- }
- if(mForSculpt)
- {
- max_size = MAX_CACHED_RAW_SCULPT_IMAGE_AREA;
- mCachedRawImageReady = !mRawDiscardLevel;
- }
- else
- {
- mCachedRawImageReady = (!mRawDiscardLevel || ((w * h) >= max_size));
- }
-
- while(((w >> i) * (h >> i)) > max_size)
- {
- ++i;
- }
-
- if(i)
- {
- if(!(w >> i) || !(h >> i))
- {
- --i;
- }
-
- {
- //make a duplicate in case somebody else is using this raw image
- mRawImage = mRawImage->scaled(w >> i, h >> i);
- }
- }
- mCachedRawImage = mRawImage;
- mRawDiscardLevel += i;
- mCachedRawDiscardLevel = mRawDiscardLevel;
- }
-}
-
-void LLViewerFetchedTexture::checkCachedRawSculptImage()
-{
- if(mCachedRawImageReady && mCachedRawDiscardLevel > 0)
- {
- if(getDiscardLevel() != 0)
- {
- mCachedRawImageReady = false;
- }
- else if(isForSculptOnly())
- {
- resetTextureStats(); //do not update this image any more.
- }
- }
-}
-
void LLViewerFetchedTexture::saveRawImage()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
@@ -2904,6 +2690,20 @@ void LLViewerFetchedTexture::saveRawImage()
mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents());
}
}
+ else if (mBoostLevel == LLGLTexture::BOOST_SCULPTED)
+ {
+ S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : sMaxSculptRez;
+ S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : sMaxSculptRez;
+ if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)
+ {
+ mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents());
+ mSavedRawImage->copyScaled(mRawImage);
+ }
+ else
+ {
+ mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents());
+ }
+ }
else
{
mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents());
@@ -2949,20 +2749,23 @@ void LLViewerFetchedTexture::forceToSaveRawImage(S32 desired_discard, F32 kept_t
{
mForceToSaveRawImage = true;
mDesiredSavedRawDiscardLevel = desired_discard;
+ }
+}
- //copy from the cached raw image if exists.
- if(mCachedRawImage.notNull() && mRawImage.isNull() )
- {
- mRawImage = mCachedRawImage;
- mRawDiscardLevel = mCachedRawDiscardLevel;
-
- saveRawImage();
+void LLViewerFetchedTexture::readbackRawImage()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
- mRawImage = NULL;
- mRawDiscardLevel = INVALID_DISCARD_LEVEL;
+ if (mGLTexturep.notNull() && mGLTexturep->getTexName() != 0 && mRawImage.isNull())
+ {
+ mRawImage = new LLImageRaw();
+ if (!mGLTexturep->readBackRaw(-1, mRawImage, false))
+ {
+ mRawImage = nullptr;
}
}
}
+
void LLViewerFetchedTexture::destroySavedRawImage()
{
if(mLastReferencedSavedRawImageTime < mKeptSavedRawImageTime)
@@ -2997,6 +2800,11 @@ LLImageRaw* LLViewerFetchedTexture::getSavedRawImage()
return mSavedRawImage;
}
+const LLImageRaw* LLViewerFetchedTexture::getSavedRawImage() const
+{
+ return mSavedRawImage;
+}
+
bool LLViewerFetchedTexture::hasSavedRawImage() const
{
return mSavedRawImage.notNull();
@@ -3173,20 +2981,14 @@ void LLViewerLODTexture::processTextureStats()
bool LLViewerLODTexture::scaleDown()
{
- if(hasGLTexture() && mCachedRawDiscardLevel > getDiscardLevel())
+ if (mGLTexturep.isNull())
{
- switchToCachedImage();
-
- LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName);
- if (tester)
- {
- tester->setStablizingTime();
- }
-
- return true;
+ return false;
}
- return false;
+
+ return mGLTexturep->scaleDown(mDesiredDiscardLevel);
}
+
//----------------------------------------------------------------------------------------------
//end of LLViewerLODTexture
//----------------------------------------------------------------------------------------------
diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h
index dc9182bf1b..b1e0494a30 100644
--- a/indra/newview/llviewertexture.h
+++ b/indra/newview/llviewertexture.h
@@ -117,6 +117,7 @@ protected:
public:
static void initClass();
static void updateClass();
+ static bool isSystemMemoryLow();
LLViewerTexture(bool usemipmaps = true);
LLViewerTexture(const LLUUID& id, bool usemipmaps) ;
@@ -165,8 +166,6 @@ public:
S32 getNumVolumes(U32 channel) const;
const ll_volume_list_t* getVolumeList(U32 channel) const { return &mVolumeList[channel]; }
-
- virtual void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) ;
bool isLargeImage() ;
void setParcelMedia(LLViewerMediaTexture* media) {mParcelMedia = media;}
@@ -184,8 +183,6 @@ private:
friend class LLBumpImageList;
friend class LLUIImageList;
- virtual void switchToCachedImage();
-
protected:
friend class LLViewerTextureList;
LLUUID mID;
@@ -370,7 +367,6 @@ public:
U32 getFetchPriority() const { return mFetchPriority ;}
F32 getDownloadProgress() const {return mDownloadProgress ;}
- LLImageRaw* reloadRawImage(S8 discard_level) ;
void destroyRawImage();
bool needsToSaveRawImage();
@@ -389,17 +385,20 @@ public:
bool isForSculptOnly() const;
//raw image management
- void checkCachedRawSculptImage() ;
LLImageRaw* getRawImage()const { return mRawImage ;}
S32 getRawImageLevel() const {return mRawDiscardLevel;}
- LLImageRaw* getCachedRawImage() const { return mCachedRawImage ;}
- S32 getCachedRawImageLevel() const {return mCachedRawDiscardLevel;}
- bool isCachedRawImageReady() const {return mCachedRawImageReady ;}
bool isRawImageValid()const { return mIsRawImageValid ; }
void forceToSaveRawImage(S32 desired_discard = 0, F32 kept_time = 0.f) ;
- /*virtual*/ void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) override;
+
+ // readback the raw image from OpenGL if mRawImage is not valid
+ void readbackRawImage();
+
void destroySavedRawImage() ;
LLImageRaw* getSavedRawImage() ;
+ S32 getSavedRawImageLevel() const {return mSavedRawDiscardLevel; }
+
+ const LLImageRaw* getSavedRawImage() const;
+ const LLImageRaw* getAuxRawImage() const { return mAuxRawImage; }
bool hasSavedRawImage() const ;
F32 getElapsedLastReferencedSavedRawImageTime() const ;
bool isFullyLoaded() const;
@@ -416,7 +415,6 @@ public:
/*virtual*/bool isActiveFetching() override; //is actively in fetching by the fetching pipeline.
protected:
- /*virtual*/ void switchToCachedImage() override;
S32 getCurrentDiscardLevelForFetching() ;
void forceToRefetchTexture(S32 desired_discard = 0, F32 kept_time = 60.f);
@@ -425,7 +423,6 @@ private:
void cleanup() ;
void saveRawImage() ;
- void setCachedRawImage() ;
//for atlas
void resetFaceAtlas() ;
@@ -497,11 +494,6 @@ protected:
F32 mLastReferencedSavedRawImageTime ;
F32 mKeptSavedRawImageTime ;
- //a small version of the copy of the raw image (<= 64 * 64)
- LLPointer<LLImageRaw> mCachedRawImage;
- S32 mCachedRawDiscardLevel;
- bool mCachedRawImageReady; //the rez of the mCachedRawImage reaches the upper limit.
-
LLHost mTargetHost; // if invalid, just request from agent's simulator
// Timers
diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp
index b90c1868fc..4d8fd8ddd5 100644
--- a/indra/newview/llviewertexturelist.cpp
+++ b/indra/newview/llviewertexturelist.cpp
@@ -296,7 +296,7 @@ void LLViewerTextureList::shutdown()
// Write out list of currently loaded textures for precaching on startup
typedef std::set<std::pair<S32,LLViewerFetchedTexture*> > image_area_list_t;
image_area_list_t image_area_list;
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); ++iter)
{
LLViewerFetchedTexture* image = *iter;
@@ -367,7 +367,7 @@ void LLViewerTextureList::dump()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
LL_INFOS() << "LLViewerTextureList::dump()" << LL_ENDL;
- for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it)
+ for (image_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it)
{
LLViewerFetchedTexture* image = *it;
@@ -381,15 +381,9 @@ void LLViewerTextureList::dump()
}
}
-void LLViewerTextureList::destroyGL(bool save_state)
+void LLViewerTextureList::destroyGL()
{
- LLImageGL::destroyGL(save_state);
-}
-
-void LLViewerTextureList::restoreGL()
-{
- llassert_always(mInitialized) ;
- LLImageGL::restoreGL();
+ LLImageGL::destroyGL();
}
/* Vertical tab container button image IDs
@@ -895,7 +889,7 @@ void LLViewerTextureList::clearFetchingRequests()
LLAppViewer::getTextureFetch()->deleteAllRequests();
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); ++iter)
{
LLViewerFetchedTexture* imagep = *iter;
@@ -1208,7 +1202,7 @@ void LLViewerTextureList::updateImagesUpdateStats()
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
if (mForceResetTextureStats)
{
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); )
{
LLViewerFetchedTexture* imagep = *iter++;
@@ -1228,7 +1222,7 @@ void LLViewerTextureList::decodeAllImages(F32 max_time)
// Update texture stats and priorities
std::vector<LLPointer<LLViewerFetchedTexture> > image_list;
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); )
{
LLViewerFetchedTexture* imagep = *iter++;
@@ -1248,7 +1242,7 @@ void LLViewerTextureList::decodeAllImages(F32 max_time)
image_list.clear();
// Update fetch (decode)
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); )
{
LLViewerFetchedTexture* imagep = *iter++;
@@ -1275,7 +1269,7 @@ void LLViewerTextureList::decodeAllImages(F32 max_time)
}
}
// Update fetch again
- for (image_priority_list_t::iterator iter = mImageList.begin();
+ for (image_list_t::iterator iter = mImageList.begin();
iter != mImageList.end(); )
{
LLViewerFetchedTexture* imagep = *iter++;
diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h
index e4ebb7b0e8..2779ad9f91 100644
--- a/indra/newview/llviewertexturelist.h
+++ b/indra/newview/llviewertexturelist.h
@@ -33,7 +33,7 @@
#include "llviewertexture.h"
#include "llui.h"
#include <list>
-#include <set>
+#include <unordered_set>
#include "lluiimage.h"
const U32 LL_IMAGE_REZ_LOSSLESS_CUTOFF = 128;
@@ -115,8 +115,7 @@ public:
void init();
void shutdown();
void dump();
- void destroyGL(bool save_state = true);
- void restoreGL();
+ void destroyGL();
bool isInitialized() const {return mInitialized;}
void findTexturesByID(const LLUUID &image_id, std::vector<LLViewerFetchedTexture*> &output);
@@ -188,7 +187,7 @@ private:
LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation.
S8 texture_type = LLViewerTexture::FETCHED_TEXTURE,
LLGLint internal_format = 0,
- LLGLenum primary_format = 0,
+ LLGLenum primary_format = 0,
const LLUUID& force_id = LLUUID::null
);
@@ -211,7 +210,7 @@ private:
{ return getImage(image_id, f_type, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, host); }
public:
- typedef std::set<LLPointer<LLViewerFetchedTexture> > image_list_t;
+ typedef std::unordered_set<LLPointer<LLViewerFetchedTexture> > image_list_t;
image_list_t mLoadingStreamList;
image_list_t mCreateTextureList;
image_list_t mCallbackList;
@@ -222,16 +221,19 @@ public:
bool mForceResetTextureStats;
+ // to make "for (auto& imagep : gTextureList)" work
+ const image_list_t::iterator begin() const { return mImageList.begin(); }
+ const image_list_t::iterator end() const { return mImageList.end(); }
+
private:
typedef std::map< LLTextureKey, LLPointer<LLViewerFetchedTexture> > uuid_map_t;
uuid_map_t mUUIDMap;
LLTextureKey mLastUpdateKey;
- typedef std::set < LLPointer<LLViewerFetchedTexture> > image_priority_list_t;
- image_priority_list_t mImageList;
+ image_list_t mImageList;
// simply holds on to LLViewerFetchedTexture references to stop them from being purged too soon
- std::set<LLPointer<LLViewerFetchedTexture> > mImagePreloads;
+ std::unordered_set<LLPointer<LLViewerFetchedTexture> > mImagePreloads;
bool mInitialized ;
LLFrameTimer mForceDecodeTimer;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 70aee2dc43..ef85d57416 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -1827,10 +1827,8 @@ LLViewerWindow::LLViewerWindow(const Params& p)
mToolStored( NULL ),
mHideCursorPermanent( false ),
mCursorHidden(false),
- mIgnoreActivate( false ),
mResDirty(false),
mStatesDirty(false),
- mCurrResolutionIndex(0),
mProgressView(NULL)
{
// gKeyboard is still NULL, so it doesn't do LLWindowListener any good to
@@ -1867,15 +1865,6 @@ LLViewerWindow::LLViewerWindow(const Params& p)
U32 max_core_count = gSavedSettings.getU32("EmulateCoreCount");
F32 max_gl_version = gSavedSettings.getF32("RenderMaxOpenGLVersion");
- LLControlVariable* vram_control = gSavedSettings.getControl("RenderMaxVRAMBudget");
- U32 max_vram = vram_control->getValue().asInteger();
- mMaxVRAMControlConnection = vram_control->getSignal()->connect(
- [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val)
- {
- if (mWindow) mWindow->setMaxVRAMMegabytes(new_val.asInteger());
- });
-
-
mWindow = LLWindowManager::createWindow(this,
p.title, p.name, p.x, p.y, p.width, p.height, 0,
p.fullscreen,
@@ -1885,7 +1874,6 @@ LLViewerWindow::LLViewerWindow(const Params& p)
p.ignore_pixel_depth,
0,
max_core_count,
- max_vram,
max_gl_version); //don't use window level anti-aliasing
if (NULL == mWindow)
@@ -2412,7 +2400,7 @@ void LLViewerWindow::shutdownGL()
LLSelectMgr::getInstance()->cleanup();
LL_INFOS() << "Stopping GL during shutdown" << LL_ENDL;
- stopGL(false);
+ stopGL();
stop_glerror();
gGL.shutdown();
@@ -2436,8 +2424,6 @@ LLViewerWindow::~LLViewerWindow()
LLViewerShaderMgr::releaseInstance();
LLViewerShaderMgr::sInitialized = false;
}
-
- mMaxVRAMControlConnection.disconnect();
}
@@ -5727,7 +5713,7 @@ void LLViewerWindow::dumpState()
<< LL_ENDL;
}
-void LLViewerWindow::stopGL(bool save_state)
+void LLViewerWindow::stopGL()
{
//Note: --bao
//if not necessary, do not change the order of the function calls in this function.
@@ -5773,7 +5759,7 @@ void LLViewerWindow::stopGL(bool save_state)
gPostProcess->invalidate();
}
- gTextureList.destroyGL(save_state);
+ gTextureList.destroyGL();
stop_glerror();
gGLManager.mIsDisabled = true;
@@ -5790,6 +5776,14 @@ void LLViewerWindow::stopGL(bool save_state)
void LLViewerWindow::restoreGL(const std::string& progress_message)
{
+ llassert(false);
+ // DEPRECATED -- this is left over from when we would completely destroy and restore a GL context
+ // when switching from windowed to fullscreen. None of this machinery has been exercised in years
+ // and is unreliable. If we ever *do* have another use case where completely unloading and reloading
+ // everthing is necessary, requiring a viewer restart for that operation is a fine thing to do.
+ // -- davep
+
+
//Note: --bao
//if not necessary, do not change the order of the function calls in this function.
//if change something, make sure it will not break anything.
@@ -5802,8 +5796,6 @@ void LLViewerWindow::restoreGL(const std::string& progress_message)
initGLDefaults();
LLGLState::restoreGL();
- gTextureList.restoreGL();
-
// for future support of non-square pixels, and fonts that are properly stretched
//LLFontGL::destroyDefaultFonts();
initFonts();
@@ -5879,122 +5871,6 @@ void LLViewerWindow::checkSettings()
}
}
-void LLViewerWindow::restartDisplay(bool show_progress_bar)
-{
- LL_INFOS() << "Restaring GL" << LL_ENDL;
- stopGL();
- if (show_progress_bar)
- {
- restoreGL(LLTrans::getString("ProgressChangingResolution"));
- }
- else
- {
- restoreGL();
- }
-}
-
-bool LLViewerWindow::changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar)
-{
- //bool was_maximized = gSavedSettings.getBOOL("WindowMaximized");
-
- //gResizeScreenTexture = true;
-
-
- //U32 fsaa = gSavedSettings.getU32("RenderFSAASamples");
- //U32 old_fsaa = mWindow->getFSAASamples();
-
- // if not maximized, use the request size
- if (!mWindow->getMaximized())
- {
- mWindow->setSize(size);
- }
-
- //if (fsaa == old_fsaa)
- {
- return true;
- }
-
-/*
-
- // Close floaters that don't handle settings change
- LLFloaterReg::hideInstance("snapshot");
-
- bool result_first_try = false;
- bool result_second_try = false;
-
- LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus();
- send_agent_pause();
- LL_INFOS() << "Stopping GL during changeDisplaySettings" << LL_ENDL;
- stopGL();
- mIgnoreActivate = true;
- LLCoordScreen old_size;
- LLCoordScreen old_pos;
- mWindow->getSize(&old_size);
-
- //mWindow->setFSAASamples(fsaa);
-
- result_first_try = mWindow->switchContext(false, size, disable_vsync);
- if (!result_first_try)
- {
- // try to switch back
- //mWindow->setFSAASamples(old_fsaa);
- result_second_try = mWindow->switchContext(false, old_size, disable_vsync);
-
- if (!result_second_try)
- {
- // we are stuck...try once again with a minimal resolution?
- send_agent_resume();
- mIgnoreActivate = false;
- return false;
- }
- }
- send_agent_resume();
-
- LL_INFOS() << "Restoring GL during resolution change" << LL_ENDL;
- if (show_progress_bar)
- {
- restoreGL(LLTrans::getString("ProgressChangingResolution"));
- }
- else
- {
- restoreGL();
- }
-
- if (!result_first_try)
- {
- LLSD args;
- args["RESX"] = llformat("%d",size.mX);
- args["RESY"] = llformat("%d",size.mY);
- LLNotificationsUtil::add("ResolutionSwitchFail", args);
- size = old_size; // for reshape below
- }
-
- bool success = result_first_try || result_second_try;
-
- if (success)
- {
- // maximize window if was maximized, else reposition
- if (was_maximized)
- {
- mWindow->maximize();
- }
- else
- {
- S32 windowX = gSavedSettings.getS32("WindowX");
- S32 windowY = gSavedSettings.getS32("WindowY");
-
- mWindow->setPosition(LLCoordScreen ( windowX, windowY ) );
- }
- }
-
- mIgnoreActivate = false;
- gFocusMgr.setKeyboardFocus(keyboard_focus);
-
- return success;
-
- */
-}
-
F32 LLViewerWindow::getWorldViewAspectRatio() const
{
F32 world_aspect = (F32)mWorldViewRectRaw.getWidth() / (F32)mWorldViewRectRaw.getHeight();
diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h
index 5cd6aacdb2..ba59ce4141 100644
--- a/indra/newview/llviewerwindow.h
+++ b/indra/newview/llviewerwindow.h
@@ -455,9 +455,7 @@ public:
// handle shutting down GL and bringing it back up
void requestResolutionUpdate();
void checkSettings();
- void restartDisplay(bool show_progress_bar);
- bool changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar);
- bool getIgnoreDestroyWindow() { return mIgnoreActivate; }
+
F32 getWorldViewAspectRatio() const;
const LLVector2& getDisplayScale() const { return mDisplayScale; }
void calcDisplayScale();
@@ -471,7 +469,7 @@ private:
void switchToolByMask(MASK mask);
void destroyWindow();
void drawMouselookInstructions();
- void stopGL(bool save_state = true);
+ void stopGL();
void restoreGL(const std::string& progress_message = LLStringUtil::null);
void initFonts(F32 zoom_factor = 1.f);
void schedulePick(LLPickInfo& pick_info);
@@ -528,8 +526,6 @@ private:
std::string mOverlayTitle; // Used for special titles such as "Second Life - Special E3 2003 Beta"
- bool mIgnoreActivate;
-
std::string mInitAlert; // Window / GL initialization requires an alert
LLHandle<LLView> mWorldViewPlaceholder; // widget that spans the portion of screen dedicated to rendering the 3d world
@@ -542,7 +538,6 @@ private:
bool mResDirty;
bool mStatesDirty;
- U32 mCurrResolutionIndex;
std::unique_ptr<LLWindowListener> mWindowListener;
std::unique_ptr<LLViewerWindowListener> mViewerWindowListener;
@@ -550,8 +545,6 @@ private:
// Object temporarily hovered over while dragging
LLPointer<LLViewerObject> mDragHoveredObject;
- boost::signals2::connection mMaxVRAMControlConnection;
-
static LLTrace::SampleStatHandle<> sMouseVelocityStat;
};
diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp
index ba255f2b24..f5f058a080 100644
--- a/indra/newview/llvlcomposition.cpp
+++ b/indra/newview/llvlcomposition.cpp
@@ -317,7 +317,7 @@ bool LLTerrainMaterials::makeMaterialsReady(bool boost, bool strict)
// static
bool LLTerrainMaterials::makeTextureReady(LLPointer<LLViewerFetchedTexture>& tex, bool boost)
{
- llassert(tex);
+ //llassert(tex); ??? maybe ok ???
if (!tex) { return false; }
if (tex->getDiscardLevel() < 0)
@@ -571,416 +571,6 @@ bool LLVLComposition::generateComposition()
return LLTerrainMaterials::generateMaterials();
}
-namespace
-{
- void prepare_fallback_image(LLImageRaw* raw_image)
- {
- raw_image->resize(BASE_SIZE, BASE_SIZE, 4);
- raw_image->fill(LLColor4U::white);
- }
-
- // Check if the raw image is loaded for this texture at a discard
- // level the minimap can use, and if not then try to get it loaded.
- bool prepare_raw_image(LLPointer<LLImageRaw>& raw_image, bool emissive, LLViewerFetchedTexture* tex, bool& delete_raw_post)
- {
- if (!tex)
- {
- if (!emissive)
- {
- prepare_fallback_image(raw_image);
- }
- else
- {
- llassert(!raw_image);
- raw_image = nullptr;
- }
- return true;
- }
- if (raw_image)
- {
- // Callback already initiated
- if (raw_image->getDataSize() > 0)
- {
- // Callback finished
- delete_raw_post = true;
- return true;
- }
- else
- {
- return false;
- }
- }
-
- raw_image = new LLImageRaw();
-
- S32 ddiscard = 0;
- {
- S32 min_dim = llmin(tex->getFullWidth(), tex->getFullHeight());
- while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL)
- {
- ddiscard++;
- min_dim /= 2;
- }
- }
-
- struct PendingImage
- {
- LLImageRaw* mRawImage;
- S32 mDesiredDiscard;
- LLUUID mTextureId;
- PendingImage(LLImageRaw* raw_image, S32 ddiscard, const LLUUID& texture_id)
- : mRawImage(raw_image)
- , mDesiredDiscard(ddiscard)
- , mTextureId(texture_id)
- {
- mRawImage->ref();
- }
- ~PendingImage()
- {
- mRawImage->unref();
- }
- };
- PendingImage* pending_image = new PendingImage(raw_image, ddiscard, tex->getID());
-
- loaded_callback_func cb = [](bool success, LLViewerFetchedTexture * src_vi, LLImageRaw * src, LLImageRaw * src_aux, S32 discard_level, bool is_final, void* userdata) {
- PendingImage* pending = (PendingImage*)userdata;
- // Owning LLVLComposition still exists
-
- // Assume mRawImage only used by single LLVLComposition for now
- const bool in_use_by_composition = pending->mRawImage->getNumRefs() > 1;
- llassert(pending->mRawImage->getNumRefs());
- llassert(pending->mRawImage->getNumRefs() <= 2);
- const bool needs_data = !pending->mRawImage->getDataSize();
- if (in_use_by_composition && needs_data)
- {
- if (success && pending->mDesiredDiscard == discard_level)
- {
- pending->mRawImage->resize(BASE_SIZE, BASE_SIZE, src->getComponents());
- pending->mRawImage->copyScaled(src);
- }
- else if (is_final)
- {
- prepare_fallback_image(pending->mRawImage);
- }
- }
-
- if (is_final) { delete pending; }
- };
- tex->setLoadedCallback(cb, ddiscard, true, false, pending_image, nullptr);
- tex->forceToSaveRawImage(ddiscard);
-
- return false;
- }
-};
-
-bool LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
- const F32 width, const F32 height)
-{
- LL_PROFILE_ZONE_SCOPED
- llassert(mSurfacep);
- llassert(x >= 0.f);
- llassert(y >= 0.f);
-
- ///////////////////////////
- //
- // Generate raw data arrays for surface textures
- //
- //
-
- // These have already been validated by generateComposition.
- U8* st_data[ASSET_COUNT];
- S32 st_data_size[ASSET_COUNT]; // for debugging
-
- const bool use_textures = getMaterialType() != LLTerrainMaterials::Type::PBR;
- if (use_textures)
- {
- if (!makeTexturesReady(true, true)) { return false; }
- }
- else
- {
- if (!makeMaterialsReady(true, true)) { return false; }
- }
-
- for (S32 i = 0; i < ASSET_COUNT; i++)
- {
- if (mRawImages[i].isNull())
- {
- // Read back a raw image for this discard level, if it exists
- LLViewerFetchedTexture* tex;
- LLViewerFetchedTexture* tex_emissive; // Can be null
- bool has_base_color_factor;
- bool has_emissive_factor;
- bool has_alpha;
- LLColor3 base_color_factor;
- LLColor3 emissive_factor;
- if (use_textures)
- {
- tex = mDetailTextures[i];
- tex_emissive = nullptr;
- has_base_color_factor = false;
- has_emissive_factor = false;
- has_alpha = false;
- llassert(tex);
- }
- else
- {
- LLPointer<LLFetchedGLTFMaterial>& mat = mDetailRenderMaterials[i];
- tex = mat->mBaseColorTexture;
- tex_emissive = mat->mEmissiveTexture;
- base_color_factor = LLColor3(mat->mBaseColor);
- // *HACK: Treat alpha as black
- base_color_factor *= (mat->mBaseColor.mV[VW]);
- emissive_factor = mat->mEmissiveColor;
- has_base_color_factor = (base_color_factor.mV[VX] != 1.f ||
- base_color_factor.mV[VY] != 1.f ||
- base_color_factor.mV[VZ] != 1.f);
- has_emissive_factor = (emissive_factor.mV[VX] != 1.f ||
- emissive_factor.mV[VY] != 1.f ||
- emissive_factor.mV[VZ] != 1.f);
- has_alpha = mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_OPAQUE;
- }
-
- if (!tex) { tex = LLViewerFetchedTexture::sWhiteImagep; }
-
- bool delete_raw_post = false;
- bool delete_raw_post_emissive = false;
- if (!prepare_raw_image(mRawImagesBaseColor[i], false, tex, delete_raw_post)) { return false; }
- if (tex_emissive && !prepare_raw_image(mRawImagesEmissive[i], true, tex_emissive, delete_raw_post_emissive)) { return false; }
- // tex_emissive can be null, and then will be ignored
-
- // In the simplest case, the minimap image is just the base color.
- // This will be replaced if we need to do any tinting/compositing.
- mRawImages[i] = mRawImagesBaseColor[i];
-
- // *TODO: This isn't quite right for PBR:
- // 1) It does not convert the color images from SRGB to linear
- // before mixing (which will always require copying the image).
- // 2) It mixes emissive and base color before mixing terrain
- // materials, but it should be the other way around
- // Long-term, we should consider a method that is more
- // maintainable. Shaders, perhaps? Bake shaders to textures?
- LLPointer<LLImageRaw> raw_emissive;
- if (tex_emissive)
- {
- raw_emissive = mRawImagesEmissive[i];
- if (has_emissive_factor ||
- tex_emissive->getWidth(tex_emissive->getRawImageLevel()) != BASE_SIZE ||
- tex_emissive->getHeight(tex_emissive->getRawImageLevel()) != BASE_SIZE ||
- tex_emissive->getComponents() != 4)
- {
- LLPointer<LLImageRaw> newraw_emissive = new LLImageRaw(BASE_SIZE, BASE_SIZE, 4);
- // Copy RGB, leave alpha alone (set to opaque by default)
- newraw_emissive->copy(mRawImagesEmissive[i]);
- if (has_emissive_factor)
- {
- newraw_emissive->tint(emissive_factor);
- }
- raw_emissive = newraw_emissive;
- }
- }
- if (has_base_color_factor ||
- raw_emissive ||
- has_alpha ||
- tex->getWidth(tex->getRawImageLevel()) != BASE_SIZE ||
- tex->getHeight(tex->getRawImageLevel()) != BASE_SIZE ||
- tex->getComponents() != 3)
- {
- LLPointer<LLImageRaw> newraw = new LLImageRaw(BASE_SIZE, BASE_SIZE, 3);
- if (has_alpha)
- {
- // Approximate the water underneath terrain alpha with solid water color
- newraw->clear(
- MAX_WATER_COLOR.mV[VX],
- MAX_WATER_COLOR.mV[VY],
- MAX_WATER_COLOR.mV[VZ],
- 255);
- }
- newraw->composite(mRawImagesBaseColor[i]);
- if (has_base_color_factor)
- {
- newraw->tint(base_color_factor);
- }
- // Apply emissive texture
- if (raw_emissive)
- {
- newraw->addEmissive(raw_emissive);
- }
-
- mRawImages[i] = newraw; // deletes old
- }
-
- if (delete_raw_post)
- {
- tex->destroyRawImage();
- }
- if (delete_raw_post_emissive)
- {
- tex_emissive->destroyRawImage();
- }
-
- // Remove intermediary image references
- mRawImagesBaseColor[i] = nullptr;
- mRawImagesEmissive[i] = nullptr;
- }
- st_data[i] = mRawImages[i]->getData();
- st_data_size[i] = mRawImages[i]->getDataSize();
- }
-
- ///////////////////////////////////////
- //
- // Generate and clamp x/y bounding box.
- //
- //
-
- S32 x_begin, y_begin, x_end, y_end;
- x_begin = (S32)(x * mScaleInv);
- y_begin = (S32)(y * mScaleInv);
- x_end = ll_round( (x + width) * mScaleInv );
- y_end = ll_round( (y + width) * mScaleInv );
-
- if (x_end > mWidth)
- {
- llassert(false);
- x_end = mWidth;
- }
- if (y_end > mWidth)
- {
- llassert(false);
- y_end = mWidth;
- }
-
-
- ///////////////////////////////////////////
- //
- // Generate target texture information, stride ratios.
- //
- //
-
- LLViewerTexture *texturep;
- U32 tex_width, tex_height, tex_comps;
- U32 tex_stride;
- F32 tex_x_scalef, tex_y_scalef;
- S32 tex_x_begin, tex_y_begin, tex_x_end, tex_y_end;
- F32 tex_x_ratiof, tex_y_ratiof;
-
- texturep = mSurfacep->getSTexture();
- tex_width = texturep->getWidth();
- tex_height = texturep->getHeight();
- tex_comps = texturep->getComponents();
- tex_stride = tex_width * tex_comps;
-
- U32 st_comps = 3;
- U32 st_width = BASE_SIZE;
- U32 st_height = BASE_SIZE;
-
- if (tex_comps != st_comps)
- {
- llassert(false);
- return false;
- }
-
- tex_x_scalef = (F32)tex_width / (F32)mWidth;
- tex_y_scalef = (F32)tex_height / (F32)mWidth;
- tex_x_begin = (S32)((F32)x_begin * tex_x_scalef);
- tex_y_begin = (S32)((F32)y_begin * tex_y_scalef);
- tex_x_end = (S32)((F32)x_end * tex_x_scalef);
- tex_y_end = (S32)((F32)y_end * tex_y_scalef);
-
- tex_x_ratiof = (F32)mWidth*mScale / (F32)tex_width;
- tex_y_ratiof = (F32)mWidth*mScale / (F32)tex_height;
-
- LLPointer<LLImageRaw> raw = new LLImageRaw(tex_width, tex_height, tex_comps);
- U8 *rawp = raw->getData();
-
- F32 st_x_stride, st_y_stride;
- st_x_stride = ((F32)st_width / (F32)mTexScaleX)*((F32)mWidth / (F32)tex_width);
- st_y_stride = ((F32)st_height / (F32)mTexScaleY)*((F32)mWidth / (F32)tex_height);
-
- llassert(st_x_stride > 0.f);
- llassert(st_y_stride > 0.f);
- ////////////////////////////////
- //
- // Iterate through the target texture, striding through the
- // subtextures and interpolating appropriately.
- //
- //
-
- F32 sti, stj;
- S32 st_offset;
- sti = (tex_x_begin * st_x_stride) - st_width*(llfloor((tex_x_begin * st_x_stride)/st_width));
- stj = (tex_y_begin * st_y_stride) - st_height*(llfloor((tex_y_begin * st_y_stride)/st_height));
-
- st_offset = (llfloor(stj * st_width) + llfloor(sti)) * st_comps;
- for (S32 j = tex_y_begin; j < tex_y_end; j++)
- {
- U32 offset = j * tex_stride + tex_x_begin * tex_comps;
- sti = (tex_x_begin * st_x_stride) - st_width*((U32)(tex_x_begin * st_x_stride)/st_width);
- for (S32 i = tex_x_begin; i < tex_x_end; i++)
- {
- S32 tex0, tex1;
- F32 composition = getValueScaled(i*tex_x_ratiof, j*tex_y_ratiof);
-
- tex0 = llfloor( composition );
- tex0 = llclamp(tex0, 0, 3);
- composition -= tex0;
- tex1 = tex0 + 1;
- tex1 = llclamp(tex1, 0, 3);
-
- st_offset = (lltrunc(sti) + lltrunc(stj)*st_width) * st_comps;
- for (U32 k = 0; k < tex_comps; k++)
- {
- // Linearly interpolate based on composition.
- if (st_offset >= st_data_size[tex0] || st_offset >= st_data_size[tex1])
- {
- // SJB: This shouldn't be happening, but does... Rounding error?
- //LL_WARNS() << "offset 0 [" << tex0 << "] =" << st_offset << " >= size=" << st_data_size[tex0] << LL_ENDL;
- //LL_WARNS() << "offset 1 [" << tex1 << "] =" << st_offset << " >= size=" << st_data_size[tex1] << LL_ENDL;
- }
- else
- {
- F32 a = *(st_data[tex0] + st_offset);
- F32 b = *(st_data[tex1] + st_offset);
- rawp[ offset ] = (U8)lltrunc( a + composition * (b - a) );
- }
- offset++;
- st_offset++;
- }
-
- sti += st_x_stride;
- if (sti >= st_width)
- {
- sti -= st_width;
- }
- }
-
- stj += st_y_stride;
- if (stj >= st_height)
- {
- stj -= st_height;
- }
- }
-
- if (!texturep->hasGLTexture())
- {
- texturep->createGLTexture(0, raw);
- }
- texturep->setSubImage(raw, tex_x_begin, tex_y_begin, tex_x_end - tex_x_begin, tex_y_end - tex_y_begin);
-
- // Un-boost detail textures (will get re-boosted if rendering in high detail)
- for (S32 i = 0; i < ASSET_COUNT; i++)
- {
- unboost_minimap_texture(mDetailTextures[i]);
- }
-
- // Un-boost textures for each detail material (will get re-boosted if rendering in high detail)
- for (S32 i = 0; i < ASSET_COUNT; i++)
- {
- unboost_minimap_material(mDetailMaterials[i]);
- }
-
- return true;
-}
-
F32 LLVLComposition::getStartHeight(S32 corner)
{
return mStartHeight[corner];
diff --git a/indra/newview/llvlcomposition.h b/indra/newview/llvlcomposition.h
index 61c35ade28..2637d77183 100644
--- a/indra/newview/llvlcomposition.h
+++ b/indra/newview/llvlcomposition.h
@@ -114,8 +114,6 @@ public:
// Viewer side hack to generate composition values
bool generateHeights(const F32 x, const F32 y, const F32 width, const F32 height);
bool generateComposition();
- // Generate texture from composition values.
- bool generateMinimapTileLand(const F32 x, const F32 y, const F32 width, const F32 height);
// Use these as indeces ito the get/setters below that use 'corner'
enum ECorner
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 035c02c804..4f851eabce 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -616,7 +616,8 @@ bool LLVOAvatar::sShowAnimationDebug = false;
bool LLVOAvatar::sVisibleInFirstPerson = false;
F32 LLVOAvatar::sLODFactor = 1.f;
F32 LLVOAvatar::sPhysicsLODFactor = 1.f;
-bool LLVOAvatar::sJointDebug = false;
+bool LLVOAvatar::sJointDebug = false;
+bool LLVOAvatar::sLipSyncEnabled = false;
F32 LLVOAvatar::sUnbakedTime = 0.f;
F32 LLVOAvatar::sUnbakedUpdateTime = 0.f;
F32 LLVOAvatar::sGreyTime = 0.f;
@@ -1179,6 +1180,7 @@ void LLVOAvatar::initClass()
LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged);
sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c");
+ gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&LLVOAvatar::handleVOAvatarPrefsChanged, _2));
}
@@ -1186,6 +1188,12 @@ void LLVOAvatar::cleanupClass()
{
}
+bool LLVOAvatar::handleVOAvatarPrefsChanged(const LLSD &newvalue)
+{
+ sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled");
+ return true;
+}
+
// virtual
void LLVOAvatar::initInstance()
{
@@ -2587,7 +2595,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time)
return;
}
- LLCachedControl<bool> friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false);
+ static LLCachedControl<bool> friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false);
if (friends_only()
&& !isUIAvatar()
&& !isControlAvatar()
@@ -3103,7 +3111,7 @@ void LLVOAvatar::idleUpdateLipSync(bool voice_enabled)
// Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync
if ( voice_enabled
&& mLastRezzedStatus > 0 // no point updating lip-sync for clouds
- && (LLVoiceClient::getInstance()->lipSyncEnabled())
+ && sLipSyncEnabled
&& LLVoiceClient::getInstance()->getIsSpeaking( mID ) )
{
F32 ooh_morph_amount = 0.0f;
@@ -8473,17 +8481,29 @@ bool LLVOAvatar::isTooComplex() const
bool LLVOAvatar::isTooSlow() const
{
+ if (mIsControlAvatar)
+ {
+ return mTooSlow;
+ }
+
static LLCachedControl<S32> compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode");
- bool render_friend = (LLAvatarTracker::instance().isBuddy(getID()) && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY);
+ static LLCachedControl<bool> friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false);
+ bool is_friend = LLAvatarTracker::instance().isBuddy(getID());
+ bool render_friend = is_friend && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY;
if (render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER)
{
return false;
}
- else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar)
+ else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS)
{
return true;
}
+ else if (!is_friend && friends_only())
+ {
+ return true;
+ }
+
return mTooSlow;
}
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 1ef0e6e768..1ca8b81553 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -109,6 +109,7 @@ public:
virtual void initInstance(); // Called after construction to initialize the class.
protected:
virtual ~LLVOAvatar();
+ static bool handleVOAvatarPrefsChanged(const LLSD &newvalue);
/** Initialization
** **
@@ -367,6 +368,7 @@ public:
static F32 sLODFactor; // user-settable LOD factor
static F32 sPhysicsLODFactor; // user-settable physics LOD factor
static bool sJointDebug; // output total number of joints being touched for each avatar
+ static bool sLipSyncEnabled;
static LLPointer<LLViewerTexture> sCloudTexture;
diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp
index c92de576f9..0fe2a3e714 100644
--- a/indra/newview/llvocache.cpp
+++ b/indra/newview/llvocache.cpp
@@ -540,6 +540,7 @@ bool LLVOCacheEntry::isAnyVisible(const LLVector4a& camera_origin, const LLVecto
// Honestly, the entire VOCache partition system needs to be removed since it doubles the overhead of
// the spatial partition system and is redundant to the object cache, but this is a start
// - davep 2024.06.07
+
LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)getGroup();
if(!group)
{
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 234520a544..b95e43da8d 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -39,7 +39,6 @@
#include "llcorehttputil.h"
LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap;
-LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap;
LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL;
LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL;
LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal;
@@ -89,29 +88,18 @@ LLVoiceChannel::~LLVoiceChannel()
}
sVoiceChannelMap.erase(mSessionID);
- sVoiceChannelURIMap.erase(mURI);
}
-void LLVoiceChannel::setChannelInfo(
- const std::string& uri,
- const std::string& credentials)
+void LLVoiceChannel::setChannelInfo(const LLSD &channelInfo)
{
- setURI(uri);
-
- mCredentials = credentials;
+ mChannelInfo = channelInfo;
if (mState == STATE_NO_CHANNEL_INFO)
{
- if (mURI.empty())
- {
- LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
- LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL;
- deactivate();
- }
- else if (mCredentials.empty())
+ if (mChannelInfo.isUndefined() || !mChannelInfo.isMap() || mChannelInfo.size() == 0)
{
LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs);
- LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL;
+ LL_WARNS("Voice") << "Received empty channel info for channel " << mSessionName << LL_ENDL;
deactivate();
}
else
@@ -130,9 +118,15 @@ void LLVoiceChannel::setChannelInfo(
}
}
-void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal)
+void LLVoiceChannel::onChange(EStatusType type, const LLSD& channelInfo, bool proximal)
{
- if (channelURI != mURI)
+ LL_DEBUGS("Voice") << "Incoming channel info: " << channelInfo << LL_ENDL;
+ LL_DEBUGS("Voice") << "Current channel info: " << mChannelInfo << LL_ENDL;
+ if (mChannelInfo.isUndefined() || (mChannelInfo.isMap() && mChannelInfo.size() == 0))
+ {
+ mChannelInfo = channelInfo;
+ }
+ if (!LLVoiceClient::getInstance()->compareChannels(mChannelInfo, channelInfo))
{
return;
}
@@ -158,13 +152,16 @@ void LLVoiceChannel::handleStatusChange(EStatusType type)
case STATUS_LOGGED_IN:
break;
case STATUS_LEFT_CHANNEL:
- if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended)
+ if (callStarted() && !sSuspended)
{
// if forceably removed from channel
// update the UI and revert to default channel
+ // deactivate will set the State to STATE_HUNG_UP
+ // so when handleStatusChange is called again during
+ // shutdown callStarted will return false and deactivate
+ // won't be called again.
deactivate();
}
- mIgnoreNextSessionLeave = false;
break;
case STATUS_JOINING:
if (callStarted())
@@ -193,7 +190,7 @@ void LLVoiceChannel::handleError(EStatusType type)
bool LLVoiceChannel::isActive()
{
// only considered active when currently bound channel matches what our channel
- return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI;
+ return callStarted() && LLVoiceClient::getInstance()->isCurrentChannel(mChannelInfo);
}
bool LLVoiceChannel::callStarted()
@@ -246,10 +243,8 @@ void LLVoiceChannel::activate()
// activating the proximal channel between IM calls
LLVoiceChannel* old_channel = sCurrentVoiceChannel;
sCurrentVoiceChannel = this;
- mCallDialogPayload["old_channel_name"] = "";
if (old_channel)
{
- mCallDialogPayload["old_channel_name"] = old_channel->getSessionName();
old_channel->deactivate();
}
}
@@ -257,7 +252,7 @@ void LLVoiceChannel::activate()
if (mState == STATE_NO_CHANNEL_INFO)
{
// responsible for setting status to active
- getChannelInfo();
+ requestChannelInfo();
}
else
{
@@ -270,7 +265,7 @@ void LLVoiceChannel::activate()
sCurrentVoiceChannelChangedSignal(this->mSessionID);
}
-void LLVoiceChannel::getChannelInfo()
+void LLVoiceChannel::requestChannelInfo()
{
// pretend we have everything we need
if (sCurrentVoiceChannel == this)
@@ -293,20 +288,6 @@ LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id)
}
}
-//static
-LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri)
-{
- voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri);
- if (found_it == sVoiceChannelURIMap.end())
- {
- return NULL;
- }
- else
- {
- return found_it->second;
- }
-}
-
LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel()
{
return sCurrentVoiceChannel;
@@ -319,13 +300,6 @@ void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id)
sVoiceChannelMap.insert(std::make_pair(mSessionID, this));
}
-void LLVoiceChannel::setURI(std::string uri)
-{
- sVoiceChannelURIMap.erase(mURI);
- mURI = uri;
- sVoiceChannelURIMap.insert(std::make_pair(mURI, this));
-}
-
void LLVoiceChannel::setState(EState state)
{
switch(state)
@@ -410,8 +384,11 @@ boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallbac
// LLVoiceChannelGroup
//
-LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) :
- LLVoiceChannel(session_id, session_name)
+LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID &session_id,
+ const std::string &session_name,
+ bool is_p2p) :
+ LLVoiceChannel(session_id, session_name),
+ mIsP2P(is_p2p)
{
mRetries = DEFAULT_RETRIES_COUNT;
mIsRetrying = false;
@@ -424,7 +401,15 @@ void LLVoiceChannelGroup::deactivate()
LLVoiceClient::getInstance()->leaveNonSpatialChannel();
}
LLVoiceChannel::deactivate();
-}
+
+ if (mIsP2P)
+ {
+ // void the channel info for p2p adhoc channels
+ // so we request it again, hence throwing up the
+ // connect dialogue on the other side.
+ setState(STATE_NO_CHANNEL_INFO);
+ }
+ }
void LLVoiceChannelGroup::activate()
{
@@ -435,43 +420,46 @@ void LLVoiceChannelGroup::activate()
if (callStarted())
{
// we have the channel info, just need to use it now
- LLVoiceClient::getInstance()->setNonSpatialChannel(
- mURI,
- mCredentials);
+ LLVoiceClient::getInstance()->setNonSpatialChannel(mChannelInfo,
+ mIsP2P && (mCallDirection == OUTGOING_CALL),
+ mIsP2P);
- if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel
+ if (mIsP2P)
{
- LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID);
- // Adding ad-hoc call participants to Recent People List.
- // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we
- // called(both online and offline) as source to get people for recent (STORM-210).
- if (session->isOutgoingAdHoc())
+ LLIMModel::addSpeakersToRecent(mSessionID);
+ }
+ else
+ {
+ if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel
{
- for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();
- it!=session->mInitialTargetIDs.end();++it)
+ LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(mSessionID);
+ // Adding ad-hoc call participants to Recent People List.
+ // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we
+ // called(both online and offline) as source to get people for recent (STORM-210).
+ if (session && session->isOutgoingAdHoc())
{
- const LLUUID id = *it;
- LLRecentPeople::instance().add(id);
+ for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); it != session->mInitialTargetIDs.end(); ++it)
+ {
+ const LLUUID id = *it;
+ LLRecentPeople::instance().add(id);
+ }
+ }
+ // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs
+ // would lead to EXT-8246. So in this case we get them from speakers list.
+ else
+ {
+ LLIMModel::addSpeakersToRecent(mSessionID);
}
- }
- // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs
- // would lead to EXT-8246. So in this case we get them from speakers list.
- else
- {
- LLIMModel::addSpeakersToRecent(mSessionID);
}
}
- //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls
- if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle())
- {
- LLVoiceClient::getInstance()->inputUserControlState(true);
- }
+ // Mic default state is OFF on initiating/joining Ad-Hoc/Group calls. It's on for P2P using the AdHoc infra.
+ LLVoiceClient::getInstance()->setUserPTTState(mIsP2P);
}
}
-void LLVoiceChannelGroup::getChannelInfo()
+void LLVoiceChannelGroup::requestChannelInfo()
{
LLViewerRegion* region = gAgent.getRegion();
if (region)
@@ -483,17 +471,13 @@ void LLVoiceChannelGroup::getChannelInfo()
}
}
-void LLVoiceChannelGroup::setChannelInfo(
- const std::string& uri,
- const std::string& credentials)
+void LLVoiceChannelGroup::setChannelInfo(const LLSD& channelInfo)
{
- setURI(uri);
-
- mCredentials = credentials;
+ mChannelInfo = channelInfo;
if (mState == STATE_NO_CHANNEL_INFO)
{
- if(!mURI.empty() && !mCredentials.empty())
+ if(mChannelInfo.isDefined() && mChannelInfo.isMap())
{
setState(STATE_READY);
@@ -516,9 +500,9 @@ void LLVoiceChannelGroup::setChannelInfo(
else if ( mIsRetrying )
{
// we have the channel info, just need to use it now
- LLVoiceClient::getInstance()->setNonSpatialChannel(
- mURI,
- mCredentials);
+ LLVoiceClient::getInstance()->setNonSpatialChannel(channelInfo,
+ mCallDirection == OUTGOING_CALL,
+ mIsP2P);
}
}
@@ -556,7 +540,7 @@ void LLVoiceChannelGroup::handleError(EStatusType status)
mIsRetrying = true;
mIgnoreNextSessionLeave = true;
- getChannelInfo();
+ requestChannelInfo();
return;
}
else
@@ -612,6 +596,16 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url)
LLSD postData;
postData["method"] = "call";
postData["session-id"] = mSessionID;
+ LLSD altParams;
+ std::string preferred_voice_server_type = gSavedSettings.getString("VoiceServerType");
+ if (preferred_voice_server_type.empty())
+ {
+ // default to the server type associated with the region we're on.
+ LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+ preferred_voice_server_type = versionInfo.internalVoiceServerType;
+ }
+ altParams["preferred_voice_server_type"] = preferred_voice_server_type;
+ postData["alt_params"] = altParams;
LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL;
@@ -651,14 +645,12 @@ void LLVoiceChannelGroup::voiceCallCapCoro(std::string url)
LLSD::map_const_iterator iter;
for (iter = result.beginMap(); iter != result.endMap(); ++iter)
{
- LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got "
+ LL_DEBUGS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got "
<< iter->first << LL_ENDL;
}
+ LL_INFOS("Voice") << "LLVoiceChannelGroup::voiceCallCapCoro got " << result << LL_ENDL;
- channelp->setChannelInfo(
- result["voice_credentials"]["channel_uri"].asString(),
- result["voice_credentials"]["channel_credentials"].asString());
-
+ channelp->setChannelInfo(result["voice_credentials"]);
}
@@ -684,11 +676,12 @@ void LLVoiceChannelProximal::activate()
// we're connected to a non-spatial channel, so disconnect.
LLVoiceClient::getInstance()->leaveNonSpatialChannel();
}
- LLVoiceChannel::activate();
+ LLVoiceClient::getInstance()->activateSpatialChannel(true);
+ LLVoiceChannel::activate();
}
-void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal)
+void LLVoiceChannelProximal::onChange(EStatusType type, const LLSD& channelInfo, bool proximal)
{
if (!proximal)
{
@@ -749,7 +742,7 @@ void LLVoiceChannelProximal::handleError(EStatusType status)
LLNotificationsUtil::add(notify, mNotifyArgs);
}
- LLVoiceChannel::handleError(status);
+ // proximal voice remains up and the provider will try to reconnect.
}
void LLVoiceChannelProximal::deactivate()
@@ -758,19 +751,23 @@ void LLVoiceChannelProximal::deactivate()
{
setState(STATE_HUNG_UP);
}
+ LLVoiceClient::getInstance()->removeObserver(this);
+ LLVoiceClient::getInstance()->activateSpatialChannel(false);
}
//
// LLVoiceChannelP2P
//
-LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) :
- LLVoiceChannelGroup(session_id, session_name),
- mOtherUserID(other_user_id),
- mReceivedCall(false)
+LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID &session_id,
+ const std::string &session_name,
+ const LLUUID &other_user_id,
+ LLVoiceP2POutgoingCallInterface* outgoing_call_interface) :
+ LLVoiceChannelGroup(session_id, session_name, true),
+ mOtherUserID(other_user_id),
+ mReceivedCall(false),
+ mOutgoingCallInterface(outgoing_call_interface)
{
- // make sure URI reflects encoded version of other user's agent id
- setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id));
}
void LLVoiceChannelP2P::handleStatusChange(EStatusType type)
@@ -837,23 +834,23 @@ void LLVoiceChannelP2P::activate()
if (callStarted())
{
// no session handle yet, we're starting the call
- if (mSessionHandle.empty())
+ if (mIncomingCallInterface == nullptr)
{
mReceivedCall = false;
- LLVoiceClient::getInstance()->callUser(mOtherUserID);
+ mOutgoingCallInterface->callUser(mOtherUserID);
}
// otherwise answering the call
else
{
- if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle))
+ if (!mIncomingCallInterface->answerInvite())
{
mCallEndedByAgent = false;
- mSessionHandle.clear();
+ mIncomingCallInterface.reset();
handleError(ERROR_UNKNOWN);
return;
}
- // using the session handle invalidates it. Clear it out here so we can't reuse it by accident.
- mSessionHandle.clear();
+ // using the incoming call interface invalidates it. Clear it out here so we can't reuse it by accident.
+ mIncomingCallInterface.reset();
}
// Add the party to the list of people with which we've recently interacted.
@@ -867,7 +864,17 @@ void LLVoiceChannelP2P::activate()
}
}
-void LLVoiceChannelP2P::getChannelInfo()
+void LLVoiceChannelP2P::deactivate()
+{
+ if (callStarted())
+ {
+ mOutgoingCallInterface->hangup();
+ }
+ LLVoiceChannel::deactivate();
+}
+
+
+void LLVoiceChannelP2P::requestChannelInfo()
{
// pretend we have everything we need, since P2P doesn't use channel info
if (sCurrentVoiceChannel == this)
@@ -877,8 +884,9 @@ void LLVoiceChannelP2P::getChannelInfo()
}
// receiving session from other user who initiated call
-void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)
+void LLVoiceChannelP2P::setChannelInfo(const LLSD& channel_info)
{
+ mChannelInfo = channel_info;
bool needs_activate = false;
if (callStarted())
{
@@ -893,28 +901,16 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s
{
// we are active and have priority, invite the other user again
// under the assumption they will join this new session
- mSessionHandle.clear();
- LLVoiceClient::getInstance()->callUser(mOtherUserID);
+ mOutgoingCallInterface->callUser(mOtherUserID);
return;
}
}
- mSessionHandle = handle;
-
- // The URI of a p2p session should always be the other end's SIP URI.
- if(!inURI.empty())
- {
- setURI(inURI);
- }
- else
+ mReceivedCall = true;
+ if (channel_info.isDefined() && channel_info.isMap())
{
- LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL;
- // See LLVoiceClient::sessionAddedEvent()
- setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
+ mIncomingCallInterface = LLVoiceClient::getInstance()->getIncomingCallInterface(channel_info);
}
-
- mReceivedCall = true;
-
if (needs_activate)
{
activate();
@@ -932,7 +928,7 @@ void LLVoiceChannelP2P::setState(EState state)
if (mReceivedCall && state == STATE_RINGING)
{
//TODO: remove or redirect this call status notification
-// LLCallInfoDialog::show("answering", mNotifyArgs);
+ // LLCallInfoDialog::show("answering", mNotifyArgs);
doSetState(state);
return;
}
diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h
index db176f4e50..6af1bd57a3 100644
--- a/indra/newview/llvoicechannel.h
+++ b/indra/newview/llvoicechannel.h
@@ -66,16 +66,14 @@ public:
LLVoiceChannel(const LLUUID& session_id, const std::string& session_name);
virtual ~LLVoiceChannel();
- /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
+ virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal);
virtual void handleStatusChange(EStatusType status);
virtual void handleError(EStatusType status);
virtual void deactivate();
virtual void activate();
- virtual void setChannelInfo(
- const std::string& uri,
- const std::string& credentials);
- virtual void getChannelInfo();
+ virtual void setChannelInfo(const LLSD &channelInfo);
+ virtual void requestChannelInfo();
virtual bool isActive();
virtual bool callStarted();
@@ -95,8 +93,9 @@ public:
void setCallDirection(EDirection direction) {mCallDirection = direction;}
EDirection getCallDirection() {return mCallDirection;}
+ bool isThisVoiceChannel(const LLSD &voiceChannelInfo) { return LLVoiceClient::getInstance()->compareChannels(mChannelInfo, voiceChannelInfo); }
+
static LLVoiceChannel* getChannelByID(const LLUUID& session_id);
- static LLVoiceChannel* getChannelByURI(std::string uri);
static LLVoiceChannel* getCurrentVoiceChannel();
static void initClass();
@@ -104,24 +103,21 @@ public:
static void suspend();
static void resume();
-protected:
+ protected:
virtual void setState(EState state);
/**
* Use this method if you want mStateChangedCallback to be executed while state is changed
*/
void doSetState(const EState& state);
- void setURI(std::string uri);
// there can be two directions INCOMING and OUTGOING
EDirection mCallDirection;
- std::string mURI;
- std::string mCredentials;
LLUUID mSessionID;
EState mState;
std::string mSessionName;
- LLSD mNotifyArgs;
- LLSD mCallDialogPayload;
+ LLSD mNotifyArgs;
+ LLSD mChannelInfo;
// true if call was ended by agent
bool mCallEndedByAgent;
bool mIgnoreNextSessionLeave;
@@ -130,9 +126,7 @@ protected:
typedef std::map<LLUUID, LLVoiceChannel*> voice_channel_map_t;
static voice_channel_map_t sVoiceChannelMap;
- typedef std::map<std::string, LLVoiceChannel*> voice_channel_map_uri_t;
- static voice_channel_map_uri_t sVoiceChannelURIMap;
-
+ static LLVoiceChannel* sProximalVoiceChannel;
static LLVoiceChannel* sCurrentVoiceChannel;
static LLVoiceChannel* sSuspendedVoiceChannel;
static bool sSuspended;
@@ -144,55 +138,60 @@ private:
class LLVoiceChannelGroup : public LLVoiceChannel
{
public:
- LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name);
+ LLVoiceChannelGroup(const LLUUID& session_id,
+ const std::string& session_name,
+ bool is_p2p);
+
+ void handleStatusChange(EStatusType status) override;
+ void handleError(EStatusType status) override;
+ void activate() override;
+ void deactivate() override;
+ void setChannelInfo(const LLSD &channelInfo) override;
+ void requestChannelInfo() override;
- /*virtual*/ void handleStatusChange(EStatusType status);
- /*virtual*/ void handleError(EStatusType status);
- /*virtual*/ void activate();
- /*virtual*/ void deactivate();
- /*vritual*/ void setChannelInfo(
- const std::string& uri,
- const std::string& credentials);
- /*virtual*/ void getChannelInfo();
+ bool isP2P() { return mIsP2P; }
protected:
- virtual void setState(EState state);
+ void setState(EState state) override;
private:
void voiceCallCapCoro(std::string url);
U32 mRetries;
bool mIsRetrying;
+ bool mIsP2P;
};
class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton<LLVoiceChannelProximal>
{
LLSINGLETON(LLVoiceChannelProximal);
-public:
-
- /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override;
- /*virtual*/ void handleStatusChange(EStatusType status) override;
- /*virtual*/ void handleError(EStatusType status) override;
- /*virtual*/ bool isActive() override;
- /*virtual*/ void activate() override;
- /*virtual*/ void deactivate() override;
-
+ public:
+
+ void onChange(EStatusType status, const LLSD &channelInfo, bool proximal) override;
+ void handleStatusChange(EStatusType status) override;
+ void handleError(EStatusType status) override;
+ bool isActive() override;
+ void activate() override;
+ void deactivate() override;
};
class LLVoiceChannelP2P : public LLVoiceChannelGroup
{
-public:
- LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id);
-
- /*virtual*/ void handleStatusChange(EStatusType status) override;
- /*virtual*/ void handleError(EStatusType status) override;
- /*virtual*/ void activate() override;
- /*virtual*/ void getChannelInfo() override;
-
- void setSessionHandle(const std::string& handle, const std::string &inURI);
-
-protected:
- virtual void setState(EState state) override;
+ public:
+ LLVoiceChannelP2P(const LLUUID &session_id,
+ const std::string &session_name,
+ const LLUUID &other_user_id,
+ LLVoiceP2POutgoingCallInterface * outgoing_call_interface);
+
+ void handleStatusChange(EStatusType status) override;
+ void handleError(EStatusType status) override;
+ void activate() override;
+ void requestChannelInfo() override;
+ void deactivate() override;
+ void setChannelInfo(const LLSD& channel_info) override;
+
+ protected:
+ void setState(EState state) override;
private:
@@ -201,10 +200,10 @@ private:
*
**/
void addToTheRecentPeopleList();
-
- std::string mSessionHandle;
LLUUID mOtherUserID;
bool mReceivedCall;
+ LLVoiceP2POutgoingCallInterface *mOutgoingCallInterface;
+ LLVoiceP2PIncomingCallInterfacePtr mIncomingCallInterface;
};
#endif // LL_VOICECHANNEL_H
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index 48a34efdeb..67f41b2733 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -24,13 +24,13 @@
* $/LicenseInfo$
*/
-#include "llviewerprecompiledheaders.h"
#include "llvoiceclient.h"
-#include "llviewercontrol.h"
-#include "llviewerwindow.h"
#include "llvoicevivox.h"
+#include "llvoicewebrtc.h"
#include "llviewernetwork.h"
+#include "llviewercontrol.h"
#include "llcommandhandler.h"
+#include "lldir.h"
#include "llhttpnode.h"
#include "llnotificationsutil.h"
#include "llsdserialize.h"
@@ -113,16 +113,33 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
return result;
}
-
+LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type)
+{
+ if (voice_server_type == VIVOX_VOICE_SERVER_TYPE || voice_server_type.empty())
+ {
+ return (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance();
+ }
+ else if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE)
+ {
+ return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance();
+ }
+ else
+ {
+ LLNotificationsUtil::add("VoiceVersionMismatch");
+ return nullptr;
+ }
+}
///////////////////////////////////////////////////////////////////////////////////////////////
LLVoiceClient::LLVoiceClient(LLPumpIO *pump)
:
- mVoiceModule(NULL),
+ mSpatialVoiceModule(NULL),
+ mNonSpatialVoiceModule(NULL),
m_servicePump(NULL),
- mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled", true)),
+ mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled", false)),
mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")),
+ mVoiceEffectSupportNotified(false),
mPTTDirty(true),
mPTT(true),
mUsePTT(true),
@@ -133,7 +150,6 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump)
mMuteMic(false),
mDisableMic(false)
{
- updateSettings();
init(pump);
}
@@ -142,46 +158,138 @@ LLVoiceClient::LLVoiceClient(LLPumpIO *pump)
LLVoiceClient::~LLVoiceClient()
{
- llassert(!mVoiceModule);
}
void LLVoiceClient::init(LLPumpIO *pump)
{
// Initialize all of the voice modules
m_servicePump = pump;
+ LLWebRTCVoiceClient::getInstance()->init(pump);
+ LLVivoxVoiceClient::getInstance()->init(pump);
}
void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID)
{
- // In the future, we should change this to allow voice module registration
- // with a table lookup of sorts.
- std::string voice_server = gSavedSettings.getString("VoiceServerType");
- LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL;
- if(voice_server == "vivox")
+ gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this));
+ LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID);
+ LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID);
+}
+
+void LLVoiceClient::handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures)
+{
+ std::string voiceServerType = simulatorFeatures["VoiceServerType"].asString();
+ if (voiceServerType.empty())
{
- mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance();
+ voiceServerType = VIVOX_VOICE_SERVER_TYPE;
}
- else
+
+ if (mSpatialVoiceModule && !mNonSpatialVoiceModule)
{
- mVoiceModule = NULL;
+ // stop processing if we're going to change voice modules
+ // and we're not currently in non-spatial.
+ LLVoiceVersionInfo version = mSpatialVoiceModule->getVersion();
+ if (version.internalVoiceServerType != voiceServerType)
+ {
+ mSpatialVoiceModule->processChannels(false);
+ }
+ }
+ setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString());
+
+ // if we should be in spatial voice, switch to it and set the creds
+ if (mSpatialVoiceModule && !mNonSpatialVoiceModule)
+ {
+ if (!mSpatialCredentials.isUndefined())
+ {
+ mSpatialVoiceModule->setSpatialChannel(mSpatialCredentials);
+ }
+ mSpatialVoiceModule->processChannels(true);
+ }
+}
+
+static void simulator_features_received_callback(const LLUUID& region_id)
+{
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region && (region->getRegionID() == region_id))
+ {
+ LLSD simulatorFeatures;
+ region->getSimulatorFeatures(simulatorFeatures);
+ if (LLVoiceClient::getInstance())
+ {
+ LLVoiceClient::getInstance()->handleSimulatorFeaturesReceived(simulatorFeatures);
+ }
+ }
+}
+
+void LLVoiceClient::onRegionChanged()
+{
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region && region->simulatorFeaturesReceived())
+ {
+ LLSD simulatorFeatures;
+ region->getSimulatorFeatures(simulatorFeatures);
+ if (LLVoiceClient::getInstance())
+ {
+ LLVoiceClient::getInstance()->handleSimulatorFeaturesReceived(simulatorFeatures);
+ }
+ }
+ else if (region)
+ {
+ if (mSimulatorFeaturesReceivedSlot.connected())
+ {
+ mSimulatorFeaturesReceivedSlot.disconnect();
+ }
+ mSimulatorFeaturesReceivedSlot =
+ region->setSimulatorFeaturesReceivedCallback(boost::bind(&simulator_features_received_callback, _1));
+ }
+}
+
+void LLVoiceClient::setSpatialVoiceModule(const std::string &voice_server_type)
+{
+ LLVoiceModuleInterface *module = getVoiceModule(voice_server_type);
+ if (!module)
+ {
+ return;
+ }
+ if (module != mSpatialVoiceModule)
+ {
+ if (inProximalChannel())
+ {
+ mSpatialVoiceModule->processChannels(false);
+ }
+ module->processChannels(true);
+ mSpatialVoiceModule = module;
+ mSpatialVoiceModule->updateSettings();
+ }
+}
+
+void LLVoiceClient::setNonSpatialVoiceModule(const std::string &voice_server_type)
+{
+ mNonSpatialVoiceModule = getVoiceModule(voice_server_type);
+ if (!mNonSpatialVoiceModule)
+ {
+ // we don't have a non-spatial voice module,
+ // so revert to spatial.
+ if (mSpatialVoiceModule)
+ {
+ mSpatialVoiceModule->processChannels(true);
+ }
return;
}
- mVoiceModule->init(m_servicePump);
- mVoiceModule->userAuthorized(user_id, agentID);
+ mNonSpatialVoiceModule->updateSettings();
}
void LLVoiceClient::setHidden(bool hidden)
{
- if (mVoiceModule)
+ if (mSpatialVoiceModule)
{
- mVoiceModule->setHidden(hidden);
+ mSpatialVoiceModule->setHidden(hidden);
}
}
void LLVoiceClient::terminate()
{
- if (mVoiceModule) mVoiceModule->terminate();
- mVoiceModule = NULL;
+ if (mSpatialVoiceModule) mSpatialVoiceModule->terminate();
+ mSpatialVoiceModule = NULL;
m_servicePump = NULL;
// Shutdown speaker volume storage before LLSingletonBase::deleteAll() does it
@@ -193,15 +301,15 @@ void LLVoiceClient::terminate()
const LLVoiceVersionInfo LLVoiceClient::getVersion()
{
- if (mVoiceModule)
+ if (mSpatialVoiceModule)
{
- return mVoiceModule->getVersion();
+ return mSpatialVoiceModule->getVersion();
}
else
{
LLVoiceVersionInfo result;
result.serverVersion = std::string();
- result.serverType = std::string();
+ result.voiceServerType = std::string();
result.mBuildVersion = std::string();
return result;
}
@@ -215,10 +323,8 @@ void LLVoiceClient::updateSettings()
updateMicMuteLogic();
- if (mVoiceModule)
- {
- mVoiceModule->updateSettings();
- }
+ LLWebRTCVoiceClient::getInstance()->updateSettings();
+ LLVivoxVoiceClient::getInstance()->updateSettings();
}
//--------------------------------------------------
@@ -226,117 +332,75 @@ void LLVoiceClient::updateSettings()
void LLVoiceClient::tuningStart()
{
- if (mVoiceModule) mVoiceModule->tuningStart();
+ LLWebRTCVoiceClient::getInstance()->tuningStart();
+ LLVivoxVoiceClient::getInstance()->tuningStart();
}
void LLVoiceClient::tuningStop()
{
- if (mVoiceModule) mVoiceModule->tuningStop();
+ LLWebRTCVoiceClient::getInstance()->tuningStop();
+ LLVivoxVoiceClient::getInstance()->tuningStop();
}
bool LLVoiceClient::inTuningMode()
{
- if (mVoiceModule)
- {
- return mVoiceModule->inTuningMode();
- }
- else
- {
- return false;
- }
+ return LLWebRTCVoiceClient::getInstance()->inTuningMode();
}
void LLVoiceClient::tuningSetMicVolume(float volume)
{
- if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume);
+ LLWebRTCVoiceClient::getInstance()->tuningSetMicVolume(volume);
}
void LLVoiceClient::tuningSetSpeakerVolume(float volume)
{
- if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume);
+ LLWebRTCVoiceClient::getInstance()->tuningSetSpeakerVolume(volume);
}
float LLVoiceClient::tuningGetEnergy(void)
{
- if (mVoiceModule)
- {
- return mVoiceModule->tuningGetEnergy();
- }
- else
- {
- return 0.0;
- }
+ return LLWebRTCVoiceClient::getInstance()->tuningGetEnergy();
}
-
//------------------------------------------------
// devices
bool LLVoiceClient::deviceSettingsAvailable()
{
- if (mVoiceModule)
- {
- return mVoiceModule->deviceSettingsAvailable();
- }
- else
- {
- return false;
- }
+ return LLWebRTCVoiceClient::getInstance()->deviceSettingsAvailable();
}
bool LLVoiceClient::deviceSettingsUpdated()
{
- if (mVoiceModule)
- {
- return mVoiceModule->deviceSettingsUpdated();
- }
- else
- {
- return false;
- }
+ return LLWebRTCVoiceClient::getInstance()->deviceSettingsUpdated();
}
void LLVoiceClient::refreshDeviceLists(bool clearCurrentList)
{
- if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList);
+ LLWebRTCVoiceClient::getInstance()->refreshDeviceLists(clearCurrentList);
}
void LLVoiceClient::setCaptureDevice(const std::string& name)
{
- if (mVoiceModule) mVoiceModule->setCaptureDevice(name);
-
+ LLVivoxVoiceClient::getInstance()->setCaptureDevice(name);
+ LLWebRTCVoiceClient::getInstance()->setCaptureDevice(name);
}
void LLVoiceClient::setRenderDevice(const std::string& name)
{
- if (mVoiceModule) mVoiceModule->setRenderDevice(name);
+ LLVivoxVoiceClient::getInstance()->setRenderDevice(name);
+ LLWebRTCVoiceClient::getInstance()->setRenderDevice(name);
}
const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices()
{
- static LLVoiceDeviceList nullCaptureDevices;
- if (mVoiceModule)
- {
- return mVoiceModule->getCaptureDevices();
- }
- else
- {
- return nullCaptureDevices;
- }
+ return LLWebRTCVoiceClient::getInstance()->getCaptureDevices();
}
const LLVoiceDeviceList& LLVoiceClient::getRenderDevices()
{
- static LLVoiceDeviceList nullRenderDevices;
- if (mVoiceModule)
- {
- return mVoiceModule->getRenderDevices();
- }
- else
- {
- return nullRenderDevices;
- }
+ return LLWebRTCVoiceClient::getInstance()->getRenderDevices();
}
@@ -345,214 +409,204 @@ const LLVoiceDeviceList& LLVoiceClient::getRenderDevices()
void LLVoiceClient::getParticipantList(std::set<LLUUID> &participants)
{
- if (mVoiceModule)
- {
- mVoiceModule->getParticipantList(participants);
- }
- else
- {
- participants = std::set<LLUUID>();
- }
+ LLWebRTCVoiceClient::getInstance()->getParticipantList(participants);
+ LLVivoxVoiceClient::getInstance()->getParticipantList(participants);
}
bool LLVoiceClient::isParticipant(const LLUUID &speaker_id)
{
- if(mVoiceModule)
- {
- return mVoiceModule->isParticipant(speaker_id);
- }
- return false;
+ return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id) ||
+ LLVivoxVoiceClient::getInstance()->isParticipant(speaker_id);
}
//--------------------------------------------------
// text chat
-
bool LLVoiceClient::isSessionTextIMPossible(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->isSessionTextIMPossible(id);
- }
- else
- {
- return false;
- }
+ // all sessions can do TextIM, as we no longer support PSTN
+ return true;
}
bool LLVoiceClient::isSessionCallBackPossible(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->isSessionCallBackPossible(id);
- }
- else
- {
- return false;
- }
+ // we don't support PSTN calls anymore. (did we ever?)
+ return true;
}
-/* obsolete
-bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)
+//----------------------------------------------
+// channels
+
+bool LLVoiceClient::inProximalChannel()
{
- if (mVoiceModule)
+ if (mSpatialVoiceModule)
{
- return mVoiceModule->sendTextMessage(participant_id, message);
+ return mSpatialVoiceModule->inProximalChannel();
}
else
{
return false;
}
}
-*/
-void LLVoiceClient::endUserIMSession(const LLUUID& participant_id)
+void LLVoiceClient::setNonSpatialChannel(
+ const LLSD& channelInfo,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave)
{
- if (mVoiceModule)
+ setNonSpatialVoiceModule(channelInfo["voice_server_type"].asString());
+ if (mSpatialVoiceModule && mSpatialVoiceModule != mNonSpatialVoiceModule)
{
- // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover
+ mSpatialVoiceModule->processChannels(false);
+ }
+ if (mNonSpatialVoiceModule)
+ {
+ mNonSpatialVoiceModule->processChannels(true);
+ mNonSpatialVoiceModule->setNonSpatialChannel(channelInfo, notify_on_first_join, hangup_on_last_leave);
}
}
-//----------------------------------------------
-// channels
-
-bool LLVoiceClient::inProximalChannel()
+void LLVoiceClient::setSpatialChannel(const LLSD &channelInfo)
{
- if (mVoiceModule)
+ mSpatialCredentials = channelInfo;
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region && region->simulatorFeaturesReceived())
{
- return mVoiceModule->inProximalChannel();
+ LLSD simulatorFeatures;
+ region->getSimulatorFeatures(simulatorFeatures);
+ setSpatialVoiceModule(simulatorFeatures["VoiceServerType"].asString());
}
else
{
- return false;
- }
-}
-
-void LLVoiceClient::setNonSpatialChannel(
- const std::string &uri,
- const std::string &credentials)
-{
- if (mVoiceModule)
- {
- mVoiceModule->setNonSpatialChannel(uri, credentials);
+ return;
}
-}
-void LLVoiceClient::setSpatialChannel(
- const std::string &uri,
- const std::string &credentials)
-{
- if (mVoiceModule)
+ if (mSpatialVoiceModule)
{
- mVoiceModule->setSpatialChannel(uri, credentials);
+ mSpatialVoiceModule->setSpatialChannel(channelInfo);
}
}
void LLVoiceClient::leaveNonSpatialChannel()
{
- if (mVoiceModule)
+ if (mNonSpatialVoiceModule)
{
- mVoiceModule->leaveNonSpatialChannel();
+ mNonSpatialVoiceModule->leaveNonSpatialChannel();
+ mNonSpatialVoiceModule->processChannels(false);
+ mNonSpatialVoiceModule = nullptr;
}
}
-void LLVoiceClient::leaveChannel(void)
+void LLVoiceClient::activateSpatialChannel(bool activate)
{
- if (mVoiceModule)
+ if (mSpatialVoiceModule)
{
- mVoiceModule->leaveChannel();
+ mSpatialVoiceModule->processChannels(activate);
}
}
-std::string LLVoiceClient::getCurrentChannel()
+bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getCurrentChannel();
- }
- else
- {
- return std::string();
- }
+ return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo) ||
+ LLVivoxVoiceClient::getInstance()->isCurrentChannel(channelInfo);
}
-
-//---------------------------------------
-// invitations
-
-void LLVoiceClient::callUser(const LLUUID &uuid)
+bool LLVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2)
{
- if (mVoiceModule) mVoiceModule->callUser(uuid);
+ return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2) ||
+ LLVivoxVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2);
}
-bool LLVoiceClient::isValidChannel(std::string &session_handle)
+LLVoiceP2PIncomingCallInterfacePtr LLVoiceClient::getIncomingCallInterface(const LLSD& voice_call_info)
{
- if (mVoiceModule)
+ LLVoiceModuleInterface *module = getVoiceModule(voice_call_info["voice_server_type"]);
+ if (module)
{
- return mVoiceModule->isValidChannel(session_handle);
- }
- else
- {
- return false;
+ return module->getIncomingCallInterface(voice_call_info);
}
+ return nullptr;
+
}
-bool LLVoiceClient::answerInvite(std::string &channelHandle)
+//---------------------------------------
+// outgoing calls
+LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const LLSD& voiceChannelInfo)
{
- if (mVoiceModule)
+ std::string voice_server_type = gSavedSettings.getString("VoiceServerType");
+ if (voice_server_type.empty())
{
- return mVoiceModule->answerInvite(channelHandle);
+ // default to the server type associated with the region we're on.
+ LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+ voice_server_type = versionInfo.internalVoiceServerType;
}
- else
+ if (voiceChannelInfo.has("voice_server_type") && voiceChannelInfo["voice_server_type"] != voice_server_type)
{
- return false;
+ // there's a mismatch between what the peer is offering and what our server
+ // can handle, so downgrade to vivox
+ voice_server_type = VIVOX_VOICE_SERVER_TYPE;
}
+ LLVoiceModuleInterface *module = getVoiceModule(voice_server_type);
+ return dynamic_cast<LLVoiceP2POutgoingCallInterface *>(module);
}
-void LLVoiceClient::declineInvite(std::string &channelHandle)
-{
- if (mVoiceModule) mVoiceModule->declineInvite(channelHandle);
-}
-
-
//------------------------------------------
// Volume/gain
void LLVoiceClient::setVoiceVolume(F32 volume)
{
- if (mVoiceModule) mVoiceModule->setVoiceVolume(volume);
+ LLWebRTCVoiceClient::getInstance()->setVoiceVolume(volume);
+ LLVivoxVoiceClient::getInstance()->setVoiceVolume(volume);
}
-void LLVoiceClient::setMicGain(F32 volume)
+void LLVoiceClient::setMicGain(F32 gain)
{
- if (mVoiceModule) mVoiceModule->setMicGain(volume);
+ LLWebRTCVoiceClient::getInstance()->setMicGain(gain);
+ LLVivoxVoiceClient::getInstance()->setMicGain(gain);
}
//------------------------------------------
// enable/disable voice features
-bool LLVoiceClient::voiceEnabled()
+// static
+bool LLVoiceClient::onVoiceEffectsNotSupported(const LLSD &notification, const LLSD &response)
{
- if (mVoiceModule)
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ switch (option)
{
- return mVoiceModule->voiceEnabled();
+ case 0: // "Okay"
+ gSavedSettings.setBOOL("VoiceMorphingEnabled", FALSE);
+ break;
+
+ case 1: // "Cancel"
+ break;
+
+ default:
+ llassert(0);
+ break;
}
- else
+ return false;
+}
+
+bool LLVoiceClient::voiceEnabled()
+{
+ static LLCachedControl<bool> enable_voice_chat(gSavedSettings, "EnableVoiceChat");
+ static LLCachedControl<bool> cmd_line_disable_voice(gSavedSettings, "CmdLineDisableVoice");
+ bool enabled = enable_voice_chat && !cmd_line_disable_voice && !gNonInteractive;
+ if (enabled && !mVoiceEffectSupportNotified && getVoiceEffectEnabled() && !getVoiceEffectDefault().isNull())
{
- return false;
+ LLNotificationsUtil::add("VoiceEffectsNotSupported", LLSD(), LLSD(), &LLVoiceClient::onVoiceEffectsNotSupported);
+ mVoiceEffectSupportNotified = true;
}
+ return enabled;
}
void LLVoiceClient::setVoiceEnabled(bool enabled)
{
- if (mVoiceModule)
- {
- mVoiceModule->setVoiceEnabled(enabled);
- }
+ LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled);
+ LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled);
}
void LLVoiceClient::updateMicMuteLogic()
@@ -571,34 +625,20 @@ void LLVoiceClient::updateMicMuteLogic()
// Either of these always overrides any other PTT setting.
new_mic_mute = true;
}
-
- if (mVoiceModule) mVoiceModule->setMuteMic(new_mic_mute);
-}
-
-void LLVoiceClient::setLipSyncEnabled(bool enabled)
-{
- if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled);
+ LLWebRTCVoiceClient::getInstance()->setMuteMic(new_mic_mute);
+ LLVivoxVoiceClient::getInstance()->setMuteMic(new_mic_mute);
}
-bool LLVoiceClient::lipSyncEnabled()
+void LLVoiceClient::setMuteMic(bool muted)
{
- if (mVoiceModule)
- {
- return mVoiceModule->lipSyncEnabled();
- }
- else
+ if (mMuteMic != muted)
{
- return false;
+ mMuteMic = muted;
+ updateMicMuteLogic();
+ mMicroChangedSignal();
}
}
-void LLVoiceClient::setMuteMic(bool muted)
-{
- mMuteMic = muted;
- updateMicMuteLogic();
- mMicroChangedSignal();
-}
-
// ----------------------------------------------
// PTT
@@ -675,117 +715,71 @@ void LLVoiceClient::toggleUserPTTState(void)
bool LLVoiceClient::getVoiceEnabled(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getVoiceEnabled(id);
- }
- else
- {
- return false;
- }
+ return isParticipant(id);
}
std::string LLVoiceClient::getDisplayName(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getDisplayName(id);
- }
- else
+ std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id);
+ if (result.empty())
{
- return std::string();
+ result = LLVivoxVoiceClient::getInstance()->getDisplayName(id);
}
+ return result;
}
bool LLVoiceClient::isVoiceWorking() const
{
- if (mVoiceModule)
- {
- return mVoiceModule->isVoiceWorking();
- }
- return false;
+ return LLVivoxVoiceClient::getInstance()->isVoiceWorking() ||
+ LLWebRTCVoiceClient::getInstance()->isVoiceWorking();
}
bool LLVoiceClient::isParticipantAvatar(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->isParticipantAvatar(id);
- }
- else
- {
- return false;
- }
+ return true;
}
bool LLVoiceClient::isOnlineSIP(const LLUUID& id)
{
- return false;
+ return false;
}
bool LLVoiceClient::getIsSpeaking(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getIsSpeaking(id);
- }
- else
- {
- return false;
- }
+ return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id) ||
+ LLVivoxVoiceClient::getInstance()->getIsSpeaking(id);
}
bool LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getIsModeratorMuted(id);
- }
- else
- {
- return false;
- }
+ // don't bother worrying about p2p calls, as
+ // p2p calls don't have mute.
+ return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id) ||
+ LLVivoxVoiceClient::getInstance()->getIsModeratorMuted(id);
}
F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getCurrentPower(id);
- }
- else
- {
- return 0.0;
- }
+ return std::fmax(LLVivoxVoiceClient::getInstance()->getCurrentPower(id),
+ LLWebRTCVoiceClient::getInstance()->getCurrentPower(id));
}
bool LLVoiceClient::getOnMuteList(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getOnMuteList(id);
- }
- else
- {
- return false;
- }
+ // don't bother worrying about p2p calls, as
+ // p2p calls don't have mute.
+ return LLMuteList::getInstance()->isMuted(id, LLMute::flagVoiceChat);
}
F32 LLVoiceClient::getUserVolume(const LLUUID& id)
{
- if (mVoiceModule)
- {
- return mVoiceModule->getUserVolume(id);
- }
- else
- {
- return 0.0;
- }
+ return std::fmax(LLVivoxVoiceClient::getInstance()->getUserVolume(id), LLWebRTCVoiceClient::getInstance()->getUserVolume(id));
}
void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
{
- if (mVoiceModule) mVoiceModule->setUserVolume(id, volume);
+ LLWebRTCVoiceClient::getInstance()->setUserVolume(id, volume);
+ LLVivoxVoiceClient::getInstance()->setUserVolume(id, volume);
}
//--------------------------------------------------
@@ -793,48 +787,49 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
{
- if (mVoiceModule) mVoiceModule->addObserver(observer);
+ LLVivoxVoiceClient::getInstance()->addObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->addObserver(observer);
}
void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
{
- if (mVoiceModule)
- {
- mVoiceModule->removeObserver(observer);
- }
+ LLVivoxVoiceClient::getInstance()->removeObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->removeObserver(observer);
}
void LLVoiceClient::addObserver(LLFriendObserver* observer)
{
- if (mVoiceModule) mVoiceModule->addObserver(observer);
+ LLVivoxVoiceClient::getInstance()->addObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->addObserver(observer);
}
void LLVoiceClient::removeObserver(LLFriendObserver* observer)
{
- if (mVoiceModule)
- {
- mVoiceModule->removeObserver(observer);
- }
+ LLVivoxVoiceClient::getInstance()->removeObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->removeObserver(observer);
}
void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
{
- if (mVoiceModule) mVoiceModule->addObserver(observer);
+ LLVivoxVoiceClient::getInstance()->addObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->addObserver(observer);
}
void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
{
- if (mVoiceModule)
- {
- mVoiceModule->removeObserver(observer);
- }
+ LLVivoxVoiceClient::getInstance()->removeObserver(observer);
+ LLWebRTCVoiceClient::getInstance()->removeObserver(observer);
}
std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
{
- if (mVoiceModule)
+ if (mNonSpatialVoiceModule)
+ {
+ return mNonSpatialVoiceModule->sipURIFromID(id);
+ }
+ else if (mSpatialVoiceModule)
{
- return mVoiceModule->sipURIFromID(id);
+ return mSpatialVoiceModule->sipURIFromID(id);
}
else
{
@@ -844,7 +839,7 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const
{
- return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL;
+ return NULL;
}
///////////////////
@@ -858,25 +853,46 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode
const LLSD& context,
const LLSD& input) const
{
- //You received this messsage (most likely on region cross or
- //teleport)
- if ( input.has("body") && input["body"].has("major_version") )
+ std::string voice_server_type = "vivox";
+ if (input.has("body") && input["body"].has("voice_server_type"))
+ {
+ voice_server_type = input["body"]["voice_server_type"].asString();
+ }
+
+ LLVoiceModuleInterface *voiceModule = NULL;
+
+ if (voice_server_type == "vivox" || voice_server_type.empty())
{
- int major_voice_version =
- input["body"]["major_version"].asInteger();
- // int minor_voice_version =
- // input["body"]["minor_version"].asInteger();
- LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion();
+ voiceModule = (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance();
+ }
+ else if (voice_server_type == "webrtc")
+ {
+ voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance();
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Unknown voice server type " << voice_server_type << LL_ENDL;
+ if (!sAlertedUser)
+ {
+ // sAlertedUser = true;
+ LLNotificationsUtil::add("VoiceVersionMismatch");
+ }
+ return;
+ }
- if (major_voice_version > 1)
+ LLVoiceVersionInfo versionInfo = voiceModule->getVersion();
+ if (input.has("body") && input["body"].has("major_version") &&
+ input["body"]["major_version"].asInteger() > versionInfo.majorVersion)
+ {
+ if (!sAlertedUser)
{
- if (!sAlertedUser)
- {
- //sAlertedUser = true;
- LLNotificationsUtil::add("VoiceVersionMismatch");
- gSavedSettings.setBOOL("EnableVoiceChat", false); // toggles listener
- }
+ // sAlertedUser = true;
+ LLNotificationsUtil::add("VoiceVersionMismatch");
+ LL_WARNS("Voice") << "Voice server version mismatch " << input["body"]["major_version"].asInteger() << "/"
+ << versionInfo.majorVersion
+ << LL_ENDL;
}
+ return;
}
}
};
@@ -910,21 +926,7 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode
//we believe we're in
if ( body.has("voice_credentials") )
{
- LLSD voice_credentials = body["voice_credentials"];
- std::string uri;
- std::string credentials;
-
- if ( voice_credentials.has("channel_uri") )
- {
- uri = voice_credentials["channel_uri"].asString();
- }
- if ( voice_credentials.has("channel_credentials") )
- {
- credentials =
- voice_credentials["channel_credentials"].asString();
- }
-
- LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials);
+ LLVoiceClient::getInstance()->setSpatialChannel(body["voice_credentials"]);
}
}
}
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index 9e035ef908..9ae18b315c 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -34,9 +34,11 @@ class LLVOAvatar;
#include "lliosocket.h"
#include "v3math.h"
#include "llframetimer.h"
+#include "llsingleton.h"
#include "llcallingcard.h" // for LLFriendObserver
#include "llsecapi.h"
#include "llcontrol.h"
+#include <boost/shared_ptr.hpp>
// devices
@@ -86,19 +88,54 @@ public:
} EStatusType;
virtual ~LLVoiceClientStatusObserver() { }
- virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0;
+ virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) = 0;
static std::string status2string(EStatusType inStatus);
};
struct LLVoiceVersionInfo
{
- std::string serverType;
+ std::string voiceServerType;
+ std::string internalVoiceServerType;
+ int majorVersion;
+ int minorVersion;
std::string serverVersion;
std::string mBuildVersion;
};
//////////////////////////////////
+/// @class LLVoiceP2POutgoingCallInterface
+/// @brief Outgoing call interface
+///
+/// For providers that support P2P signaling (vivox)
+/////////////////////////////////
+
+class LLVoiceP2POutgoingCallInterface
+{
+ public:
+ // initiate an outgoing call to a user
+ virtual void callUser(const LLUUID &agentID) = 0;
+ virtual void hangup() = 0;
+};
+
+//////////////////////////////////
+/// @class LLVoiceP2PIncomingCallInterface
+/// @brief Incoming call interface
+///
+/// For providers that support P2P signaling (vivox)
+/////////////////////////////////
+class LLVoiceP2PIncomingCallInterface
+{
+ public:
+ virtual ~LLVoiceP2PIncomingCallInterface() {}
+
+ virtual bool answerInvite() = 0;
+ virtual void declineInvite() = 0;
+};
+
+typedef boost::shared_ptr<LLVoiceP2PIncomingCallInterface> LLVoiceP2PIncomingCallInterfacePtr;
+
+//////////////////////////////////
/// @class LLVoiceModuleInterface
/// @brief Voice module interface
///
@@ -122,6 +159,8 @@ public:
virtual const LLVoiceVersionInfo& getVersion()=0;
+
+
/////////////////////
/// @name Tuning
//@{
@@ -165,30 +204,32 @@ public:
// Note that gestures should only fire if this returns true.
virtual bool inProximalChannel()=0;
- virtual void setNonSpatialChannel(const std::string &uri,
- const std::string &credentials)=0;
+ virtual void setNonSpatialChannel(const LLSD& channelInfo,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave)=0;
- virtual bool setSpatialChannel(const std::string &uri,
- const std::string &credentials)=0;
+ virtual bool setSpatialChannel(const LLSD& channelInfo)=0;
- virtual void leaveNonSpatialChannel()=0;
+ virtual void leaveNonSpatialChannel() = 0;
+ virtual void processChannels(bool process) = 0;
- virtual void leaveChannel(void)=0;
+ virtual bool isCurrentChannel(const LLSD &channelInfo) = 0;
+ virtual bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) = 0;
- // Returns the URI of the current channel, or an empty string if not currently in a channel.
- // NOTE that it will return an empty string if it's in the process of joining a channel.
- virtual std::string getCurrentChannel()=0;
//@}
//////////////////////////
- /// @name invitations
+ /// @name p2p
//@{
- // start a voice channel with the specified user
- virtual void callUser(const LLUUID &uuid)=0;
- virtual bool isValidChannel(std::string& channelHandle)=0;
- virtual bool answerInvite(std::string &channelHandle)=0;
- virtual void declineInvite(std::string &channelHandle)=0;
+
+ // initiate a call with a peer using the P2P interface, which only applies to some
+ // voice server types. Otherwise, a group call should be used for P2P
+ virtual LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface() = 0;
+
+ // an incoming call was received, and the incoming call dialogue is asking for an interface to
+ // answer or decline.
+ virtual LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) = 0;
//@}
/////////////////////////
@@ -201,23 +242,18 @@ public:
/////////////////////////
/// @name enable disable voice and features
//@{
- virtual bool voiceEnabled()=0;
virtual void setVoiceEnabled(bool enabled)=0;
- virtual void setLipSyncEnabled(bool enabled)=0;
- virtual bool lipSyncEnabled()=0;
virtual void setMuteMic(bool muted)=0; // Set the mute state of the local mic.
//@}
//////////////////////////
/// @name nearby speaker accessors
//@{
- virtual bool getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar
virtual std::string getDisplayName(const LLUUID& id)=0;
virtual bool isParticipantAvatar(const LLUUID &id)=0;
virtual bool getIsSpeaking(const LLUUID& id)=0;
virtual bool getIsModeratorMuted(const LLUUID& id)=0;
virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
- virtual bool getOnMuteList(const LLUUID& id)=0;
virtual F32 getUserVolume(const LLUUID& id)=0;
virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal)
//@}
@@ -228,7 +264,6 @@ public:
virtual bool isSessionTextIMPossible(const LLUUID& id)=0;
virtual bool isSessionCallBackPossible(const LLUUID& id)=0;
//virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message)=0;
- virtual void endUserIMSession(const LLUUID &uuid)=0;
//@}
// authorize the user
@@ -369,24 +404,26 @@ public:
// returns true iff the user is currently in a proximal (local spatial) channel.
// Note that gestures should only fire if this returns true.
bool inProximalChannel();
- void setNonSpatialChannel(
- const std::string &uri,
- const std::string &credentials);
- void setSpatialChannel(
- const std::string &uri,
- const std::string &credentials);
+
+ void setNonSpatialChannel(const LLSD& channelInfo,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave);
+
+ void setSpatialChannel(const LLSD &channelInfo);
+
+ void activateSpatialChannel(bool activate);
+
void leaveNonSpatialChannel();
- // Returns the URI of the current channel, or an empty string if not currently in a channel.
- // NOTE that it will return an empty string if it's in the process of joining a channel.
- std::string getCurrentChannel();
- // start a voice channel with the specified user
- void callUser(const LLUUID &uuid);
- bool isValidChannel(std::string& channelHandle);
- bool answerInvite(std::string &channelHandle);
- void declineInvite(std::string &channelHandle);
- void leaveChannel(void); // call this on logout or teleport begin
+ bool isCurrentChannel(const LLSD& channelInfo);
+
+ bool compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2);
+
+ // initiate a call with a peer using the P2P interface, which only applies to some
+ // voice server types. Otherwise, a group call should be used for P2P
+ LLVoiceP2POutgoingCallInterface* getOutgoingCallInterface(const LLSD& voiceChannelInfo = LLSD());
+ LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voiceCallInfo);
/////////////////////////////
// Sending updates of current state
@@ -396,7 +433,6 @@ public:
void setMicGain(F32 volume);
void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal)
bool voiceEnabled();
- void setLipSyncEnabled(bool enabled);
void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state.
void setUserPTTState(bool ptt);
bool getUserPTTState();
@@ -410,8 +446,6 @@ public:
void updateMicMuteLogic();
- bool lipSyncEnabled();
-
boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); }
@@ -428,8 +462,6 @@ public:
F32 getUserVolume(const LLUUID& id);
/////////////////////////////
- bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled.
- // Use this to determine whether to show a "no speech" icon in the menu bar.
void getParticipantList(std::set<LLUUID> &participants);
bool isParticipant(const LLUUID& speaker_id);
@@ -439,12 +471,15 @@ public:
bool isSessionTextIMPossible(const LLUUID& id);
bool isSessionCallBackPossible(const LLUUID& id);
//bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ;
- void endUserIMSession(const LLUUID &uuid);
//@}
+ void setSpatialVoiceModule(const std::string& voice_server_type);
+ void setNonSpatialVoiceModule(const std::string &voice_server_type);
void userAuthorized(const std::string& user_id,
- const LLUUID &agentID);
+ const LLUUID &agentID);
+
+ void onRegionChanged();
void addObserver(LLVoiceClientStatusObserver* observer);
void removeObserver(LLVoiceClientStatusObserver* observer);
@@ -464,16 +499,29 @@ public:
// Returns NULL if voice effects are not supported, or not enabled.
LLVoiceEffectInterface* getVoiceEffectInterface() const;
//@}
-private:
+
+ void handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures);
+
+ private:
+
void init(LLPumpIO *pump);
protected:
- LLVoiceModuleInterface* mVoiceModule;
+
+ static bool onVoiceEffectsNotSupported(const LLSD &notification, const LLSD &response);
+
+ LLVoiceModuleInterface* mSpatialVoiceModule;
+ LLVoiceModuleInterface* mNonSpatialVoiceModule;
+ LLSD mSpatialCredentials; // used to store spatial credentials for vivox
+ // so they're available when the region voice
+ // server is retrieved.
LLPumpIO *m_servicePump;
+ boost::signals2::connection mSimulatorFeaturesReceivedSlot;
LLCachedControl<bool> mVoiceEffectEnabled;
LLCachedControl<std::string> mVoiceEffectDefault;
+ bool mVoiceEffectSupportNotified;
bool mPTTDirty;
bool mPTT;
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 3c2a0ef6a3..371b0df860 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -80,12 +80,14 @@
extern LLMenuBarGL* gMenuBarView;
extern void handle_voice_morphing_subscribe();
+const std::string VIVOX_VOICE_SERVER_TYPE = "vivox";
+
namespace {
const F32 VOLUME_SCALE_VIVOX = 0.01f;
const F32 SPEAKING_TIMEOUT = 1.f;
- static const std::string VOICE_SERVER_TYPE = "Vivox";
+ static const std::string VISIBLE_VOICE_SERVER_TYPE = "Vivox";
// Don't retry connecting to the daemon more frequently than this:
const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f;
@@ -280,6 +282,8 @@ bool LLVivoxVoiceClient::sConnected = false;
LLPumpIO *LLVivoxVoiceClient::sPump = nullptr;
LLVivoxVoiceClient::LLVivoxVoiceClient() :
+ mWriteOffset(0),
+ mHidden(true),
mSessionTerminateRequested(false),
mRelogRequested(false),
mTerminateDaemon(false),
@@ -293,7 +297,6 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mTuningSpeakerVolumeDirty(true),
mDevicesListUpdated(false),
- mAreaVoiceDisabled(false),
mAudioSession(), // TBD - should be NULL
mAudioSessionChanged(false),
mNextAudioSession(),
@@ -325,10 +328,9 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mMicVolumeDirty(true),
mVoiceEnabled(false),
+ mProcessChannels(false),
mWriteInProgress(false),
- mLipSyncEnabled(false),
-
mVoiceFontsReceived(false),
mVoiceFontsNew(false),
mVoiceFontListDirty(false),
@@ -358,7 +360,12 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mSpeakerVolume = scale_speaker_volume(0);
mVoiceVersion.serverVersion = "";
- mVoiceVersion.serverType = VOICE_SERVER_TYPE;
+ mVoiceVersion.voiceServerType = VISIBLE_VOICE_SERVER_TYPE;
+ mVoiceVersion.internalVoiceServerType = VIVOX_VOICE_SERVER_TYPE;
+ mVoiceVersion.majorVersion = 1;
+ mVoiceVersion.minorVersion = 0;
+ mVoiceVersion.mBuildVersion = "";
+ mVoiceVersion.serverVersion = "";
// gMuteListp isn't set up at this point, so we defer this until later.
// gMuteListp->addObserver(&mutelist_listener);
@@ -455,7 +462,7 @@ const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion()
void LLVivoxVoiceClient::updateSettings()
{
- setVoiceEnabled(voiceEnabled());
+ setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled());
setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
@@ -464,7 +471,6 @@ void LLVivoxVoiceClient::updateSettings()
setRenderDevice(outputDevice);
F32 mic_level = gSavedSettings.getF32("AudioLevelMic");
setMicGain(mic_level);
- setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled"));
}
/////////////////////////////
@@ -914,7 +920,7 @@ bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data)
bool LLVivoxVoiceClient::startAndLaunchDaemon()
{
//---------------------------------------------------------------------
- if (!voiceEnabled())
+ if (!LLVoiceClient::getInstance()->voiceEnabled())
{
// Voice is locked out, we must not launch the vivox daemon.
LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL;
@@ -1169,7 +1175,9 @@ bool LLVivoxVoiceClient::provisionVoiceAccount()
do
{
LLVoiceVivoxStats::getInstance()->provisionAttemptStart();
- result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts);
+ LLSD body;
+ body["voice_server_type"] = "vivox";
+ result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts);
if (sShuttingDown)
{
@@ -1213,7 +1221,7 @@ bool LLVivoxVoiceClient::provisionVoiceAccount()
LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL;
return false;
}
-
+ LL_DEBUGS("Voice") << "Voice Provision Result." << result << LL_ENDL;
std::string voiceSipUriHostname;
std::string voiceAccountServerUri;
std::string voiceUserName = result["username"].asString();
@@ -1609,48 +1617,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo()
return false;
}
- std::string uri;
- std::string credentials;
-
- if (result.has("voice_credentials"))
- {
- LLSD voice_credentials = result["voice_credentials"];
- if (voice_credentials.has("channel_uri"))
- {
- LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL;
- uri = voice_credentials["channel_uri"].asString();
- }
- else
- {
- LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL;
- }
-
- if (voice_credentials.has("channel_credentials"))
- {
- LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL;
- credentials =
- voice_credentials["channel_credentials"].asString();
- }
- else
- {
- LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel();
- if (channel != NULL)
- {
- if (channel->getSessionName().empty() && channel->getSessionID().isNull())
- {
- if (LLViewerParcelMgr::getInstance()->allowAgentVoice())
- {
- LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL;
- }
- }
- }
- }
- else
+ if (!result.has("voice_credentials"))
{
if (LLViewerParcelMgr::getInstance()->allowAgentVoice())
{
@@ -1664,7 +1631,7 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo()
// set the spatial channel. If no voice credentials or uri are
// available, then we simply drop out of voice spatially.
- return !setSpatialChannel(uri, credentials);
+ return setSpatialChannel(result["voice_credentials"]);
}
bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
@@ -1803,7 +1770,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
}
else if ((message == "failed") || (message == "removed") || (message == "timeout"))
{ // we will get a removed message if a voice call is declined.
-
+ LL_INFOS("Voice") << "Result:" << result << LL_ENDL;
if (message == "failed")
{
int reason = result["reason"].asInteger();
@@ -1957,7 +1924,7 @@ bool LLVivoxVoiceClient::terminateAudioSession(bool wait)
<< " VoiceEnabled " << mVoiceEnabled
<< " IsInitialized " << mIsInitialized
<< " RelogRequested " << mRelogRequested
- << " ShuttingDown " << (sShuttingDown ? "true" : "false")
+ << " ShuttingDown " << (sShuttingDown ? "True" : "False")
<< " returning " << status
<< LL_ENDL;
return status;
@@ -2027,7 +1994,6 @@ bool LLVivoxVoiceClient::waitForChannel()
break;
case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING:
- mIsProcessingChannels = true;
llcoro::suspend();
state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL;
break;
@@ -2041,7 +2007,7 @@ bool LLVivoxVoiceClient::waitForChannel()
{
recordingAndPlaybackMode();
}
- else if (checkParcelChanged() || (mNextAudioSession == NULL))
+ else if (mProcessChannels && (mNextAudioSession == NULL) && checkParcelChanged())
{
// the parcel is changed, or we have no pending audio sessions,
// so try to request the parcel voice info
@@ -2056,15 +2022,23 @@ bool LLVivoxVoiceClient::waitForChannel()
}
else if (mNextAudioSession)
{
+ if (!mNextAudioSession->mIsP2P && !mProcessChannels)
+ {
+ llcoro::suspend();
+ break;
+ }
sessionStatePtr_t joinSession = mNextAudioSession;
mNextAudioSession.reset();
+ mIsProcessingChannels = true;
if (!runSession(joinSession)) //suspends
{
+ mIsProcessingChannels = false;
LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL;
break;
}
else
{
+ mIsProcessingChannels = false;
LL_DEBUGS("Voice")
<< "runSession returned true to inner loop"
<< " RelogRequested=" << mRelogRequested
@@ -2150,13 +2124,13 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
{
LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL;
- bool joined_session = addAndJoinSession(session);
-
- if (sShuttingDown)
+ if (sShuttingDown || !mProcessChannels)
{
return false;
}
+ bool joined_session = addAndJoinSession(session);
+
if (!joined_session)
{
notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
@@ -2180,12 +2154,14 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
mIsInChannel = true;
mMuteMicDirty = true;
+ mSessionTerminateRequested = false;
while (!sShuttingDown
&& mVoiceEnabled
&& isGatewayRunning()
&& !mSessionTerminateRequested
- && !mTuningMode)
+ && !mTuningMode
+ && mProcessChannels)
{
sendCaptureAndRenderDevices(); // suspends
@@ -2437,95 +2413,11 @@ int LLVivoxVoiceClient::voicePlaybackBuffer()
bool LLVivoxVoiceClient::performMicTuning()
{
LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL;
-
mIsInTuningMode = true;
- llcoro::suspend();
while (mTuningMode && !sShuttingDown)
{
-
- if (mCaptureDeviceDirty || mRenderDeviceDirty)
- {
- // These can't be changed while in tuning mode. Set them before starting.
- std::ostringstream stream;
-
- buildSetCaptureDevice(stream);
- buildSetRenderDevice(stream);
-
- if (!stream.str().empty())
- {
- writeString(stream.str());
- }
-
- llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
- }
-
- // loop mic back to render device.
- //setMuteMic(0); // make sure the mic is not muted
- std::ostringstream stream;
-
- stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
- << "<ConnectorHandle>" << LLVivoxSecurity::getInstance()->connectorHandle() << "</ConnectorHandle>"
- << "<Value>false</Value>"
- << "</Request>\n\n\n";
-
- // Dirty the mute mic state so that it will get reset when we finishing previewing
- mMuteMicDirty = true;
- mTuningSpeakerVolumeDirty = true;
-
- writeString(stream.str());
- tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop
-
- //---------------------------------------------------------------------
- if (!sShuttingDown)
- {
- llcoro::suspend();
- }
-
- while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown)
- {
- // process mic/speaker volume changes
- if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty)
- {
- std::ostringstream stream;
-
- if (mTuningMicVolumeDirty)
- {
- LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
- << "<Level>" << mTuningMicVolume << "</Level>"
- << "</Request>\n\n\n";
- }
-
- if (mTuningSpeakerVolumeDirty)
- {
- LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL;
- stream
- << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
- << "<Level>" << mTuningSpeakerVolume << "</Level>"
- << "</Request>\n\n\n";
- }
-
- mTuningMicVolumeDirty = false;
- mTuningSpeakerVolumeDirty = false;
-
- if (!stream.str().empty())
- {
- writeString(stream.str());
- }
- }
- llcoro::suspend();
- }
-
- //---------------------------------------------------------------------
-
- // transition out of mic tuning
- tuningCaptureStopSendMessage();
- if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown)
- {
- llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
- }
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
}
mIsInTuningMode = false;
@@ -2651,6 +2543,7 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &sessi
<< "<VoiceFontID>" << font_index << "</VoiceFontID>"
<< "<Name>" << mChannelName << "</Name>"
<< "</Request>\n\n\n";
+ LL_WARNS("Voice") << "Session.Create: " << stream.str() << LL_ENDL;
writeString(stream.str());
}
@@ -2950,6 +2843,9 @@ void LLVivoxVoiceClient::tuningStart()
void LLVivoxVoiceClient::tuningStop()
{
mTuningMode = false;
+ // force a renegotiation.
+ mCurrentParcelLocalID = 0;
+ mCurrentRegionName = "";
}
bool LLVivoxVoiceClient::inTuningMode()
@@ -4830,7 +4726,7 @@ void LLVivoxVoiceClient::sessionState::VerifySessions()
void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants)
{
- if(mAudioSession)
+ if(mProcessChannels && mAudioSession)
{
for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin();
iter != mAudioSession->mParticipantsByUUID.end();
@@ -4843,11 +4739,11 @@ void LLVivoxVoiceClient::getParticipantList(std::set<LLUUID> &participants)
bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id)
{
- if(mAudioSession)
+ if(mProcessChannels && mAudioSession)
{
- return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end());
+ return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end());
}
- return false;
+ return false;
}
@@ -4951,8 +4847,10 @@ bool LLVivoxVoiceClient::switchChannel(
// If a terminate has been requested, we need to compare against where the URI we're already headed to.
if(mNextAudioSession)
{
- if(mNextAudioSession->mSIPURI != uri)
+ if (mNextAudioSession->mSIPURI != uri)
+ {
needsSwitch = true;
+ }
}
else
{
@@ -5042,22 +4940,23 @@ void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session)
}
}
-void LLVivoxVoiceClient::setNonSpatialChannel(
- const std::string &uri,
- const std::string &credentials)
+void LLVivoxVoiceClient::setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave)
{
- switchChannel(uri, false, false, false, credentials);
+ switchChannel(channelInfo["channel_uri"].asString(), false, false, false, channelInfo["channel_credentials"].asString());
}
-bool LLVivoxVoiceClient::setSpatialChannel(
- const std::string &uri,
- const std::string &credentials)
+bool LLVivoxVoiceClient::setSpatialChannel(const LLSD& channelInfo)
{
- mSpatialSessionURI = uri;
- mSpatialSessionCredentials = credentials;
- mAreaVoiceDisabled = mSpatialSessionURI.empty();
+ mSpatialSessionURI = channelInfo["channel_uri"].asString();
+ mSpatialSessionCredentials = channelInfo["channel_credentials"].asString();
+ if (!mProcessChannels)
+ {
+ // we're not even processing channels (another provider is) so
+ // save the credentials aside and exit
+ return false;
+ }
- LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
+ LL_DEBUGS("Voice") << "got spatial channel uri: \"" << mSpatialSessionURI << "\"" << LL_ENDL;
if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial)))
{
@@ -5074,84 +4973,30 @@ bool LLVivoxVoiceClient::setSpatialChannel(
void LLVivoxVoiceClient::callUser(const LLUUID &uuid)
{
std::string userURI = sipURIFromID(uuid);
+ mProcessChannels = true;
switchChannel(userURI, false, true, true);
}
-#if 0
-// Vivox text IMs are not in use.
-LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid)
-{
- // Figure out if a session with the user already exists
- sessionStatePtr_t session(findSession(uuid));
- if(!session)
- {
- // No session with user, need to start one.
- std::string uri = sipURIFromID(uuid);
- session = addSession(uri);
-
- llassert(session);
- if (!session)
- return session;
-
- session->mIsSpatial = false;
- session->mReconnect = false;
- session->mIsP2P = true;
- session->mCallerID = uuid;
- }
-
- if(session->mHandle.empty())
- {
- // Session isn't active -- start it up.
- sessionCreateSendMessage(session, false, false);
- }
- else
- {
- // Session is already active -- start up text.
- sessionTextConnectSendMessage(session);
- }
+void LLVivoxVoiceClient::hangup() { leaveChannel(); }
- return session;
-}
-#endif
-void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid)
+LLVoiceP2PIncomingCallInterfacePtr LLVivoxVoiceClient::getIncomingCallInterface(const LLSD &voice_call_info)
{
-#if 0
- // Vivox text IMs are not in use.
-
- // Figure out if a session with the user exists
- sessionStatePtr_t session(findSession(uuid));
- if(session)
- {
- // found the session
- if(!session->mHandle.empty())
- {
- // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more.
- }
- }
- else
- {
- LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL;
- }
-#endif
+ return boost::make_shared<LLVivoxVoiceP2PIncomingCall>(voice_call_info);
}
-bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle)
-{
- return(findSession(sessionHandle) != NULL);
-}
-bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle)
+bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle)
{
// this is only ever used to answer incoming p2p call invites.
sessionStatePtr_t session(findSession(sessionHandle));
- if(session)
+ if (session)
{
session->mIsSpatial = false;
session->mReconnect = false;
- session->mIsP2P = true;
-
+ session->mIsP2P = true;
+ mProcessChannels = true;
joinSession(session);
return true;
}
@@ -5159,6 +5004,15 @@ bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle)
return false;
}
+void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle)
+{
+ sessionStatePtr_t session(findSession(sessionHandle));
+ if (session)
+ {
+ sessionMediaDisconnectSendMessage(session);
+ }
+}
+
bool LLVivoxVoiceClient::isVoiceWorking() const
{
@@ -5228,16 +5082,6 @@ bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id)
return result;
}
-
-void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle)
-{
- sessionStatePtr_t session(findSession(sessionHandle));
- if(session)
- {
- sessionMediaDisconnectSendMessage(session);
- }
-}
-
void LLVivoxVoiceClient::leaveNonSpatialChannel()
{
LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL;
@@ -5254,16 +5098,41 @@ void LLVivoxVoiceClient::leaveNonSpatialChannel()
sessionTerminate();
}
-std::string LLVivoxVoiceClient::getCurrentChannel()
+void LLVivoxVoiceClient::processChannels(bool process)
{
- std::string result;
+ mCurrentParcelLocalID = -1;
+ mCurrentRegionName.clear();
+ mProcessChannels = process;
+}
- if (mIsInChannel && !mSessionTerminateRequested)
+bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo)
+{
+ if (!mProcessChannels || (channelInfo.has("voice_server_type") && channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE))
{
- result = getAudioSessionURI();
+ return false;
+ }
+ // favor the next audio session, as that's the one we're bringing up.
+ sessionStatePtr_t session = mNextAudioSession;
+ if (!session)
+ {
+ session = mAudioSession;
}
+ if (session)
+ {
+ if (!channelInfo["session_handle"].asString().empty())
+ {
+ return session->mHandle == channelInfo["session_handle"].asString();
+ }
+ return channelInfo["channel_uri"].asString() == session->mSIPURI;
+ }
+ return false;
+}
- return result;
+bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2)
+{
+ return (!channelInfo1.has("voice_server_type") || (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) &&
+ (!channelInfo2.has("voice_server_type") || (channelInfo2["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) &&
+ (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]);
}
bool LLVivoxVoiceClient::inProximalChannel()
@@ -5303,16 +5172,6 @@ std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
return result;
}
-std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
-{
- std::string result;
- if(avatar)
- {
- result = nameFromID(avatar->getID());
- }
- return result;
-}
-
std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid)
{
std::string result;
@@ -5387,11 +5246,6 @@ bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid)
return result;
}
-std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
-{
- return avatar->getFullname();
-}
-
std::string LLVivoxVoiceClient::sipURIFromName(std::string &name)
{
std::string result;
@@ -5432,12 +5286,15 @@ bool LLVivoxVoiceClient::inSpatialChannel(void)
return result;
}
-std::string LLVivoxVoiceClient::getAudioSessionURI()
+
+LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo()
{
- std::string result;
+ LLSD result;
- if(mAudioSession)
- result = mAudioSession->mSIPURI;
+ if (mAudioSession)
+ {
+ result = mAudioSession->getVoiceChannelInfo();
+ }
return result;
}
@@ -5629,6 +5486,8 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
LLVoiceChannel::getCurrentVoiceChannel()->deactivate();
gAgent.setVoiceConnected(false);
status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED;
+ mCurrentParcelLocalID = -1;
+ mCurrentRegionName.clear();
}
notifyStatusObservers(status);
@@ -5639,31 +5498,6 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
}
}
-bool LLVivoxVoiceClient::voiceEnabled()
-{
- return gSavedSettings.getBOOL("EnableVoiceChat") &&
- !gSavedSettings.getBOOL("CmdLineDisableVoice") &&
- !gNonInteractive;
-}
-
-void LLVivoxVoiceClient::setLipSyncEnabled(bool enabled)
-{
- mLipSyncEnabled = enabled;
-}
-
-bool LLVivoxVoiceClient::lipSyncEnabled()
-{
-
- if ( mVoiceEnabled )
- {
- return mLipSyncEnabled;
- }
- else
- {
- return false;
- }
-}
-
void LLVivoxVoiceClient::setEarLocation(S32 loc)
{
@@ -5706,27 +5540,17 @@ void LLVivoxVoiceClient::setMicGain(F32 volume)
/////////////////////////////
// Accessors for data related to nearby speakers
-bool LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id)
-{
- bool result = false;
- participantStatePtr_t participant(findParticipantByID(id));
- if(participant)
- {
- // I'm not sure what the semantics of this should be.
- // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
- result = true;
- }
-
- return result;
-}
std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id)
{
std::string result;
- participantStatePtr_t participant(findParticipantByID(id));
- if(participant)
+ if (mProcessChannels)
{
- result = participant->mDisplayName;
+ participantStatePtr_t participant(findParticipantByID(id));
+ if (participant)
+ {
+ result = participant->mDisplayName;
+ }
}
return result;
@@ -5737,15 +5561,17 @@ std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id)
bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id)
{
bool result = false;
-
- participantStatePtr_t participant(findParticipantByID(id));
- if(participant)
+ if (mProcessChannels)
{
- if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+ participantStatePtr_t participant(findParticipantByID(id));
+ if (participant)
{
- participant->mIsSpeaking = false;
+ if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+ {
+ participant->mIsSpeaking = false;
+ }
+ result = participant->mIsSpeaking;
}
- result = participant->mIsSpeaking;
}
return result;
@@ -5754,7 +5580,10 @@ bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id)
bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id)
{
bool result = false;
-
+ if (!mProcessChannels)
+ {
+ return false;
+ }
participantStatePtr_t participant(findParticipantByID(id));
if(participant)
{
@@ -5793,19 +5622,6 @@ bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id)
return result;
}
-bool LLVivoxVoiceClient::getOnMuteList(const LLUUID& id)
-{
- bool result = false;
-
- participantStatePtr_t participant(findParticipantByID(id));
- if(participant)
- {
- result = participant->mOnMuteList;
- }
-
- return result;
-}
-
// External accessors.
F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id)
{
@@ -5863,11 +5679,6 @@ std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id)
return result;
}
-bool LLVivoxVoiceClient::getAreaVoiceDisabled()
-{
- return mAreaVoiceDisabled;
-}
-
void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
{
// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
@@ -5994,6 +5805,18 @@ LLVivoxVoiceClient::sessionState::sessionState() :
{
}
+LLSD LLVivoxVoiceClient::sessionState::getVoiceChannelInfo()
+{
+ LLSD result;
+
+ result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE;
+ result["channel_credentials"] = mHash;
+ result["channel_uri"] = mSIPURI;
+ result["session_handle"] = mHandle;
+
+ return result;
+}
+
/*static*/
LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession()
{
@@ -6440,40 +6263,41 @@ void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::ESta
LL_DEBUGS("Voice")
<< " " << LLVoiceClientStatusObserver::status2string(status)
- << ", session URI " << getAudioSessionURI()
+ << ", session channelInfo " << getAudioSessionChannelInfo()
<< ", proximal is " << inSpatialChannel()
<< LL_ENDL;
- // this function is called from a coroutine, shuttle application hook back to main loop
- auto work = [=]()
- {
- for (status_observer_set_t::iterator it = mStatusObservers.begin();
- it != mStatusObservers.end();
- )
- {
- LLVoiceClientStatusObserver* observer = *it;
- observer->onChange(status, getAudioSessionURI(), inSpatialChannel());
- // In case onError() deleted an entry.
- it = mStatusObservers.upper_bound(observer);
- }
-
- // skipped to avoid speak button blinking
- if (status != LLVoiceClientStatusObserver::STATUS_JOINING
- && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL
- && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED)
- {
- bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
+ if (!mProcessChannels)
+ {
+ // we're not processing...another voice module is.
+ // so nobody wants to hear from us.
+ return;
+ }
+ for (status_observer_set_t::iterator it = mStatusObservers.begin();
+ it != mStatusObservers.end();
+ )
+ {
+ LLVoiceClientStatusObserver* observer = *it;
+ observer->onChange(status, getAudioSessionChannelInfo(), inSpatialChannel());
+ // In case onError() deleted an entry.
+ it = mStatusObservers.upper_bound(observer);
+ }
- gAgent.setVoiceConnected(voice_status);
+ // skipped to avoid speak button blinking
+ if ( status != LLVoiceClientStatusObserver::STATUS_JOINING
+ && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL
+ && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED)
+ {
+ bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
- if (voice_status)
- {
- LLFirstUse::speak(true);
- }
- }
- };
+ LL_WARNS("Voice") << "Setting voice connected " << (voice_status ? "True" : "False") << LL_ENDL;
+ gAgent.setVoiceConnected(voice_status);
- LLAppViewer::instance()->postToMainCoro(work);
+ if (voice_status)
+ {
+ LLAppViewer::instance()->postToMainCoro([=]() { LLFirstUse::speak(true); });
+ }
+ }
}
void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer)
@@ -6541,7 +6365,6 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess
if (session->mVoiceInvitePending)
{
session->mVoiceInvitePending = false;
-
LLIMMgr::getInstance()->inviteToSession(
session->mIMSessionID,
session->mName,
@@ -6549,10 +6372,8 @@ void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sess
session->mName,
IM_SESSION_P2P_INVITE,
LLIMMgr::INVITATION_TYPE_VOICE,
- session->mHandle,
- session->mSIPURI);
+ session->getVoiceChannelInfo());
}
-
}
}
@@ -7409,6 +7230,7 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
XML_SetUserData(parser, this);
XML_Parse(parser, mInput.data() + start, static_cast<int>(delim - start), false);
+
LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
start = delim + 3;
}
@@ -8106,3 +7928,7 @@ LLVivoxSecurity::LLVivoxSecurity()
LLVivoxSecurity::~LLVivoxSecurity()
{
}
+
+bool LLVivoxVoiceP2PIncomingCall::answerInvite() { return LLVivoxVoiceClient::getInstance()->answerInvite(mCallInfo["session_handle"]); }
+
+void LLVivoxVoiceP2PIncomingCall::declineInvite() { LLVivoxVoiceClient::getInstance()->declineInvite(mCallInfo["session_handle"]); }
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index dde3a46514..420e0aa869 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -51,10 +51,25 @@ class LLVivoxProtocolParser;
class LLAvatarName;
class LLVivoxVoiceClientMuteListObserver;
+extern const std::string VIVOX_VOICE_SERVER_TYPE;
+
+class LLVivoxVoiceP2PIncomingCall : public LLVoiceP2PIncomingCallInterface
+{
+ public:
+ LLVivoxVoiceP2PIncomingCall(const LLSD& call_info) : mCallInfo(call_info) {}
+ ~LLVivoxVoiceP2PIncomingCall() override {}
+
+ bool answerInvite() override;
+ void declineInvite() override;
+
+ protected:
+ LLSD mCallInfo;
+};
class LLVivoxVoiceClient : public LLSingleton<LLVivoxVoiceClient>,
virtual public LLVoiceModuleInterface,
- virtual public LLVoiceEffectInterface
+ virtual public LLVoiceEffectInterface,
+ virtual public LLVoiceP2POutgoingCallInterface
{
LLSINGLETON(LLVivoxVoiceClient);
LOG_CLASS(LLVivoxVoiceClient);
@@ -64,26 +79,27 @@ public:
/// @name LLVoiceModuleInterface virtual implementations
/// @see LLVoiceModuleInterface
//@{
- virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector)
- virtual void terminate() override; // Call this to clean up during shutdown
+ void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector)
+ void terminate() override; // Call this to clean up during shutdown
- virtual const LLVoiceVersionInfo& getVersion() override;
+ const LLVoiceVersionInfo& getVersion() override;
- virtual void updateSettings() override; // call after loading settings and whenever they change
+ void updateSettings() override; // call after loading settings and whenever they change
// Returns true if vivox has successfully logged in and is not in error state
- virtual bool isVoiceWorking() const override;
+ bool isVoiceWorking() const override;
/////////////////////
/// @name Tuning
//@{
- virtual void tuningStart() override;
- virtual void tuningStop() override;
- virtual bool inTuningMode() override;
+ void tuningStart() override;
+ void tuningStop() override;
+ bool inTuningMode() override;
+
+ void tuningSetMicVolume(float volume) override;
+ void tuningSetSpeakerVolume(float volume) override;
+ float tuningGetEnergy(void) override;
- virtual void tuningSetMicVolume(float volume) override;
- virtual void tuningSetSpeakerVolume(float volume) override;
- virtual float tuningGetEnergy(void) override;
//@}
/////////////////////
@@ -91,122 +107,121 @@ public:
//@{
// This returns true when it's safe to bring up the "device settings" dialog in the prefs.
// i.e. when the daemon is running and connected, and the device lists are populated.
- virtual bool deviceSettingsAvailable() override;
- virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel.
+ bool deviceSettingsAvailable() override;
+ bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel.
// Requery the vivox daemon for the current list of input/output devices.
// If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
// (use this if you want to know when it's done).
// If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
- virtual void refreshDeviceLists(bool clearCurrentList = true) override;
+ void refreshDeviceLists(bool clearCurrentList = true) override;
- virtual void setCaptureDevice(const std::string& name) override;
- virtual void setRenderDevice(const std::string& name) override;
+ void setCaptureDevice(const std::string& name) override;
+ void setRenderDevice(const std::string& name) override;
- virtual LLVoiceDeviceList& getCaptureDevices() override;
- virtual LLVoiceDeviceList& getRenderDevices() override;
+ LLVoiceDeviceList& getCaptureDevices() override;
+ LLVoiceDeviceList& getRenderDevices() override;
//@}
- virtual void getParticipantList(std::set<LLUUID> &participants) override;
- virtual bool isParticipant(const LLUUID& speaker_id) override;
+ void getParticipantList(std::set<LLUUID> &participants) override;
+ bool isParticipant(const LLUUID& speaker_id) override;
// Send a text message to the specified user, initiating the session if necessary.
// virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;};
- // close any existing text IM session with the specified user
- virtual void endUserIMSession(const LLUUID &uuid) override;
-
// Returns true if calling back the session URI after the session has closed is possible.
// Currently this will be false only for PSTN P2P calls.
// NOTE: this will return true if the session can't be found.
- virtual bool isSessionCallBackPossible(const LLUUID &session_id) override;
+ bool isSessionCallBackPossible(const LLUUID &session_id) override;
// Returns true if the session can accepte text IM's.
// Currently this will be false only for PSTN P2P calls.
// NOTE: this will return true if the session can't be found.
- virtual bool isSessionTextIMPossible(const LLUUID &session_id) override;
-
+ bool isSessionTextIMPossible(const LLUUID &session_id) override;
////////////////////////////
/// @name Channel stuff
//@{
// returns true iff the user is currently in a proximal (local spatial) channel.
// Note that gestures should only fire if this returns true.
- virtual bool inProximalChannel() override;
+ bool inProximalChannel() override;
+
+ void setNonSpatialChannel(const LLSD& channelInfo,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave) override;
- virtual void setNonSpatialChannel(const std::string &uri,
- const std::string &credentials) override;
+ bool setSpatialChannel(const LLSD& channelInfo) override;
- virtual bool setSpatialChannel(const std::string &uri,
- const std::string &credentials) override;
+ void leaveNonSpatialChannel() override;
- virtual void leaveNonSpatialChannel() override;
+ void processChannels(bool process) override;
- virtual void leaveChannel(void) override;
+ void leaveChannel(void);
+
+ bool isCurrentChannel(const LLSD &channelInfo) override;
+ bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override;
- // Returns the URI of the current channel, or an empty string if not currently in a channel.
- // NOTE that it will return an empty string if it's in the process of joining a channel.
- virtual std::string getCurrentChannel() override;
//@}
//////////////////////////
- /// @name invitations
+ /// @name LLVoiceP2POutgoingCallInterface
//@{
// start a voice channel with the specified user
- virtual void callUser(const LLUUID &uuid) override;
- virtual bool isValidChannel(std::string &channelHandle) override;
- virtual bool answerInvite(std::string &channelHandle) override;
- virtual void declineInvite(std::string &channelHandle) override;
+ void callUser(const LLUUID &uuid) override;
+ void hangup() override;
+
//@}
+ LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return this; }
+
+ LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override;
+
+ bool answerInvite(const std::string &sessionHandle);
+ void declineInvite(const std::string &sessionHandle);
+
/////////////////////////
/// @name Volume/gain
//@{
- virtual void setVoiceVolume(F32 volume) override;
- virtual void setMicGain(F32 volume) override;
+ void setVoiceVolume(F32 volume) override;
+ void setMicGain(F32 volume) override;
//@}
/////////////////////////
/// @name enable disable voice and features
//@{
- virtual bool voiceEnabled() override;
- virtual void setVoiceEnabled(bool enabled) override;
- virtual bool lipSyncEnabled() override;
- virtual void setLipSyncEnabled(bool enabled) override;
- virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic.
+ void setVoiceEnabled(bool enabled) override;
+ void setMuteMic(bool muted) override; // Set the mute state of the local mic.
//@}
//////////////////////////
/// @name nearby speaker accessors
//@{
- virtual bool getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar
- virtual std::string getDisplayName(const LLUUID& id) override;
- virtual bool isParticipantAvatar(const LLUUID &id) override;
- virtual bool getIsSpeaking(const LLUUID& id) override;
- virtual bool getIsModeratorMuted(const LLUUID& id) override;
- virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
- virtual bool getOnMuteList(const LLUUID& id) override;
- virtual F32 getUserVolume(const LLUUID& id) override;
- virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal)
+ std::string getDisplayName(const LLUUID& id) override;
+ bool isParticipantAvatar(const LLUUID &id) override;
+ bool getIsSpeaking(const LLUUID& id) override;
+ bool getIsModeratorMuted(const LLUUID& id) override;
+ F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
+ F32 getUserVolume(const LLUUID& id) override;
+ void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal)
//@}
// authorize the user
- virtual void userAuthorized(const std::string& user_id,
- const LLUUID &agentID) override;
+ void userAuthorized(const std::string& user_id,
+ const LLUUID &agentID) override;
//////////////////////////////
/// @name Status notification
//@{
- virtual void addObserver(LLVoiceClientStatusObserver* observer) override;
- virtual void removeObserver(LLVoiceClientStatusObserver* observer) override;
- virtual void addObserver(LLFriendObserver* observer) override;
- virtual void removeObserver(LLFriendObserver* observer) override;
- virtual void addObserver(LLVoiceClientParticipantObserver* observer) override;
- virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override;
+ void addObserver(LLVoiceClientStatusObserver* observer) override;
+ void removeObserver(LLVoiceClientStatusObserver* observer) override;
+ void addObserver(LLFriendObserver* observer) override;
+ void removeObserver(LLFriendObserver* observer) override;
+ void addObserver(LLVoiceClientParticipantObserver* observer) override;
+ void removeObserver(LLVoiceClientParticipantObserver* observer) override;
//@}
- virtual std::string sipURIFromID(const LLUUID &id) override;
+ std::string sipURIFromID(const LLUUID &id) override;
//@}
/// @name LLVoiceEffectInterface virtual implementations
@@ -216,32 +231,32 @@ public:
//////////////////////////
/// @name Accessors
//@{
- virtual bool setVoiceEffect(const LLUUID& id) override;
- virtual const LLUUID getVoiceEffect() override;
- virtual LLSD getVoiceEffectProperties(const LLUUID& id) override;
+ bool setVoiceEffect(const LLUUID& id) override;
+ const LLUUID getVoiceEffect() override;
+ LLSD getVoiceEffectProperties(const LLUUID& id) override;
- virtual void refreshVoiceEffectLists(bool clear_lists) override;
- virtual const voice_effect_list_t& getVoiceEffectList() const override;
- virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override;
+ void refreshVoiceEffectLists(bool clear_lists) override;
+ const voice_effect_list_t& getVoiceEffectList() const override;
+ const voice_effect_list_t& getVoiceEffectTemplateList() const override;
//@}
//////////////////////////////
/// @name Status notification
//@{
- virtual void addObserver(LLVoiceEffectObserver* observer) override;
- virtual void removeObserver(LLVoiceEffectObserver* observer) override;
+ void addObserver(LLVoiceEffectObserver* observer) override;
+ void removeObserver(LLVoiceEffectObserver* observer) override;
//@}
//////////////////////////////
/// @name Effect preview buffer
//@{
- virtual void enablePreviewBuffer(bool enable) override;
- virtual void recordPreviewBuffer() override;
- virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override;
- virtual void stopPreviewBuffer() override;
+ void enablePreviewBuffer(bool enable) override;
+ void recordPreviewBuffer() override;
+ void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override;
+ void stopPreviewBuffer() override;
- virtual bool isPreviewRecording() override;
- virtual bool isPreviewPlaying() override;
+ bool isPreviewRecording() override;
+ bool isPreviewPlaying() override;
//@}
//@}
@@ -311,6 +326,8 @@ protected:
static ptr_t createSession();
~sessionState();
+ LLSD getVoiceChannelInfo();
+
participantStatePtr_t addParticipant(const std::string &uri);
void removeParticipant(const participantStatePtr_t &participant);
void removeAllParticipants();
@@ -325,6 +342,7 @@ protected:
bool isCallBackPossible();
bool isTextIMPossible();
+ bool isSpatial() { return mIsSpatial; }
static void for_each(sessionFunc_t func);
@@ -494,11 +512,6 @@ protected:
std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable)
/////////////////////////////
- bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled.
- // Use this to determine whether to show a "no speech" icon in the menu bar.
-
-
- /////////////////////////////
// Recording controls
void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200);
void recordingLoopSave(const std::string& filename);
@@ -692,7 +705,6 @@ private:
std::string mMainSessionGroupHandle; // handle of the "main" session group.
std::string mChannelName; // Name of the channel to be looked up
- bool mAreaVoiceDisabled;
sessionStatePtr_t mAudioSession; // Session state for the current audio session
bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified.
@@ -736,10 +748,8 @@ private:
bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = "");
void joinSession(const sessionStatePtr_t &session);
- std::string nameFromAvatar(LLVOAvatar *avatar);
std::string nameFromID(const LLUUID &id);
bool IDFromName(const std::string name, LLUUID &uuid);
- std::string displayNameFromAvatar(LLVOAvatar *avatar);
std::string sipURIFromAvatar(LLVOAvatar *avatar);
std::string sipURIFromName(std::string &name);
@@ -747,7 +757,7 @@ private:
std::string nameFromsipURI(const std::string &uri);
bool inSpatialChannel(void);
- std::string getAudioSessionURI();
+ LLSD getAudioSessionChannelInfo();
std::string getAudioSessionHandle();
void setHidden(bool hidden) override; //virtual
@@ -803,12 +813,11 @@ private:
bool mMicVolumeDirty;
bool mVoiceEnabled;
+ bool mProcessChannels;
bool mWriteInProgress;
std::string mWriteString;
size_t mWriteOffset;
- bool mLipSyncEnabled;
-
typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t;
observer_set_t mParticipantObservers;
diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp
new file mode 100644
index 0000000000..8049bb7a73
--- /dev/null
+++ b/indra/newview/llvoicewebrtc.cpp
@@ -0,0 +1,3087 @@
+ /**
+ * @file llvoicewebrtc.cpp
+ * @brief Implementation of LLWebRTCVoiceClient class which is the interface to the voice client process.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+#include <algorithm>
+#include "llvoicewebrtc.h"
+
+#include "llsdutil.h"
+
+// Linden library includes
+#include "llavatarnamecache.h"
+#include "llvoavatarself.h"
+#include "llbufferstream.h"
+#include "llfile.h"
+#include "llmenugl.h"
+#ifdef LL_USESYSTEMLIBS
+# include "expat.h"
+#else
+# include "expat/expat.h"
+#endif
+#include "llcallbacklist.h"
+#include "llviewernetwork.h" // for gGridChoice
+#include "llbase64.h"
+#include "llviewercontrol.h"
+#include "llappviewer.h" // for gDisconnected, gDisableVoice
+#include "llprocess.h"
+
+// Viewer includes
+#include "llmutelist.h" // to check for muted avatars
+#include "llagent.h"
+#include "llcachename.h"
+#include "llimview.h" // for LLIMMgr
+#include "llworld.h"
+#include "llparcel.h"
+#include "llviewerparcelmgr.h"
+#include "llfirstuse.h"
+#include "llspeakers.h"
+#include "lltrans.h"
+#include "llrand.h"
+#include "llviewerwindow.h"
+#include "llviewercamera.h"
+#include "llversioninfo.h"
+
+#include "llviewernetwork.h"
+#include "llnotificationsutil.h"
+
+#include "llcorehttputil.h"
+#include "lleventfilter.h"
+
+#include "stringize.h"
+
+#include "llwebrtc.h"
+
+// for base64 decoding
+#include "apr_base64.h"
+
+#include "boost/json.hpp"
+
+const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc";
+
+namespace {
+
+ const F32 MAX_AUDIO_DIST = 50.0f;
+ const F32 VOLUME_SCALE_WEBRTC = 0.01f;
+ const F32 LEVEL_SCALE_WEBRTC = 0.008f;
+
+ const F32 SPEAKING_AUDIO_LEVEL = 0.30;
+
+ static const std::string REPORTED_VOICE_SERVER_TYPE = "Secondlife WebRTC Gateway";
+
+ // Don't send positional updates more frequently than this:
+ const F32 UPDATE_THROTTLE_SECONDS = 0.1f;
+ const F32 MAX_RETRY_WAIT_SECONDS = 10.0f;
+
+ // Cosine of a "trivially" small angle
+ const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f);
+ const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES);
+
+} // namespace
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+void LLVoiceWebRTCStats::reset()
+{
+ mStartTime = -1.0f;
+ mConnectCycles = 0;
+ mConnectTime = -1.0f;
+ mConnectAttempts = 0;
+ mProvisionTime = -1.0f;
+ mProvisionAttempts = 0;
+ mEstablishTime = -1.0f;
+ mEstablishAttempts = 0;
+}
+
+LLVoiceWebRTCStats::LLVoiceWebRTCStats()
+{
+ reset();
+}
+
+LLVoiceWebRTCStats::~LLVoiceWebRTCStats()
+{
+}
+
+void LLVoiceWebRTCStats::connectionAttemptStart()
+{
+ if (!mConnectAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ mConnectCycles++;
+ }
+ mConnectAttempts++;
+}
+
+void LLVoiceWebRTCStats::connectionAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+void LLVoiceWebRTCStats::provisionAttemptStart()
+{
+ if (!mProvisionAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ }
+ mProvisionAttempts++;
+}
+
+void LLVoiceWebRTCStats::provisionAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+void LLVoiceWebRTCStats::establishAttemptStart()
+{
+ if (!mEstablishAttempts)
+ {
+ mStartTime = LLTimer::getTotalTime();
+ }
+ mEstablishAttempts++;
+}
+
+void LLVoiceWebRTCStats::establishAttemptEnd(bool success)
+{
+ if ( success )
+ {
+ mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC;
+ }
+}
+
+LLSD LLVoiceWebRTCStats::read()
+{
+ LLSD stats(LLSD::emptyMap());
+
+ stats["connect_cycles"] = LLSD::Integer(mConnectCycles);
+ stats["connect_attempts"] = LLSD::Integer(mConnectAttempts);
+ stats["connect_time"] = LLSD::Real(mConnectTime);
+
+ stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts);
+ stats["provision_time"] = LLSD::Real(mProvisionTime);
+
+ stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts);
+ stats["establish_time"] = LLSD::Real(mEstablishTime);
+
+ return stats;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+bool LLWebRTCVoiceClient::sShuttingDown = false;
+
+LLWebRTCVoiceClient::LLWebRTCVoiceClient() :
+ mHidden(false),
+ mTuningMode(false),
+ mTuningMicGain(0.0),
+ mTuningSpeakerVolume(50), // Set to 50 so the user can hear themselves when he sets his mic volume
+ mDevicesListUpdated(false),
+
+ mSpatialCoordsDirty(false),
+
+ mMuteMic(false),
+
+ mEarLocation(0),
+ mMicGain(0.0),
+
+ mVoiceEnabled(false),
+ mProcessChannels(false),
+
+ mAvatarNameCacheConnection(),
+ mIsInTuningMode(false),
+ mIsProcessingChannels(false),
+ mIsCoroutineActive(false),
+ mWebRTCPump("WebRTCClientPump"),
+ mWebRTCDeviceInterface(nullptr)
+{
+ sShuttingDown = false;
+
+ mSpeakerVolume = 0.0;
+
+ mVoiceVersion.serverVersion = "";
+ mVoiceVersion.voiceServerType = REPORTED_VOICE_SERVER_TYPE;
+ mVoiceVersion.internalVoiceServerType = WEBRTC_VOICE_SERVER_TYPE;
+ mVoiceVersion.minorVersion = 0;
+ mVoiceVersion.majorVersion = 2;
+ mVoiceVersion.mBuildVersion = "";
+}
+
+//---------------------------------------------------
+
+LLWebRTCVoiceClient::~LLWebRTCVoiceClient()
+{
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ sShuttingDown = true;
+}
+
+//---------------------------------------------------
+
+void LLWebRTCVoiceClient::init(LLPumpIO* pump)
+{
+ // constructor will set up LLVoiceClient::getInstance()
+ llwebrtc::init();
+
+ mWebRTCDeviceInterface = llwebrtc::getDeviceInterface();
+ mWebRTCDeviceInterface->setDevicesObserver(this);
+ mMainQueue = LL::WorkQueue::getInstance("mainloop");
+}
+
+void LLWebRTCVoiceClient::terminate()
+{
+ if (sShuttingDown)
+ {
+ return;
+ }
+
+ mVoiceEnabled = false;
+ llwebrtc::terminate();
+
+ sShuttingDown = true;
+}
+
+//---------------------------------------------------
+
+void LLWebRTCVoiceClient::cleanUp()
+{
+ mNextSession.reset();
+ mSession.reset();
+ mNeighboringRegions.clear();
+ sessionState::for_each(boost::bind(predShutdownSession, _1));
+ LL_DEBUGS("Voice") << "Exiting" << LL_ENDL;
+}
+
+// --------------------------------------------------
+
+const LLVoiceVersionInfo& LLWebRTCVoiceClient::getVersion()
+{
+ return mVoiceVersion;
+}
+
+//---------------------------------------------------
+
+void LLWebRTCVoiceClient::updateSettings()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled());
+ static LLCachedControl<S32> sVoiceEarLocation(gSavedSettings, "VoiceEarLocation");
+ setEarLocation(sVoiceEarLocation);
+
+ static LLCachedControl<std::string> sInputDevice(gSavedSettings, "VoiceInputAudioDevice");
+ setCaptureDevice(sInputDevice);
+
+ static LLCachedControl<std::string> sOutputDevice(gSavedSettings, "VoiceOutputAudioDevice");
+ setRenderDevice(sOutputDevice);
+
+ static LLCachedControl<F32> sMicLevel(gSavedSettings, "AudioLevelMic");
+ setMicGain(sMicLevel);
+
+ llwebrtc::LLWebRTCDeviceInterface::AudioConfig config;
+
+ static LLCachedControl<bool> sEchoCancellation(gSavedSettings, "VoiceEchoCancellation", true);
+ config.mEchoCancellation = sEchoCancellation;
+
+ static LLCachedControl<bool> sAGC(gSavedSettings, "VoiceAutomaticGainControl", true);
+ config.mAGC = sAGC;
+
+ static LLCachedControl<U32> sNoiseSuppressionLevel(gSavedSettings,
+ "VoiceNoiseSuppressionLevel",
+ llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel::NOISE_SUPPRESSION_LEVEL_VERY_HIGH);
+ config.mNoiseSuppressionLevel = (llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel) (U32)sNoiseSuppressionLevel;
+
+ mWebRTCDeviceInterface->setAudioConfig(config);
+
+}
+
+// Observers
+void LLWebRTCVoiceClient::addObserver(LLVoiceClientParticipantObserver *observer)
+{
+ mParticipantObservers.insert(observer);
+}
+
+void LLWebRTCVoiceClient::removeObserver(LLVoiceClientParticipantObserver *observer)
+{
+ mParticipantObservers.erase(observer);
+}
+
+void LLWebRTCVoiceClient::notifyParticipantObservers()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+ for (observer_set_t::iterator it = mParticipantObservers.begin(); it != mParticipantObservers.end();)
+ {
+ LLVoiceClientParticipantObserver *observer = *it;
+ observer->onParticipantsChanged();
+ // In case onParticipantsChanged() deleted an entry.
+ it = mParticipantObservers.upper_bound(observer);
+ }
+}
+
+void LLWebRTCVoiceClient::addObserver(LLVoiceClientStatusObserver *observer)
+{
+ mStatusObservers.insert(observer);
+}
+
+void LLWebRTCVoiceClient::removeObserver(LLVoiceClientStatusObserver *observer)
+{
+ mStatusObservers.erase(observer);
+}
+
+void LLWebRTCVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )"
+ << " mSession=" << mSession << LL_ENDL;
+
+ LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status) << ", session channelInfo "
+ << getAudioSessionChannelInfo() << ", proximal is " << inSpatialChannel() << LL_ENDL;
+
+ mIsProcessingChannels = status == LLVoiceClientStatusObserver::STATUS_JOINED;
+
+ LLSD channelInfo = getAudioSessionChannelInfo();
+ for (status_observer_set_t::iterator it = mStatusObservers.begin(); it != mStatusObservers.end();)
+ {
+ LLVoiceClientStatusObserver *observer = *it;
+ observer->onChange(status, channelInfo, inSpatialChannel());
+ // In case onError() deleted an entry.
+ it = mStatusObservers.upper_bound(observer);
+ }
+
+ // skipped to avoid speak button blinking
+ if (status != LLVoiceClientStatusObserver::STATUS_JOINING &&
+ status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL &&
+ status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED)
+ {
+ bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking();
+
+ gAgent.setVoiceConnected(voice_status);
+
+ if (voice_status)
+ {
+ LLAppViewer::instance()->postToMainCoro([=]() { LLFirstUse::speak(true); });
+ }
+ }
+}
+
+void LLWebRTCVoiceClient::addObserver(LLFriendObserver *observer)
+{
+}
+
+void LLWebRTCVoiceClient::removeObserver(LLFriendObserver *observer)
+{
+}
+
+//---------------------------------------------------
+// Primary voice loop.
+// This voice loop is called every 100ms plus the time it
+// takes to process the various functions called in the loop
+// The loop does the following:
+// * gates whether we do channel processing depending on
+// whether we're running a WebRTC voice channel or
+// one from another voice provider.
+// * If in spatial voice, it determines whether we've changed
+// parcels, whether region/parcel voice settings have changed,
+// etc. and manages whether the voice channel needs to change.
+// * calls the state machines for the sessions to negotiate
+// connection to various voice channels.
+// * Sends updates to the voice server when this agent's
+// voice levels, or positions have changed.
+void LLWebRTCVoiceClient::voiceConnectionCoro()
+{
+ LL_DEBUGS("Voice") << "starting" << LL_ENDL;
+ mIsCoroutineActive = true;
+ LLCoros::set_consuming(true);
+ try
+ {
+ LLMuteList::getInstance()->addObserver(this);
+ while (!sShuttingDown)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("voiceConnectionCoroLoop")
+ // TODO: Doing some measurement and calculation here,
+ // we could reduce the timeout to take into account the
+ // time spent on the previous loop to have the loop
+ // cycle at exactly 100ms, instead of 100ms + loop
+ // execution time.
+ // Could help with voice updates making for smoother
+ // voice when we're busy.
+ llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS);
+ bool voiceEnabled = mVoiceEnabled;
+
+ if (!isAgentAvatarValid())
+ {
+ continue;
+ }
+
+ LLViewerRegion *regionp = gAgent.getRegion();
+ if (!regionp)
+ {
+ continue;
+ }
+
+ if (!mProcessChannels)
+ {
+ // we've switched away from webrtc voice, so shut all channels down.
+ // leave channel can be called again and again without adverse effects.
+ // it merely tells channels to shut down if they're not already doing so.
+ leaveChannel(false);
+ }
+ else if (inSpatialChannel())
+ {
+ bool useEstateVoice = true;
+ // add session for region or parcel voice.
+ if (!regionp || regionp->getRegionID().isNull())
+ {
+ // no region, no voice.
+ continue;
+ }
+
+ voiceEnabled = voiceEnabled && regionp->isVoiceEnabled();
+
+ if (voiceEnabled)
+ {
+ LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
+ // check to see if parcel changed.
+ if (parcel && parcel->getLocalID() != INVALID_PARCEL_ID)
+ {
+ // parcel voice
+ if (!parcel->getParcelFlagAllowVoice())
+ {
+ voiceEnabled = false;
+ }
+ else if (!parcel->getParcelFlagUseEstateVoiceChannel())
+ {
+ // use the parcel-specific voice channel.
+ S32 parcel_local_id = parcel->getLocalID();
+ std::string channelID = regionp->getRegionID().asString() + "-" + std::to_string(parcel->getLocalID());
+
+ useEstateVoice = false;
+ if (!inOrJoiningChannel(channelID))
+ {
+ startParcelSession(channelID, parcel_local_id);
+ }
+ }
+ }
+ if (voiceEnabled && useEstateVoice && !inEstateChannel())
+ {
+ // estate voice
+ startEstateSession();
+ }
+ }
+ if (!voiceEnabled)
+ {
+ // voice is disabled, so leave and disable PTT
+ leaveChannel(true);
+ }
+ else
+ {
+ // we're in spatial voice, and voice is enabled, so determine positions in order
+ // to send position updates.
+ updatePosition();
+ }
+ }
+
+ sessionState::processSessionStates();
+ if (mProcessChannels && voiceEnabled && !mHidden)
+ {
+ sendPositionUpdate(false);
+ updateOwnVolume();
+ }
+ }
+ }
+ catch (const LLCoros::Stop&)
+ {
+ LL_DEBUGS("LLWebRTCVoiceClient") << "Received a shutdown exception" << LL_ENDL;
+ }
+ catch (const LLContinueError&)
+ {
+ LOG_UNHANDLED_EXCEPTION("LLWebRTCVoiceClient");
+ }
+ catch (...)
+ {
+ // Ideally for Windows need to log SEH exception instead or to set SEH
+ // handlers but bugsplat shows local variables for windows, which should
+ // be enough
+ LL_WARNS("Voice") << "voiceConnectionStateMachine crashed" << LL_ENDL;
+ throw;
+ }
+
+ cleanUp();
+}
+
+// For spatial, determine which neighboring regions to connect to
+// for cross-region voice.
+void LLWebRTCVoiceClient::updateNeighboringRegions()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ static const std::vector<LLVector3d> neighbors {LLVector3d(0.0f, 1.0f, 0.0f), LLVector3d(0.707f, 0.707f, 0.0f),
+ LLVector3d(1.0f, 0.0f, 0.0f), LLVector3d(0.707f, -0.707f, 0.0f),
+ LLVector3d(0.0f, -1.0f, 0.0f), LLVector3d(-0.707f, -0.707f, 0.0f),
+ LLVector3d(-1.0f, 0.0f, 0.0f), LLVector3d(-0.707f, 0.707f, 0.0f)};
+
+ // Estate voice requires connection to neighboring regions.
+ mNeighboringRegions.clear();
+
+ // add current region.
+ mNeighboringRegions.insert(gAgent.getRegion()->getRegionID());
+
+ // base off of speaker position as it'll move more slowly than camera position.
+ // Once we have hysteresis, we may be able to track off of speaker and camera position at 50m
+ // TODO: Add hysteresis so we don't flip-flop connections to neighbors
+ LLVector3d speaker_pos = LLWebRTCVoiceClient::getInstance()->getSpeakerPosition();
+ for (auto &neighbor_pos : neighbors)
+ {
+ // include every region within 100m (2*MAX_AUDIO_DIST) to deal witht he fact that the camera
+ // can stray 50m away from the avatar.
+ LLViewerRegion *neighbor = LLWorld::instance().getRegionFromPosGlobal(speaker_pos + 2 * MAX_AUDIO_DIST * neighbor_pos);
+ if (neighbor && !neighbor->getRegionID().isNull())
+ {
+ mNeighboringRegions.insert(neighbor->getRegionID());
+ }
+ }
+}
+
+//=========================================================================
+// shut down the current audio session to make room for the next one.
+void LLWebRTCVoiceClient::leaveAudioSession()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if(mSession)
+ {
+ LL_DEBUGS("Voice") << "leaving session: " << mSession->mChannelID << LL_ENDL;
+ mSession->shutdownAllConnections();
+ }
+ else
+ {
+ LL_WARNS("Voice") << "called with no active session" << LL_ENDL;
+ }
+}
+
+//=========================================================================
+// Device Management
+void LLWebRTCVoiceClient::clearCaptureDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mCaptureDevices.clear();
+}
+
+void LLWebRTCVoiceClient::addCaptureDevice(const LLVoiceDevice& device)
+{
+ LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL;
+ mCaptureDevices.push_back(device);
+}
+
+LLVoiceDeviceList& LLWebRTCVoiceClient::getCaptureDevices()
+{
+ return mCaptureDevices;
+}
+
+void LLWebRTCVoiceClient::setCaptureDevice(const std::string& name)
+{
+ mWebRTCDeviceInterface->setCaptureDevice(name);
+}
+void LLWebRTCVoiceClient::setDevicesListUpdated(bool state)
+{
+ mDevicesListUpdated = state;
+}
+
+// the singleton 'this' pointer will outlive the work queue.
+void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList& render_devices,
+ const llwebrtc::LLWebRTCVoiceDeviceList& capture_devices)
+{
+
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=]
+ {
+ OnDevicesChangedImpl(render_devices, capture_devices);
+ });
+}
+
+void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices,
+ const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+ std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
+ std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
+
+ LL_DEBUGS("Voice") << "Setting devices to-input: '" << inputDevice << "' output: '" << outputDevice << "'" << LL_ENDL;
+ clearRenderDevices();
+ for (auto &device : render_devices)
+ {
+ addRenderDevice(LLVoiceDevice(device.mDisplayName, device.mID));
+ }
+ setRenderDevice(outputDevice);
+
+ clearCaptureDevices();
+ for (auto &device : capture_devices)
+ {
+ LL_DEBUGS("Voice") << "Checking capture device:'" << device.mID << "'" << LL_ENDL;
+
+ addCaptureDevice(LLVoiceDevice(device.mDisplayName, device.mID));
+ }
+ setCaptureDevice(inputDevice);
+
+ setDevicesListUpdated(true);
+}
+
+void LLWebRTCVoiceClient::clearRenderDevices()
+{
+ LL_DEBUGS("Voice") << "called" << LL_ENDL;
+ mRenderDevices.clear();
+}
+
+void LLWebRTCVoiceClient::addRenderDevice(const LLVoiceDevice& device)
+{
+ LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL;
+ mRenderDevices.push_back(device);
+
+}
+
+LLVoiceDeviceList& LLWebRTCVoiceClient::getRenderDevices()
+{
+ return mRenderDevices;
+}
+
+void LLWebRTCVoiceClient::setRenderDevice(const std::string& name)
+{
+ mWebRTCDeviceInterface->setRenderDevice(name);
+}
+
+void LLWebRTCVoiceClient::tuningStart()
+{
+ if (!mIsInTuningMode)
+ {
+ mWebRTCDeviceInterface->setTuningMode(true);
+ mIsInTuningMode = true;
+ }
+}
+
+void LLWebRTCVoiceClient::tuningStop()
+{
+ if (mIsInTuningMode)
+ {
+ mWebRTCDeviceInterface->setTuningMode(false);
+ mIsInTuningMode = false;
+ }
+}
+
+bool LLWebRTCVoiceClient::inTuningMode()
+{
+ return mIsInTuningMode;
+}
+
+void LLWebRTCVoiceClient::tuningSetMicVolume(float volume)
+{
+ mTuningMicGain = volume;
+}
+
+void LLWebRTCVoiceClient::tuningSetSpeakerVolume(float volume)
+{
+
+ if (volume != mTuningSpeakerVolume)
+ {
+ mTuningSpeakerVolume = volume;
+ }
+}
+
+float LLWebRTCVoiceClient::getAudioLevel()
+{
+ if (mIsInTuningMode)
+ {
+ return (1.0 - mWebRTCDeviceInterface->getTuningAudioLevel() * LEVEL_SCALE_WEBRTC) * mTuningMicGain / 2.1;
+ }
+ else
+ {
+ return (1.0 - mWebRTCDeviceInterface->getPeerConnectionAudioLevel() * LEVEL_SCALE_WEBRTC) * mMicGain / 2.1;
+ }
+}
+
+float LLWebRTCVoiceClient::tuningGetEnergy(void)
+{
+ return getAudioLevel();
+}
+
+bool LLWebRTCVoiceClient::deviceSettingsAvailable()
+{
+ bool result = true;
+
+ if(mRenderDevices.empty() || mCaptureDevices.empty())
+ result = false;
+
+ return result;
+}
+bool LLWebRTCVoiceClient::deviceSettingsUpdated()
+{
+ bool updated = mDevicesListUpdated;
+ mDevicesListUpdated = false;
+ return updated;
+}
+
+void LLWebRTCVoiceClient::refreshDeviceLists(bool clearCurrentList)
+{
+ if(clearCurrentList)
+ {
+ clearCaptureDevices();
+ clearRenderDevices();
+ }
+ mWebRTCDeviceInterface->refreshDevices();
+}
+
+
+void LLWebRTCVoiceClient::setHidden(bool hidden)
+{
+ mHidden = hidden;
+
+ if (inSpatialChannel())
+ {
+ if (mHidden)
+ {
+ // get out of the channel entirely
+ // mute the microphone.
+ sessionState::for_each(boost::bind(predSetMuteMic, _1, true));
+ }
+ else
+ {
+ // and put it back
+ sessionState::for_each(boost::bind(predSetMuteMic, _1, mMuteMic));
+ updatePosition();
+ sendPositionUpdate(true);
+ }
+ }
+}
+
+/////////////////////////////
+// session control messages.
+//
+// these are called by the sessions to report
+// status for a given channel. By filtering
+// on channel and region, these functions
+// can send various notifications to
+// other parts of the viewer, as well as
+// managing housekeeping
+
+// A connection to a channel was successfully established,
+// so shut down the current session and move on to the next
+// if one is available.
+// if the current session is the one that was established,
+// notify the observers.
+void LLWebRTCVoiceClient::OnConnectionEstablished(const std::string &channelID, const LLUUID &regionID)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (gAgent.getRegion()->getRegionID() == regionID)
+ {
+ if (mNextSession && mNextSession->mChannelID == channelID)
+ {
+ if (mSession)
+ {
+ mSession->shutdownAllConnections();
+ }
+ mSession = mNextSession;
+ mNextSession.reset();
+ }
+
+ if (mSession)
+ {
+ // Add ourselves as a participant.
+ mSession->addParticipant(gAgentID, gAgent.getRegion()->getRegionID());
+ }
+
+ // The current session was established.
+ if (mSession && mSession->mChannelID == channelID)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
+
+ // only set status to joined if asked to. This will happen in the case where we're not
+ // doing an ad-hoc based p2p session. Those sessions expect a STATUS_JOINED when the peer
+ // has, in fact, joined, which we detect elsewhere.
+ if (!mSession->mNotifyOnFirstJoin)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
+ }
+ }
+ }
+}
+
+void LLWebRTCVoiceClient::OnConnectionShutDown(const std::string &channelID, const LLUUID &regionID)
+{
+ if (mSession && (mSession->mChannelID == channelID))
+ {
+ if (gAgent.getRegion()->getRegionID() == regionID)
+ {
+ if (mSession && mSession->mChannelID == channelID)
+ {
+ LL_DEBUGS("Voice") << "Main WebRTC Connection Shut Down." << LL_ENDL;
+ }
+ }
+ mSession->removeAllParticipants(regionID);
+ }
+}
+
+void LLWebRTCVoiceClient::OnConnectionFailure(const std::string &channelID,
+ const LLUUID &regionID,
+ LLVoiceClientStatusObserver::EStatusType status_type)
+{
+ LL_DEBUGS("Voice") << "A connection failed. channel:" << channelID << LL_ENDL;
+ if (gAgent.getRegion()->getRegionID() == regionID)
+ {
+ if (mNextSession && mNextSession->mChannelID == channelID)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(status_type);
+ }
+ else if (mSession && mSession->mChannelID == channelID)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(status_type);
+ }
+ }
+}
+
+// -----------------------------------------------------------
+// positional functionality.
+void LLWebRTCVoiceClient::setEarLocation(S32 loc)
+{
+ if (mEarLocation != loc)
+ {
+ LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
+
+ mEarLocation = loc;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLWebRTCVoiceClient::updatePosition(void)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LLViewerRegion *region = gAgent.getRegion();
+ if (region && isAgentAvatarValid())
+ {
+ // get the avatar position.
+ LLVector3d avatar_pos = gAgentAvatarp->getPositionGlobal();
+ LLQuaternion avatar_qrot = gAgentAvatarp->getRootJoint()->getWorldRotation();
+
+ avatar_pos += LLVector3d(0.f, 0.f, 1.f); // bump it up to head height
+
+ LLVector3d earPosition;
+ LLQuaternion earRot;
+ switch (mEarLocation)
+ {
+ case earLocCamera:
+ default:
+ earPosition = region->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin());
+ earRot = LLViewerCamera::getInstance()->getQuaternion();
+ break;
+
+ case earLocAvatar:
+ earPosition = mAvatarPosition;
+ earRot = mAvatarRot;
+ break;
+
+ case earLocMixed:
+ earPosition = mAvatarPosition;
+ earRot = LLViewerCamera::getInstance()->getQuaternion();
+ break;
+ }
+ setListenerPosition(earPosition, // position
+ LLVector3::zero, // velocity
+ earRot); // rotation matrix
+
+ setAvatarPosition(avatar_pos, // position
+ LLVector3::zero, // velocity
+ avatar_qrot); // rotation matrix
+
+ enforceTether();
+
+ updateNeighboringRegions();
+
+ // update own region id to be the region id avatar is currently in.
+ LLWebRTCVoiceClient::participantStatePtr_t participant = findParticipantByID("Estate", gAgentID);
+ if(participant)
+ {
+ participant->mRegion = gAgent.getRegion()->getRegionID();
+ }
+ }
+}
+
+void LLWebRTCVoiceClient::setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot)
+{
+ mListenerRequestedPosition = position;
+
+ if (mListenerVelocity != velocity)
+ {
+ mListenerVelocity = velocity;
+ mSpatialCoordsDirty = true;
+ }
+
+ if (mListenerRot != rot)
+ {
+ mListenerRot = rot;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+void LLWebRTCVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot)
+{
+ if (dist_vec_squared(mAvatarPosition, position) > 0.01)
+ {
+ mAvatarPosition = position;
+ mSpatialCoordsDirty = true;
+ }
+
+ if (mAvatarVelocity != velocity)
+ {
+ mAvatarVelocity = velocity;
+ mSpatialCoordsDirty = true;
+ }
+
+ // If the two rotations are not exactly equal test their dot product
+ // to get the cos of the angle between them.
+ // If it is too small, don't update.
+ F32 rot_cos_diff = llabs(dot(mAvatarRot, rot));
+ if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS))
+ {
+ mAvatarRot = rot;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+// The listener (camera) must be within 50m of the
+// avatar. Enforce it on the client.
+// This will also be enforced on the voice server
+// based on position sent from the simulator to the
+// voice server.
+void LLWebRTCVoiceClient::enforceTether()
+{
+ LLVector3d tethered = mListenerRequestedPosition;
+
+ // constrain 'tethered' to within 50m of mAvatarPosition.
+ {
+ LLVector3d camera_offset = mListenerRequestedPosition - mAvatarPosition;
+ F32 camera_distance = (F32) camera_offset.magVec();
+ if (camera_distance > MAX_AUDIO_DIST)
+ {
+ tethered = mAvatarPosition + (MAX_AUDIO_DIST / camera_distance) * camera_offset;
+ }
+ }
+
+ if (dist_vec_squared(mListenerPosition, tethered) > 0.01)
+ {
+ mListenerPosition = tethered;
+ mSpatialCoordsDirty = true;
+ }
+}
+
+// We send our position via a WebRTC data channel to the WebRTC
+// server for fine-grained, low latency updates. On the server,
+// these updates will be 'tethered' to the actual position of the avatar.
+// Those updates are higher latency, however.
+// This mechanism gives low latency spatial updates and server-enforced
+// prevention of 'evesdropping' by sending camera updates beyond the
+// standard 50m
+void LLWebRTCVoiceClient::sendPositionUpdate(bool force)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ std::string spatial_data;
+
+ if (mSpatialCoordsDirty || force)
+ {
+ boost::json::object spatial;
+
+ spatial["sp"] = {
+ {"x", (int) (mAvatarPosition[0] * 100)},
+ {"y", (int) (mAvatarPosition[1] * 100)},
+ {"z", (int) (mAvatarPosition[2] * 100)}
+ };
+ spatial["sh"] = {
+ {"x", (int) (mAvatarRot[0] * 100)},
+ {"y", (int) (mAvatarRot[1] * 100)},
+ {"z", (int) (mAvatarRot[2] * 100)},
+ {"w", (int) (mAvatarRot[3] * 100)}
+ };
+
+ spatial["lp"] = {
+ {"x", (int) (mListenerPosition[0] * 100)},
+ {"y", (int) (mListenerPosition[1] * 100)},
+ {"z", (int) (mListenerPosition[2] * 100)}
+ };
+
+ spatial["lh"] = {
+ {"x", (int) (mListenerRot[0] * 100)},
+ {"y", (int) (mListenerRot[1] * 100)},
+ {"z", (int) (mListenerRot[2] * 100)},
+ {"w", (int) (mListenerRot[3] * 100)}};
+
+ mSpatialCoordsDirty = false;
+ spatial_data = boost::json::serialize(spatial);
+
+ sessionState::for_each(boost::bind(predSendData, _1, spatial_data));
+ }
+}
+
+// Update our own volume on our participant, so it'll show up
+// in the UI. This is done on all sessions, so switching
+// sessions retains consistent volume levels.
+void LLWebRTCVoiceClient::updateOwnVolume() {
+ F32 audio_level = 0.0;
+ if (!mMuteMic && !mTuningMode)
+ {
+ audio_level = getAudioLevel();
+ }
+
+ sessionState::for_each(boost::bind(predUpdateOwnVolume, _1, audio_level));
+}
+
+////////////////////////////////////
+// Managing list of participants
+
+// Provider-level participant management
+
+bool LLWebRTCVoiceClient::isParticipantAvatar(const LLUUID &id)
+{
+ // WebRTC participants are always SL avatars.
+ return true;
+}
+
+void LLWebRTCVoiceClient::getParticipantList(std::set<LLUUID> &participants)
+{
+ if (mProcessChannels && mSession)
+ {
+ for (participantUUIDMap::iterator iter = mSession->mParticipantsByUUID.begin();
+ iter != mSession->mParticipantsByUUID.end();
+ iter++)
+ {
+ participants.insert(iter->first);
+ }
+ }
+}
+
+bool LLWebRTCVoiceClient::isParticipant(const LLUUID &speaker_id)
+{
+ if (mProcessChannels && mSession)
+ {
+ return (mSession->mParticipantsByUUID.find(speaker_id) != mSession->mParticipantsByUUID.end());
+ }
+ return false;
+}
+
+// protected provider-level participant management.
+LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::findParticipantByID(const std::string &channelID, const LLUUID &id)
+{
+ participantStatePtr_t result;
+ LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID);
+
+ if (session)
+ {
+ result = session->findParticipantByID(id);
+ }
+
+ return result;
+}
+
+LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::addParticipantByID(const std::string &channelID, const LLUUID &id, const LLUUID& region)
+{
+ participantStatePtr_t result;
+ LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID);
+ if (session)
+ {
+ result = session->addParticipant(id, region);
+ if (session->mNotifyOnFirstJoin && (id != gAgentID))
+ {
+ notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
+ }
+ }
+ return result;
+}
+
+void LLWebRTCVoiceClient::removeParticipantByID(const std::string &channelID, const LLUUID &id, const LLUUID& region)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ participantStatePtr_t result;
+ LLWebRTCVoiceClient::sessionState::ptr_t session = sessionState::matchSessionByChannelID(channelID);
+ if (session)
+ {
+ participantStatePtr_t participant = session->findParticipantByID(id);
+ if (participant && (participant->mRegion == region))
+ {
+ session->removeParticipant(participant);
+ }
+ }
+}
+
+
+// participantState level participant management
+LLWebRTCVoiceClient::participantState::participantState(const LLUUID& agent_id, const LLUUID& region) :
+ mURI(agent_id.asString()),
+ mAvatarID(agent_id),
+ mIsSpeaking(false),
+ mIsModeratorMuted(false),
+ mLevel(0.f),
+ mVolume(LLVoiceClient::VOLUME_DEFAULT),
+ mRegion(region)
+{
+}
+
+LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::addParticipant(const LLUUID& agent_id, const LLUUID& region)
+{
+
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ participantStatePtr_t result;
+
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(agent_id);
+
+ if (iter != mParticipantsByUUID.end())
+ {
+ result = iter->second;
+ result->mRegion = region;
+ }
+
+ if (!result)
+ {
+ // participant isn't already in one list or the other.
+ result.reset(new participantState(agent_id, region));
+ mParticipantsByUUID.insert(participantUUIDMap::value_type(agent_id, result));
+ result->mAvatarID = agent_id;
+ }
+
+ LLWebRTCVoiceClient::getInstance()->lookupName(agent_id);
+
+ LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume);
+ if (!LLWebRTCVoiceClient::sShuttingDown)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers();
+ }
+
+ LL_DEBUGS("Voice") << "Participant \"" << result->mURI << "\" added." << LL_ENDL;
+
+ return result;
+}
+
+
+// session-level participant management
+
+LLWebRTCVoiceClient::participantStatePtr_t LLWebRTCVoiceClient::sessionState::findParticipantByID(const LLUUID& id)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ participantStatePtr_t result;
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(id);
+
+ if(iter != mParticipantsByUUID.end())
+ {
+ result = iter->second;
+ }
+
+ return result;
+}
+
+void LLWebRTCVoiceClient::sessionState::removeParticipant(const LLWebRTCVoiceClient::participantStatePtr_t &participant)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (participant)
+ {
+ LLUUID participantID = participant->mAvatarID;
+ participantUUIDMap::iterator iter = mParticipantsByUUID.find(participant->mAvatarID);
+
+ LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participantID << ") removed." << LL_ENDL;
+
+ if (iter == mParticipantsByUUID.end())
+ {
+ LL_WARNS("Voice") << "Internal error: participant ID " << participantID << " not in UUID map" << LL_ENDL;
+ }
+ else
+ {
+ mParticipantsByUUID.erase(iter);
+ if (!LLWebRTCVoiceClient::sShuttingDown)
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers();
+ }
+ }
+ if (mHangupOnLastLeave && (participantID != gAgentID) && (mParticipantsByUUID.size() <= 1))
+ {
+ LLWebRTCVoiceClient::getInstance()->notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+ }
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::removeAllParticipants(const LLUUID &region)
+{
+ std::vector<participantStatePtr_t> participantsToRemove;
+
+ for (auto& participantEntry : mParticipantsByUUID)
+ {
+ if (region.isNull() || (participantEntry.second->mRegion == region))
+ {
+ participantsToRemove.push_back(participantEntry.second);
+ }
+ }
+ for (auto& participant : participantsToRemove)
+ {
+ removeParticipant(participant);
+ }
+}
+
+// Initiated the various types of sessions.
+bool LLWebRTCVoiceClient::startEstateSession()
+{
+ leaveChannel(false);
+ mNextSession = addSession("Estate", sessionState::ptr_t(new estateSessionState()));
+ return true;
+}
+
+bool LLWebRTCVoiceClient::startParcelSession(const std::string &channelID, S32 parcelID)
+{
+ leaveChannel(false);
+ mNextSession = addSession(channelID, sessionState::ptr_t(new parcelSessionState(channelID, parcelID)));
+ return true;
+}
+
+bool LLWebRTCVoiceClient::startAdHocSession(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave)
+{
+ leaveChannel(false);
+ LL_WARNS("Voice") << "Start AdHoc Session " << channelInfo << LL_ENDL;
+ std::string channelID = channelInfo["channel_uri"];
+ std::string credentials = channelInfo["channel_credentials"];
+ mNextSession = addSession(channelID,
+ sessionState::ptr_t(new adhocSessionState(channelID,
+ credentials,
+ notify_on_first_join,
+ hangup_on_last_leave)));
+ return true;
+}
+
+bool LLWebRTCVoiceClient::isVoiceWorking() const
+{
+ return mIsProcessingChannels;
+}
+
+// Returns true if calling back the session URI after the session has closed is possible.
+// Currently this will be false only for PSTN P2P calls.
+bool LLWebRTCVoiceClient::isSessionCallBackPossible(const LLUUID &session_id)
+{
+ sessionStatePtr_t session(findP2PSession(session_id));
+ return session && session->isCallbackPossible();
+}
+
+// Channel Management
+
+bool LLWebRTCVoiceClient::setSpatialChannel(const LLSD &channelInfo)
+{
+ LL_INFOS("Voice") << "SetSpatialChannel " << channelInfo << LL_ENDL;
+ LLViewerRegion *regionp = gAgent.getRegion();
+ if (!regionp)
+ {
+ return false;
+ }
+ LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
+
+ // we don't really have credentials for a spatial channel in webrtc,
+ // it's all handled by the sim.
+ if (channelInfo.isMap() && channelInfo.has("channel_uri"))
+ {
+ bool allow_voice = !channelInfo["channel_uri"].asString().empty();
+ if (parcel)
+ {
+ parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, allow_voice);
+ parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, channelInfo["channel_uri"].asUUID() == regionp->getRegionID());
+ }
+ else
+ {
+ regionp->setRegionFlag(REGION_FLAGS_ALLOW_VOICE, allow_voice);
+ }
+ }
+ return true;
+}
+
+void LLWebRTCVoiceClient::leaveNonSpatialChannel()
+{
+ LL_DEBUGS("Voice") << "Request to leave non-spatial channel." << LL_ENDL;
+
+ // make sure we're not simply rejoining the current session
+ deleteSession(mNextSession);
+
+ leaveChannel(true);
+}
+
+// determine whether we're processing channels, or whether
+// another voice provider is.
+void LLWebRTCVoiceClient::processChannels(bool process)
+{
+ mProcessChannels = process;
+}
+
+bool LLWebRTCVoiceClient::inProximalChannel()
+{
+ return inSpatialChannel();
+}
+
+bool LLWebRTCVoiceClient::inOrJoiningChannel(const std::string& channelID)
+{
+ return (mSession && mSession->mChannelID == channelID) || (mNextSession && mNextSession->mChannelID == channelID);
+}
+
+bool LLWebRTCVoiceClient::inEstateChannel()
+{
+ return (mSession && mSession->isEstate()) || (mNextSession && mNextSession->isEstate());
+}
+
+bool LLWebRTCVoiceClient::inSpatialChannel()
+{
+ bool result = true;
+
+ if (mNextSession)
+ {
+ result = mNextSession->isSpatial();
+ }
+ else if(mSession)
+ {
+ result = mSession->isSpatial();
+ }
+
+ return result;
+}
+
+// retrieves information used to negotiate p2p, adhoc, and group
+// channels
+LLSD LLWebRTCVoiceClient::getAudioSessionChannelInfo()
+{
+ LLSD result;
+
+ if (mSession)
+ {
+ result["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE;
+ result["channel_uri"] = mSession->mChannelID;
+ }
+
+ return result;
+}
+
+void LLWebRTCVoiceClient::leaveChannel(bool stopTalking)
+{
+ if (mSession)
+ {
+ deleteSession(mSession);
+ }
+
+ if (mNextSession)
+ {
+ deleteSession(mNextSession);
+ }
+
+ // If voice was on, turn it off
+ if (stopTalking && LLVoiceClient::getInstance()->getUserPTTState())
+ {
+ LLVoiceClient::getInstance()->setUserPTTState(false);
+ }
+}
+
+bool LLWebRTCVoiceClient::isCurrentChannel(const LLSD &channelInfo)
+{
+ if (!mProcessChannels || (channelInfo["voice_server_type"].asString() != WEBRTC_VOICE_SERVER_TYPE))
+ {
+ return false;
+ }
+
+ sessionStatePtr_t session = mSession;
+ if (!session)
+ {
+ session = mNextSession;
+ }
+
+ if (session)
+ {
+ if (!channelInfo["session_handle"].asString().empty())
+ {
+ return session->mHandle == channelInfo["session_handle"].asString();
+ }
+ return channelInfo["channel_uri"].asString() == session->mChannelID;
+ }
+ return false;
+}
+
+bool LLWebRTCVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2)
+{
+ return (channelInfo1["voice_server_type"] == WEBRTC_VOICE_SERVER_TYPE) &&
+ (channelInfo1["voice_server_type"] == channelInfo2["voice_server_type"]) &&
+ (channelInfo1["sip_uri"] == channelInfo2["sip_uri"]);
+}
+
+
+//----------------------------------------------
+// Audio muting, volume, gain, etc.
+
+// we're muting the mic, so tell each session such
+void LLWebRTCVoiceClient::setMuteMic(bool muted)
+{
+ mMuteMic = muted;
+ // when you're hidden, your mic is always muted.
+ if (!mHidden)
+ {
+ sessionState::for_each(boost::bind(predSetMuteMic, _1, muted));
+ }
+}
+
+void LLWebRTCVoiceClient::predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool muted)
+{
+ participantStatePtr_t participant = session->findParticipantByID(gAgentID);
+ if (participant)
+ {
+ participant->mLevel = 0.0;
+ }
+ session->setMuteMic(muted);
+}
+
+void LLWebRTCVoiceClient::setVoiceVolume(F32 volume)
+{
+ if (volume != mSpeakerVolume)
+ {
+ {
+ mSpeakerVolume = volume;
+ }
+ sessionState::for_each(boost::bind(predSetSpeakerVolume, _1, volume));
+ }
+}
+
+void LLWebRTCVoiceClient::predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume)
+{
+ session->setSpeakerVolume(volume);
+}
+
+void LLWebRTCVoiceClient::setMicGain(F32 gain)
+{
+ if (gain != mMicGain)
+ {
+ mMicGain = gain;
+ mWebRTCDeviceInterface->setPeerConnectionGain(gain);
+ }
+}
+
+
+void LLWebRTCVoiceClient::setVoiceEnabled(bool enabled)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LL_DEBUGS("Voice")
+ << "( " << (enabled ? "enabled" : "disabled") << " )"
+ << " was "<< (mVoiceEnabled ? "enabled" : "disabled")
+ << " coro "<< (mIsCoroutineActive ? "active" : "inactive")
+ << LL_ENDL;
+
+ if (enabled != mVoiceEnabled)
+ {
+ // TODO: Refactor this so we don't call into LLVoiceChannel, but simply
+ // use the status observer
+ mVoiceEnabled = enabled;
+ LLVoiceClientStatusObserver::EStatusType status;
+
+ if (enabled)
+ {
+ LL_DEBUGS("Voice") << "enabling" << LL_ENDL;
+ LLVoiceChannel::getCurrentVoiceChannel()->activate();
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED;
+ mSpatialCoordsDirty = true;
+ updatePosition();
+ if (!mIsCoroutineActive)
+ {
+ LLCoros::instance().launch("LLWebRTCVoiceClient::voiceConnectionCoro",
+ boost::bind(&LLWebRTCVoiceClient::voiceConnectionCoro, LLWebRTCVoiceClient::getInstance()));
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL;
+ }
+ }
+ else
+ {
+ // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it.
+ LLVoiceChannel::getCurrentVoiceChannel()->deactivate();
+ gAgent.setVoiceConnected(false);
+ status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED;
+ cleanUp();
+ }
+
+ notifyStatusObservers(status);
+ }
+ else
+ {
+ LL_DEBUGS("Voice") << " no-op" << LL_ENDL;
+ }
+}
+
+
+/////////////////////////////
+// Accessors for data related to nearby speakers
+
+std::string LLWebRTCVoiceClient::getDisplayName(const LLUUID& id)
+{
+ std::string result;
+ if (mProcessChannels && mSession)
+ {
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant)
+ {
+ result = participant->mDisplayName;
+ }
+ }
+ return result;
+}
+
+bool LLWebRTCVoiceClient::getIsSpeaking(const LLUUID& id)
+{
+ bool result = false;
+ if (mProcessChannels && mSession)
+ {
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant)
+ {
+ result = participant->mIsSpeaking;
+ }
+ }
+ return result;
+}
+
+// TODO: Need to pull muted status from the webrtc server
+bool LLWebRTCVoiceClient::getIsModeratorMuted(const LLUUID& id)
+{
+ bool result = false;
+ if (mProcessChannels && mSession)
+ {
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant)
+ {
+ result = participant->mIsModeratorMuted;
+ }
+ }
+ return result;
+}
+
+F32 LLWebRTCVoiceClient::getCurrentPower(const LLUUID &id)
+{
+ F32 result = 0.0;
+ if (!mProcessChannels || !mSession)
+ {
+ return result;
+ }
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant)
+ {
+ if (participant->mIsSpeaking)
+ {
+ result = participant->mLevel;
+ }
+ }
+ return result;
+}
+
+// External accessors.
+F32 LLWebRTCVoiceClient::getUserVolume(const LLUUID& id)
+{
+ // Minimum volume will be returned for users with voice disabled
+ F32 result = LLVoiceClient::VOLUME_MIN;
+
+ if (mSession)
+ {
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant)
+ {
+ result = participant->mVolume;
+ }
+ }
+
+ return result;
+}
+
+void LLWebRTCVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+{
+ F32 clamped_volume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX);
+ if(mSession)
+ {
+ participantStatePtr_t participant(mSession->findParticipantByID(id));
+ if (participant && (participant->mAvatarID != gAgentID))
+ {
+ if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT))
+ {
+ // Store this volume setting for future sessions if it has been
+ // changed from the default
+ LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume);
+ }
+ else
+ {
+ // Remove stored volume setting if it is returned to the default
+ LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id);
+ }
+
+ participant->mVolume = clamped_volume;
+ }
+ }
+ sessionState::for_each(boost::bind(predSetUserVolume, _1, id, clamped_volume));
+}
+
+// set volume level (gain level) for another user.
+void LLWebRTCVoiceClient::predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, F32 volume)
+{
+ session->setUserVolume(id, volume);
+}
+
+////////////////////////
+///LLMuteListObserver
+///
+
+void LLWebRTCVoiceClient::onChange()
+{
+}
+
+void LLWebRTCVoiceClient::onChangeDetailed(const LLMute& mute)
+{
+ if (mute.mType == LLMute::AGENT)
+ {
+ bool muted = ((mute.mFlags & LLMute::flagVoiceChat) == 0);
+ sessionState::for_each(boost::bind(predSetUserMute, _1, mute.mID, muted));
+ }
+}
+
+void LLWebRTCVoiceClient::predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID &id, bool mute)
+{
+ session->setUserMute(id, mute);
+}
+
+//------------------------------------------------------------------------
+// Sessions
+
+std::map<std::string, LLWebRTCVoiceClient::sessionState::ptr_t> LLWebRTCVoiceClient::sessionState::mSessions;
+
+
+LLWebRTCVoiceClient::sessionState::sessionState() :
+ mHangupOnLastLeave(false),
+ mNotifyOnFirstJoin(false),
+ mMuted(false),
+ mSpeakerVolume(1.0),
+ mShuttingDown(false)
+{
+}
+// ------------------------------------------------------------------
+// Predicates, for calls to all sessions
+
+void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level)
+{
+ participantStatePtr_t participant = session->findParticipantByID(gAgentID);
+ if (participant)
+ {
+ participant->mLevel = audio_level;
+ // TODO: Add VAD for our own voice.
+ participant->mIsSpeaking = audio_level > SPEAKING_AUDIO_LEVEL;
+ }
+}
+
+void LLWebRTCVoiceClient::predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string &spatial_data)
+{
+ if (session->isSpatial() && !spatial_data.empty())
+ {
+ session->sendData(spatial_data);
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data)
+{
+ for (auto &connection : mWebRTCConnections)
+ {
+ connection->sendData(data);
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::setMuteMic(bool muted)
+{
+ mMuted = muted;
+ for (auto &connection : mWebRTCConnections)
+ {
+ connection->setMuteMic(muted);
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::setSpeakerVolume(F32 volume)
+{
+ mSpeakerVolume = volume;
+ for (auto &connection : mWebRTCConnections)
+ {
+ connection->setSpeakerVolume(volume);
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::setUserVolume(const LLUUID &id, F32 volume)
+{
+ if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end())
+ {
+ return;
+ }
+ for (auto &connection : mWebRTCConnections)
+ {
+ connection->setUserVolume(id, volume);
+ }
+}
+
+void LLWebRTCVoiceClient::sessionState::setUserMute(const LLUUID &id, bool mute)
+{
+ if (mParticipantsByUUID.find(id) == mParticipantsByUUID.end())
+ {
+ return;
+ }
+ for (auto &connection : mWebRTCConnections)
+ {
+ connection->setUserMute(id, mute);
+ }
+}
+/*static*/
+void LLWebRTCVoiceClient::sessionState::addSession(
+ const std::string & channelID,
+ LLWebRTCVoiceClient::sessionState::ptr_t& session)
+{
+ mSessions[channelID] = session;
+}
+
+LLWebRTCVoiceClient::sessionState::~sessionState()
+{
+ LL_DEBUGS("Voice") << "Destroying session CHANNEL=" << mChannelID << LL_ENDL;
+
+ removeAllParticipants();
+}
+
+/*static*/
+LLWebRTCVoiceClient::sessionState::ptr_t LLWebRTCVoiceClient::sessionState::matchSessionByChannelID(const std::string& channel_id)
+{
+ sessionStatePtr_t result;
+
+ // *TODO: My kingdom for a lambda!
+ std::map<std::string, ptr_t>::iterator it = mSessions.find(channel_id);
+ if (it != mSessions.end())
+ {
+ result = (*it).second;
+ }
+ return result;
+}
+
+void LLWebRTCVoiceClient::sessionState::for_each(sessionFunc_t func)
+{
+ std::for_each(mSessions.begin(), mSessions.end(), boost::bind(for_eachPredicate, _1, func));
+}
+
+void LLWebRTCVoiceClient::sessionState::reapEmptySessions()
+{
+ std::map<std::string, ptr_t>::iterator iter;
+ for (iter = mSessions.begin(); iter != mSessions.end();)
+ {
+ if (iter->second->isEmpty())
+ {
+ iter = mSessions.erase(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+}
+
+/*static*/
+void LLWebRTCVoiceClient::sessionState::for_eachPredicate(const std::pair<std::string, LLWebRTCVoiceClient::sessionState::wptr_t> &a, sessionFunc_t func)
+{
+ ptr_t aLock(a.second.lock());
+
+ if (aLock)
+ func(aLock);
+ else
+ {
+ LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL;
+ }
+}
+
+LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::addSession(const std::string &channel_id, sessionState::ptr_t session)
+{
+ sessionStatePtr_t existingSession = sessionState::matchSessionByChannelID(channel_id);
+ if (!existingSession)
+ {
+ // No existing session found.
+
+ LL_DEBUGS("Voice") << "adding new session with channel: " << channel_id << LL_ENDL;
+ session->setMuteMic(mMuteMic);
+ session->setSpeakerVolume(mSpeakerVolume);
+
+ sessionState::addSession(channel_id, session);
+ return session;
+ }
+ else
+ {
+ // Found an existing session
+ LL_DEBUGS("Voice") << "Attempting to add already-existing session " << channel_id << LL_ENDL;
+ existingSession->revive();
+
+ return existingSession;
+ }
+}
+
+LLWebRTCVoiceClient::sessionStatePtr_t LLWebRTCVoiceClient::findP2PSession(const LLUUID &agent_id)
+{
+ sessionStatePtr_t result = sessionState::matchSessionByChannelID(agent_id.asString());
+ if (result && !result->isSpatial())
+ {
+ return result;
+ }
+
+ result.reset();
+ return result;
+}
+
+void LLWebRTCVoiceClient::sessionState::shutdownAllConnections()
+{
+ mShuttingDown = true;
+ for (auto &&connection : mWebRTCConnections)
+ {
+ connection->shutDown();
+ }
+}
+
+// in case we drop into a session (spatial, etc.) right after
+// telling the session to shut down, revive it so it reconnects.
+void LLWebRTCVoiceClient::sessionState::revive()
+{
+ mShuttingDown = false;
+}
+
+//=========================================================================
+// the following are methods to support the coroutine implementation of the
+// voice connection and processing. They should only be called in the context
+// of a coroutine.
+//
+//
+
+void LLWebRTCVoiceClient::sessionState::processSessionStates()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ auto iter = mSessions.begin();
+ while (iter != mSessions.end())
+ {
+ if (!iter->second->processConnectionStates() && iter->second->mShuttingDown)
+ {
+ // if the connections associated with a session are gone,
+ // and this session is shutting down, remove it.
+ iter = mSessions.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+}
+
+// process the states on each connection associated with a session.
+bool LLWebRTCVoiceClient::sessionState::processConnectionStates()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ std::list<connectionPtr_t>::iterator iter = mWebRTCConnections.begin();
+ while (iter != mWebRTCConnections.end())
+ {
+ if (!iter->get()->connectionStateMachine())
+ {
+ // if the state machine returns false, the connection is shut down
+ // so delete it.
+ iter = mWebRTCConnections.erase(iter);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+ return !mWebRTCConnections.empty();
+}
+
+// processing of spatial voice connection states requires special handling.
+// as neighboring regions need to be started up or shut down depending
+// on our location.
+bool LLWebRTCVoiceClient::estateSessionState::processConnectionStates()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (!mShuttingDown)
+ {
+ // Estate voice requires connection to neighboring regions.
+ std::set<LLUUID> neighbor_ids = LLWebRTCVoiceClient::getInstance()->getNeighboringRegions();
+
+ for (auto &connection : mWebRTCConnections)
+ {
+ boost::shared_ptr<LLVoiceWebRTCSpatialConnection> spatialConnection =
+ boost::static_pointer_cast<LLVoiceWebRTCSpatialConnection>(connection);
+
+ LLUUID regionID = spatialConnection.get()->getRegionID();
+
+ if (neighbor_ids.find(regionID) == neighbor_ids.end())
+ {
+ // shut down connections to neighbors that are too far away.
+ spatialConnection.get()->shutDown();
+ }
+ neighbor_ids.erase(regionID);
+ }
+
+ // add new connections for new neighbors
+ for (auto &neighbor : neighbor_ids)
+ {
+ connectionPtr_t connection(new LLVoiceWebRTCSpatialConnection(neighbor, INVALID_PARCEL_ID, mChannelID));
+
+ mWebRTCConnections.push_back(connection);
+ connection->setMuteMic(mMuted);
+ connection->setSpeakerVolume(mSpeakerVolume);
+ }
+ }
+ return LLWebRTCVoiceClient::sessionState::processConnectionStates();
+}
+
+// Various session state constructors.
+
+LLWebRTCVoiceClient::estateSessionState::estateSessionState()
+{
+ mHangupOnLastLeave = false;
+ mNotifyOnFirstJoin = false;
+ mChannelID = "Estate";
+ LLUUID region_id = gAgent.getRegion()->getRegionID();
+
+ mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, INVALID_PARCEL_ID, "Estate"));
+}
+
+LLWebRTCVoiceClient::parcelSessionState::parcelSessionState(const std::string &channelID, S32 parcel_local_id)
+{
+ mHangupOnLastLeave = false;
+ mNotifyOnFirstJoin = false;
+ LLUUID region_id = gAgent.getRegion()->getRegionID();
+ mChannelID = channelID;
+ mWebRTCConnections.emplace_back(new LLVoiceWebRTCSpatialConnection(region_id, parcel_local_id, channelID));
+}
+
+LLWebRTCVoiceClient::adhocSessionState::adhocSessionState(const std::string &channelID,
+ const std::string &credentials,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave) :
+ mCredentials(credentials)
+{
+ mHangupOnLastLeave = hangup_on_last_leave;
+ mNotifyOnFirstJoin = notify_on_first_join;
+ LLUUID region_id = gAgent.getRegion()->getRegionID();
+ mChannelID = channelID;
+ mWebRTCConnections.emplace_back(new LLVoiceWebRTCAdHocConnection(region_id, channelID, credentials));
+}
+
+void LLWebRTCVoiceClient::predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t& session)
+{
+ session->shutdownAllConnections();
+}
+
+void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session)
+{
+ if (!session)
+ {
+ return;
+ }
+
+ // At this point, the session should be unhooked from all lists and all state should be consistent.
+ session->shutdownAllConnections();
+ // If this is the current audio session, clean up the pointer which will soon be dangling.
+ bool deleteAudioSession = mSession == session;
+ bool deleteNextAudioSession = mNextSession == session;
+ if (deleteAudioSession)
+ {
+ mSession.reset();
+ }
+
+ // ditto for the next audio session
+ if (deleteNextAudioSession)
+ {
+ mNextSession.reset();
+ }
+}
+
+
+// Name resolution
+void LLWebRTCVoiceClient::lookupName(const LLUUID &id)
+{
+ if (mAvatarNameCacheConnection.connected())
+ {
+ mAvatarNameCacheConnection.disconnect();
+ }
+ mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLWebRTCVoiceClient::onAvatarNameCache, this, _1, _2));
+}
+
+void LLWebRTCVoiceClient::onAvatarNameCache(const LLUUID& agent_id,
+ const LLAvatarName& av_name)
+{
+ mAvatarNameCacheConnection.disconnect();
+ std::string display_name = av_name.getDisplayName();
+ avatarNameResolved(agent_id, display_name);
+}
+
+void LLWebRTCVoiceClient::predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name)
+{
+ participantStatePtr_t participant(session->findParticipantByID(id));
+ if (participant)
+ {
+ // Found -- fill in the name
+ participant->mDisplayName = name;
+ // and post a "participants updated" message to listeners later.
+ LLWebRTCVoiceClient::getInstance()->notifyParticipantObservers();
+ }
+}
+
+void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name)
+{
+ sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name));
+}
+
+// Leftover from vivox PTSN
+std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id)
+{
+ return id.asString();
+}
+
+
+/////////////////////////////
+// LLVoiceWebRTCConnection
+// These connections manage state transitions, negotiating webrtc connections,
+// and other such things for a single connection to a Secondlife WebRTC server.
+// Multiple of these connections may be active at once, in the case of
+// cross-region voice, or when a new connection is being created before the old
+// has a chance to shut down.
+LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID &regionID, const std::string &channelID) :
+ mWebRTCAudioInterface(nullptr),
+ mWebRTCDataInterface(nullptr),
+ mVoiceConnectionState(VOICE_STATE_START_SESSION),
+ mCurrentStatus(LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED),
+ mMuted(true),
+ mShutDown(false),
+ mIceCompleted(false),
+ mSpeakerVolume(0.0),
+ mOutstandingRequests(0),
+ mChannelID(channelID),
+ mRegionID(regionID),
+ mRetryWaitPeriod(0)
+{
+ // retries wait a short period...randomize it so
+ // all clients don't try to reconnect at once.
+ mRetryWaitSecs = ((F32) rand() / (RAND_MAX)) + 0.5;
+
+ mWebRTCPeerConnectionInterface = llwebrtc::newPeerConnection();
+ mWebRTCPeerConnectionInterface->setSignalingObserver(this);
+ mMainQueue = LL::WorkQueue::getInstance("mainloop");
+}
+
+LLVoiceWebRTCConnection::~LLVoiceWebRTCConnection()
+{
+ if (LLWebRTCVoiceClient::isShuttingDown())
+ {
+ // peer connection and observers will be cleaned up
+ // by llwebrtc::terminate() on shutdown.
+ return;
+ }
+ mWebRTCPeerConnectionInterface->unsetSignalingObserver(this);
+ llwebrtc::freePeerConnection(mWebRTCPeerConnectionInterface);
+}
+
+
+// ICE (Interactive Connectivity Establishment)
+// When WebRTC tries to negotiate a connection to the Secondlife WebRTC Server,
+// the negotiation will result in a few updates about the best path
+// to which to connect.
+// The Secondlife servers are configured for ICE trickling, where, after a session is partially
+// negotiated, updates about the best connectivity paths may trickle in. These need to be
+// sent to the Secondlife WebRTC server via the simulator so that both sides have a clear
+// view of the network environment.
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnIceGatheringState(llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState state)
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ LL_DEBUGS("Voice") << "Ice Gathering voice account. " << state << LL_ENDL;
+
+ switch (state)
+ {
+ case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_COMPLETE:
+ {
+ mIceCompleted = true;
+ break;
+ }
+ case llwebrtc::LLWebRTCSignalingObserver::EIceGatheringState::ICE_GATHERING_NEW:
+ {
+ mIceCompleted = false;
+ }
+ default:
+ break;
+ }
+ });
+}
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate& candidate)
+{
+ LL::WorkQueue::postMaybe(mMainQueue, [=] { mIceCandidates.push_back(candidate); });
+}
+
+void LLVoiceWebRTCConnection::processIceUpdates()
+{
+ mOutstandingRequests++;
+ LLCoros::getInstance()->launch("LLVoiceWebRTCConnection::processIceUpdatesCoro",
+ boost::bind(&LLVoiceWebRTCConnection::processIceUpdatesCoro, this));
+}
+
+// Ice candidates may be streamed in before or after the SDP offer is available (see below)
+// This function determines whether candidates are available to send to the Secondlife WebRTC
+// server via the simulator. If so, and there are no more candidates, this code
+// will make the cap call to the server sending up the ICE candidates.
+void LLVoiceWebRTCConnection::processIceUpdatesCoro()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (mShutDown || LLWebRTCVoiceClient::isShuttingDown())
+ {
+ mOutstandingRequests--;
+ return;
+ }
+
+ bool iceCompleted = false;
+ LLSD body;
+ if (!mIceCandidates.empty() || mIceCompleted)
+ {
+ LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID);
+ if (!regionp || !regionp->capabilitiesReceived())
+ {
+ LL_DEBUGS("Voice") << "no capabilities for ice gathering; waiting " << LL_ENDL;
+ mOutstandingRequests--;
+ return;
+ }
+
+ std::string url = regionp->getCapability("VoiceSignalingRequest");
+ if (url.empty())
+ {
+ mOutstandingRequests--;
+ return;
+ }
+
+ LL_DEBUGS("Voice") << "region ready to complete voice signaling; url=" << url << LL_ENDL;
+ if (!mIceCandidates.empty())
+ {
+ LLSD candidates = LLSD::emptyArray();
+ for (auto &ice_candidate : mIceCandidates)
+ {
+ LLSD body_candidate;
+ body_candidate["sdpMid"] = ice_candidate.mSdpMid;
+ body_candidate["sdpMLineIndex"] = ice_candidate.mMLineIndex;
+ body_candidate["candidate"] = ice_candidate.mCandidate;
+ candidates.append(body_candidate);
+ }
+ body["candidates"] = candidates;
+ mIceCandidates.clear();
+ }
+ else if (mIceCompleted)
+ {
+ LLSD body_candidate;
+ body_candidate["completed"] = true;
+ body["candidate"] = body_candidate;
+ iceCompleted = mIceCompleted;
+ mIceCompleted = false;
+ }
+
+ body["viewer_session"] = mViewerSession;
+ body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE;
+
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(
+ new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::processIceUpdatesCoro",
+ LLCore::HttpRequest::DEFAULT_POLICY_ID));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+
+ httpOpts->setWantHeaders(true);
+
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts);
+
+ if (LLWebRTCVoiceClient::isShuttingDown())
+ {
+ mOutstandingRequests--;
+ return;
+ }
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (!status)
+ {
+ // couldn't trickle the candidates, so restart the session.
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ }
+ }
+ mOutstandingRequests--;
+}
+
+
+// An 'Offer' comes in the form of a SDP (Session Description Protocol)
+// which contains all sorts of info about the session, from network paths
+// to the type of session (audio, video) to characteristics (the encoder type.)
+// This SDP also serves as the 'ticket' to the server, security-wise.
+// The Offer is retrieved from the WebRTC library on the client,
+// and is passed to the simulator via a CAP, which then passes
+// it on to the Secondlife WebRTC server.
+
+//
+// The LLWebRTCVoiceConnection object will not be deleted
+// before the webrtc connection itself is shut down, so
+// we shouldn't be getting this callback on a nonexistant
+// this pointer.
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnOfferAvailable(const std::string &sdp)
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ if (mShutDown)
+ {
+ return;
+ }
+ LL_DEBUGS("Voice") << "On Offer Available." << LL_ENDL;
+ mChannelSDP = sdp;
+ if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_SESSION_START)
+ {
+ mVoiceConnectionState = VOICE_STATE_REQUEST_CONNECTION;
+ }
+ });
+}
+
+
+//
+// The LLWebRTCVoiceConnection object will not be deleted
+// before the webrtc connection itself is shut down, so
+// we shouldn't be getting this callback on a nonexistant
+// this pointer.
+// nor should audio_interface be invalid if the LLWebRTCVoiceConnection
+// is shut down.
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface* audio_interface)
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ if (mShutDown)
+ {
+ return;
+ }
+ LL_DEBUGS("Voice") << "On AudioEstablished." << LL_ENDL;
+ mWebRTCAudioInterface = audio_interface;
+ setVoiceConnectionState(VOICE_STATE_SESSION_ESTABLISHED);
+ });
+}
+
+
+//
+// The LLWebRTCVoiceConnection object will not be deleted
+// before the webrtc connection itself is shut down, so
+// we shouldn't be getting this callback on a nonexistant
+// this pointer.
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnRenegotiationNeeded()
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ LL_DEBUGS("Voice") << "Voice channel requires renegotiation." << LL_ENDL;
+ if (!mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ }
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN;
+ });
+}
+
+// callback from llwebrtc
+void LLVoiceWebRTCConnection::OnPeerConnectionClosed()
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ LL_DEBUGS("Voice") << "Peer connection has closed." << LL_ENDL;
+ if (mVoiceConnectionState == VOICE_STATE_WAIT_FOR_CLOSE)
+ {
+ setVoiceConnectionState(VOICE_STATE_CLOSED);
+ mOutstandingRequests--;
+ }
+ });
+}
+
+void LLVoiceWebRTCConnection::setMuteMic(bool muted)
+{
+ mMuted = muted;
+ if (mWebRTCAudioInterface)
+ {
+ mWebRTCAudioInterface->setMute(muted);
+ }
+}
+
+void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume)
+{
+ mSpeakerVolume = volume;
+ if (mWebRTCAudioInterface)
+ {
+ mWebRTCAudioInterface->setReceiveVolume(volume);
+ }
+}
+
+void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume)
+{
+ boost::json::object root = {{"ug", {id.asString(), (uint32_t) (volume * 200)}}};
+ std::string json_data = boost::json::serialize(root);
+ if (mWebRTCDataInterface)
+ {
+ mWebRTCDataInterface->sendData(json_data, false);
+ }
+}
+
+void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute)
+{
+ boost::json::object root = {{"m", {id.asString(), mute}}};
+ std::string json_data = boost::json::serialize(root);
+ if (mWebRTCDataInterface)
+ {
+ mWebRTCDataInterface->sendData(json_data, false);
+ }
+}
+
+
+// Send data to the Secondlife WebRTC server via the webrtc
+// data channel.
+void LLVoiceWebRTCConnection::sendData(const std::string &data)
+{
+ if (getVoiceConnectionState() == VOICE_STATE_SESSION_UP && mWebRTCDataInterface)
+ {
+ mWebRTCDataInterface->sendData(data, false);
+ }
+}
+
+// Tell the simulator that we're shutting down a voice connection.
+// The simulator will pass this on to the Secondlife WebRTC server.
+void LLVoiceWebRTCConnection::breakVoiceConnectionCoro()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LL_DEBUGS("Voice") << "Disconnecting voice." << LL_ENDL;
+ if (mWebRTCDataInterface)
+ {
+ mWebRTCDataInterface->unsetDataObserver(this);
+ mWebRTCDataInterface = nullptr;
+ }
+ mWebRTCAudioInterface = nullptr;
+ LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID);
+ if (!regionp || !regionp->capabilitiesReceived())
+ {
+ LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL;
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ mOutstandingRequests--;
+ return;
+ }
+
+ std::string url = regionp->getCapability("ProvisionVoiceAccountRequest");
+ if (url.empty())
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ mOutstandingRequests--;
+ return;
+ }
+
+ LL_DEBUGS("Voice") << "region ready for voice break; url=" << url << LL_ENDL;
+
+ LLVoiceWebRTCStats::getInstance()->provisionAttemptStart();
+ LLSD body;
+ body["logout"] = true;
+ body["viewer_session"] = mViewerSession;
+ body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE;
+
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(
+ new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::breakVoiceConnection",
+ LLCore::HttpRequest::DEFAULT_POLICY_ID));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+
+ httpOpts->setWantHeaders(true);
+
+ mOutstandingRequests++;
+
+ // tell the server to shut down the connection as a courtesy.
+ // shutdownConnection will drop the WebRTC connection which will
+ // also shut things down.
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts);
+
+ mOutstandingRequests--;
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+}
+
+// Tell the simulator to tell the Secondlife WebRTC server that we want a voice
+// connection. The SDP is sent up as part of this, and the simulator will respond
+// with an 'answer' which is in the form of another SDP. The webrtc library
+// will use the offer and answer to negotiate the session.
+void LLVoiceWebRTCSpatialConnection::requestVoiceConnection()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID);
+
+ LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL;
+ if (!regionp || !regionp->capabilitiesReceived())
+ {
+ LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL;
+
+ // try again.
+ setVoiceConnectionState(VOICE_STATE_REQUEST_CONNECTION);
+ return;
+ }
+
+ std::string url = regionp->getCapability("ProvisionVoiceAccountRequest");
+ if (url.empty())
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ return;
+ }
+
+ LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL;
+
+ LLVoiceWebRTCStats::getInstance()->provisionAttemptStart();
+ LLSD body;
+ LLSD jsep;
+ jsep["type"] = "offer";
+ jsep["sdp"] = mChannelSDP;
+ body["jsep"] = jsep;
+ if (mParcelLocalID != INVALID_PARCEL_ID)
+ {
+ body["parcel_local_id"] = mParcelLocalID;
+ }
+ body["channel_type"] = "local";
+ body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE;
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(
+ new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::requestVoiceConnection",
+ LLCore::HttpRequest::DEFAULT_POLICY_ID));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+
+ httpOpts->setWantHeaders(true);
+ mOutstandingRequests++;
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (status)
+ {
+ OnVoiceConnectionRequestSuccess(result);
+ }
+ else
+ {
+ switch (status.getType())
+ {
+ case HTTP_CONFLICT:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL;
+ break;
+ case HTTP_UNAUTHORIZED:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED;
+ break;
+ default:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN;
+ break;
+ }
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ }
+ mOutstandingRequests--;
+}
+
+void LLVoiceWebRTCConnection::OnVoiceConnectionRequestSuccess(const LLSD &result)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (LLWebRTCVoiceClient::isShuttingDown())
+ {
+ return;
+ }
+ LLVoiceWebRTCStats::getInstance()->provisionAttemptEnd(true);
+
+ if (result.has("viewer_session") &&
+ result.has("jsep") &&
+ result["jsep"].has("type") &&
+ result["jsep"]["type"] == "answer" &&
+ result["jsep"].has("sdp"))
+ {
+ mRemoteChannelSDP = result["jsep"]["sdp"].asString();
+ mViewerSession = result["viewer_session"];
+ }
+ else
+ {
+ LL_WARNS("Voice") << "Invalid voice provision request result:" << result << LL_ENDL;
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ return;
+ }
+
+ LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response"
+ << " channel sdp " << mRemoteChannelSDP << LL_ENDL;
+ mWebRTCPeerConnectionInterface->AnswerAvailable(mRemoteChannelSDP);
+}
+
+static llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions getConnectionOptions()
+{
+ llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions options;
+ llwebrtc::LLWebRTCPeerConnectionInterface::InitOptions::IceServers servers;
+
+ // TODO: Pull these from login
+ std::string grid = LLGridManager::getInstance()->getGridLoginID();
+ std::transform(grid.begin(), grid.end(), grid.begin(), [](unsigned char c){ return std::tolower(c); });
+ int num_servers = 2;
+ if (grid == "agni")
+ {
+ num_servers = 3;
+ }
+ for (int i=1; i <= num_servers; i++)
+ {
+ servers.mUrls.push_back(llformat("stun:stun%d.%s.secondlife.io:3478", i, grid.c_str()));
+ }
+ options.mServers.push_back(servers);
+ return options;
+}
+
+
+// Primary state machine for negotiating a single voice connection to the
+// Secondlife WebRTC server.
+bool LLVoiceWebRTCConnection::connectionStateMachine()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ processIceUpdates();
+
+ switch (getVoiceConnectionState())
+ {
+ case VOICE_STATE_START_SESSION:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("VOICE_STATE_START_SESSION")
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ break;
+ }
+ mIceCompleted = false;
+ setVoiceConnectionState(VOICE_STATE_WAIT_FOR_SESSION_START);
+
+ // tell the webrtc library that we want a connection. The library will
+ // respond with an offer on a separate thread, which will cause
+ // the session state to change.
+ if (!mWebRTCPeerConnectionInterface->initializeConnection(getConnectionOptions()))
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ }
+ break;
+ }
+
+ case VOICE_STATE_WAIT_FOR_SESSION_START:
+ {
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ }
+ break;
+ }
+
+ case VOICE_STATE_REQUEST_CONNECTION:
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ break;
+ }
+ // Ask the sim to ask the Secondlife WebRTC server for a connection to
+ // a given voice channel. On completion, we'll move on to the
+ // VOICE_STATE_SESSION_ESTABLISHED via a callback on a webrtc thread.
+ setVoiceConnectionState(VOICE_STATE_CONNECTION_WAIT);
+ LLCoros::getInstance()->launch("LLVoiceWebRTCConnection::requestVoiceConnectionCoro",
+ boost::bind(&LLVoiceWebRTCConnection::requestVoiceConnectionCoro, this));
+ break;
+
+ case VOICE_STATE_CONNECTION_WAIT:
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_DISCONNECT);
+ }
+ break;
+
+ case VOICE_STATE_SESSION_ESTABLISHED:
+ {
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_DISCONNECT);
+ break;
+ }
+ // update the peer connection with the various characteristics of
+ // this connection.
+ mWebRTCAudioInterface->setMute(mMuted);
+ mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume);
+ LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID);
+ setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL);
+ break;
+ }
+
+ case VOICE_STATE_WAIT_FOR_DATA_CHANNEL:
+ {
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_DISCONNECT);
+ break;
+ }
+ if (mWebRTCDataInterface) // the interface will be set when the session is negotiated.
+ {
+ sendJoin(); // tell the Secondlife WebRTC server that we're here via the data channel.
+ setVoiceConnectionState(VOICE_STATE_SESSION_UP);
+ if (isSpatial())
+ {
+ LLWebRTCVoiceClient::getInstance()->updatePosition();
+ LLWebRTCVoiceClient::getInstance()->sendPositionUpdate(true);
+ }
+ }
+ break;
+ }
+
+ case VOICE_STATE_SESSION_UP:
+ {
+ mRetryWaitPeriod = 0;
+ mRetryWaitSecs = ((F32) rand() / (RAND_MAX)) + 0.5;
+
+ // we'll stay here as long as the session remains up.
+ if (mShutDown)
+ {
+ setVoiceConnectionState(VOICE_STATE_DISCONNECT);
+ }
+ break;
+ }
+
+ case VOICE_STATE_SESSION_RETRY:
+ // only retry ever 'n' seconds
+ if (mRetryWaitPeriod++ * UPDATE_THROTTLE_SECONDS > mRetryWaitSecs)
+ {
+ // something went wrong, so notify that the connection has failed.
+ LLWebRTCVoiceClient::getInstance()->OnConnectionFailure(mChannelID, mRegionID, mCurrentStatus);
+ setVoiceConnectionState(VOICE_STATE_DISCONNECT);
+ mRetryWaitPeriod = 0;
+ if (mRetryWaitSecs < MAX_RETRY_WAIT_SECONDS)
+ {
+ // back off the retry period, and do it by a small random
+ // bit so all clients don't reconnect at once.
+ mRetryWaitSecs += ((F32) rand() / (RAND_MAX)) + 0.5;
+ mRetryWaitPeriod = 0;
+ }
+ }
+ break;
+
+ case VOICE_STATE_DISCONNECT:
+ setVoiceConnectionState(VOICE_STATE_WAIT_FOR_EXIT);
+ LLCoros::instance().launch("LLVoiceWebRTCConnection::breakVoiceConnectionCoro",
+ boost::bind(&LLVoiceWebRTCConnection::breakVoiceConnectionCoro, this));
+ break;
+
+ case VOICE_STATE_WAIT_FOR_EXIT:
+ break;
+
+ case VOICE_STATE_SESSION_EXIT:
+ {
+ setVoiceConnectionState(VOICE_STATE_WAIT_FOR_CLOSE);
+ mOutstandingRequests++;
+ mWebRTCPeerConnectionInterface->shutdownConnection();
+ break;
+ case VOICE_STATE_WAIT_FOR_CLOSE:
+ break;
+ case VOICE_STATE_CLOSED:
+ if (!mShutDown)
+ {
+ mVoiceConnectionState = VOICE_STATE_START_SESSION;
+ }
+ else
+ {
+ // if we still have outstanding http or webrtc calls, wait for them to
+ // complete so we don't delete objects while they still may be used.
+ if (mOutstandingRequests <= 0)
+ {
+ LLWebRTCVoiceClient::getInstance()->OnConnectionShutDown(mChannelID, mRegionID);
+ return false;
+ }
+ }
+ break;
+ }
+
+ default:
+ {
+ LL_WARNS("Voice") << "Unknown voice control state " << getVoiceConnectionState() << LL_ENDL;
+ return false;
+ }
+ }
+ return true;
+}
+
+// Data has been received on the webrtc data channel
+// incoming data will be a json structure (if it's not binary.) We may pack
+// binary for size reasons. Most of the keys in the json objects are
+// single or double characters for size reasons.
+// The primary element is:
+// An object where each key is an agent id. (in the future, we may allow
+// integer indices into an agentid list, populated on join commands. For size.
+// Each key will point to a json object with keys identifying what's updated.
+// 'p' - audio source power (level/volume) (int8 as int)
+// 'j' - object of join data (currently only a boolean 'p' marking a primary participant)
+// 'l' - boolean, always true if exists.
+// 'v' - boolean - voice activity has been detected.
+
+// llwebrtc callback
+void LLVoiceWebRTCConnection::OnDataReceived(const std::string& data, bool binary)
+{
+ LL::WorkQueue::postMaybe(mMainQueue, [=] { LLVoiceWebRTCConnection::OnDataReceivedImpl(data, binary); });
+}
+
+//
+// The LLWebRTCVoiceConnection object will not be deleted
+// before the webrtc connection itself is shut down, so
+// we shouldn't be getting this callback on a nonexistant
+// this pointer.
+void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool binary)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ if (mShutDown)
+ {
+ return;
+ }
+
+ if (binary)
+ {
+ LL_WARNS("Voice") << "Binary data received from data channel." << LL_ENDL;
+ return;
+ }
+
+ boost::json::error_code ec;
+ boost::json::value voice_data_parsed = boost::json::parse(data, ec);
+ if (!ec) // don't collect comments
+ {
+ if (!voice_data_parsed.is_object())
+ {
+ LL_WARNS("Voice") << "Expected object from data channel:" << data << LL_ENDL;
+ return;
+ }
+ boost::json::object voice_data = voice_data_parsed.as_object();
+ bool new_participant = false;
+ boost::json::object mute;
+ boost::json::object user_gain;
+ for (auto &participant_elem : voice_data)
+ {
+ boost::json::string participant_id(participant_elem.key());
+ LLUUID agent_id(participant_id.c_str());
+ if (agent_id.isNull())
+ {
+ // probably a test client.
+ continue;
+ }
+
+ if (!participant_elem.value().is_object())
+ {
+ continue;
+ }
+
+ boost::json::object participant_obj = participant_elem.value().as_object();
+
+ LLWebRTCVoiceClient::participantStatePtr_t participant =
+ LLWebRTCVoiceClient::getInstance()->findParticipantByID(mChannelID, agent_id);
+ bool joined = false;
+ bool primary = false; // we ignore any 'joins' reported about participants
+ // that come from voice servers that aren't their primary
+ // voice server. This will happen with cross-region voice
+ // where a participant on a neighboring region may be
+ // connected to multiple servers. We don't want to
+ // add new identical participants from all of those servers.
+ if (participant_obj.contains("j") &&
+ participant_obj["j"].is_object())
+ {
+ // a new participant has announced that they're joining.
+ joined = true;
+ if (participant_elem.value().as_object()["j"].as_object().contains("p") &&
+ participant_elem.value().as_object()["j"].as_object()["p"].is_bool())
+ {
+ primary = participant_elem.value().as_object()["j"].as_object()["p"].as_bool();
+ }
+
+ // track incoming participants that are muted so we can mute their connections (or set their volume)
+ bool isMuted = LLMuteList::getInstance()->isMuted(agent_id, LLMute::flagVoiceChat);
+ if (isMuted)
+ {
+ mute[participant_id] = true;
+ }
+ F32 volume;
+ if(LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(agent_id, volume))
+ {
+ user_gain[participant_id] = (uint32_t)(volume * 200);
+ }
+ }
+
+ new_participant |= joined;
+ if (!participant && joined && (primary || !isSpatial()))
+ {
+ participant = LLWebRTCVoiceClient::getInstance()->addParticipantByID(mChannelID, agent_id, mRegionID);
+ }
+
+ if (participant)
+ {
+ if (participant_obj.contains("l") && participant_obj["l"].is_bool() && participant_obj["l"].as_bool())
+ {
+ // an existing participant is leaving.
+ if (agent_id != gAgentID)
+ {
+ LLWebRTCVoiceClient::getInstance()->removeParticipantByID(mChannelID, agent_id, mRegionID);
+ }
+ }
+ else
+ {
+ // we got a 'power' update.
+ if (participant_obj.contains("p") && participant_obj["p"].is_number())
+ {
+ participant->mLevel = (F32)participant_obj["p"].as_int64();
+ }
+
+ if (participant_obj.contains("v") && participant_obj["v"].is_bool())
+ {
+ participant->mIsSpeaking = participant_obj["v"].as_bool();
+ }
+
+ if (participant_obj.contains("v") && participant_obj["m"].is_bool())
+ {
+ participant->mIsModeratorMuted = participant_obj["m"].as_bool();
+ ;
+ }
+ }
+ }
+ }
+
+ // tell the simulator to set the mute and volume data for this
+ // participant, if there are any updates.
+ boost::json::object root;
+ if (mute.size() > 0)
+ {
+ root["m"] = mute;
+ }
+ if (user_gain.size() > 0)
+ {
+ root["ug"] = user_gain;
+ }
+ if (root.size() > 0)
+ {
+ std::string json_data = boost::json::serialize(root);
+ mWebRTCDataInterface->sendData(json_data, false);
+ }
+ }
+}
+
+//
+// The LLWebRTCVoiceConnection object will not be deleted
+// before the webrtc connection itself is shut down, so
+// we shouldn't be getting this callback on a nonexistant
+// this pointer.
+// nor should data_interface be invalid if the LLWebRTCVoiceConnection
+// is shut down.
+
+// llwebrtc callback
+void LLVoiceWebRTCConnection::OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface)
+{
+ LL::WorkQueue::postMaybe(mMainQueue,
+ [=] {
+ if (mShutDown)
+ {
+ return;
+ }
+
+ if (data_interface)
+ {
+ mWebRTCDataInterface = data_interface;
+ mWebRTCDataInterface->setDataObserver(this);
+ }
+ });
+}
+
+// tell the Secondlife WebRTC server that
+// we're joining and whether we're
+// joining a server associated with the
+// the region we currently occupy or not (primary)
+// The WebRTC voice server will pass this info
+// to peers.
+void LLVoiceWebRTCConnection::sendJoin()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+
+ boost::json::object root;
+ boost::json::object join_obj;
+ LLUUID regionID = gAgent.getRegion()->getRegionID();
+ if ((regionID == mRegionID) || !isSpatial())
+ {
+ join_obj["p"] = true;
+ }
+ root["j"] = join_obj;
+ std::string json_data = boost::json::serialize(root);
+ mWebRTCDataInterface->sendData(json_data, false);
+}
+
+/////////////////////////////
+// WebRTC Spatial Connection
+
+LLVoiceWebRTCSpatialConnection::LLVoiceWebRTCSpatialConnection(const LLUUID &regionID,
+ S32 parcelLocalID,
+ const std::string &channelID) :
+ LLVoiceWebRTCConnection(regionID, channelID),
+ mParcelLocalID(parcelLocalID)
+{
+}
+
+LLVoiceWebRTCSpatialConnection::~LLVoiceWebRTCSpatialConnection()
+{
+}
+
+void LLVoiceWebRTCSpatialConnection::setMuteMic(bool muted)
+{
+ if (mMuted != muted)
+ {
+ mMuted = muted;
+ if (mWebRTCAudioInterface)
+ {
+ LLViewerRegion *regionp = gAgent.getRegion();
+ if (regionp && mRegionID == regionp->getRegionID())
+ {
+ mWebRTCAudioInterface->setMute(muted);
+ }
+ else
+ {
+ // Always mute this agent with respect to neighboring regions.
+ // Peers don't want to hear this agent from multiple regions
+ // as that'll echo.
+ mWebRTCAudioInterface->setMute(true);
+ }
+ }
+ }
+}
+
+/////////////////////////////
+// WebRTC Spatial Connection
+
+LLVoiceWebRTCAdHocConnection::LLVoiceWebRTCAdHocConnection(const LLUUID &regionID,
+ const std::string& channelID,
+ const std::string& credentials) :
+ LLVoiceWebRTCConnection(regionID, channelID),
+ mCredentials(credentials)
+{
+}
+
+LLVoiceWebRTCAdHocConnection::~LLVoiceWebRTCAdHocConnection()
+{
+}
+
+// Add-hoc connections require a different channel type
+// as they go to a different set of Secondlife WebRTC servers.
+// They also require credentials for the given channels.
+// So, we have a separate requestVoiceConnection call.
+void LLVoiceWebRTCAdHocConnection::requestVoiceConnection()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE
+
+ LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(mRegionID);
+
+ LL_DEBUGS("Voice") << "Requesting voice connection." << LL_ENDL;
+ if (!regionp || !regionp->capabilitiesReceived())
+ {
+ LL_DEBUGS("Voice") << "no capabilities for voice provisioning; retrying " << LL_ENDL;
+ // try again.
+ setVoiceConnectionState(VOICE_STATE_REQUEST_CONNECTION);
+ return;
+ }
+
+ std::string url = regionp->getCapability("ProvisionVoiceAccountRequest");
+ if (url.empty())
+ {
+ setVoiceConnectionState(VOICE_STATE_SESSION_RETRY);
+ return;
+ }
+
+ LLVoiceWebRTCStats::getInstance()->provisionAttemptStart();
+ LLSD body;
+ LLSD jsep;
+ jsep["type"] = "offer";
+ {
+ jsep["sdp"] = mChannelSDP;
+ }
+ body["jsep"] = jsep;
+ body["credentials"] = mCredentials;
+ body["channel"] = mChannelID;
+ body["channel_type"] = "multiagent";
+ body["voice_server_type"] = WEBRTC_VOICE_SERVER_TYPE;
+
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(
+ new LLCoreHttpUtil::HttpCoroutineAdapter("LLVoiceWebRTCAdHocConnection::requestVoiceConnection",
+ LLCore::HttpRequest::DEFAULT_POLICY_ID));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+ LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions);
+
+ httpOpts->setWantHeaders(true);
+ mOutstandingRequests++;
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (!status)
+ {
+ switch (status.getType())
+ {
+ case HTTP_CONFLICT:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL;
+ break;
+ case HTTP_UNAUTHORIZED:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED;
+ break;
+ default:
+ mCurrentStatus = LLVoiceClientStatusObserver::ERROR_UNKNOWN;
+ break;
+ }
+ setVoiceConnectionState(VOICE_STATE_SESSION_EXIT);
+ }
+ else
+ {
+ OnVoiceConnectionRequestSuccess(result);
+ }
+ mOutstandingRequests--;
+}
diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h
new file mode 100644
index 0000000000..480a7897cd
--- /dev/null
+++ b/indra/newview/llvoicewebrtc.h
@@ -0,0 +1,743 @@
+/**
+ * @file llvoicewebrtc.h
+ * @brief Declaration of LLWebRTCVoiceClient class which is the interface to the voice client process.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+#ifndef LL_VOICE_WEBRTC_H
+#define LL_VOICE_WEBRTC_H
+
+class LLWebRTCProtocolParser;
+
+#include "lliopipe.h"
+#include "llpumpio.h"
+#include "llchainio.h"
+#include "lliosocket.h"
+#include "v3math.h"
+#include "llframetimer.h"
+#include "llviewerregion.h"
+#include "llcallingcard.h" // for LLFriendObserver
+#include "lleventcoro.h"
+#include "llcoros.h"
+#include "llparcel.h"
+#include "llmutelist.h"
+#include <queue>
+#include "boost/json.hpp"
+
+#ifdef LL_USESYSTEMLIBS
+# include "expat.h"
+#else
+# include "expat/expat.h"
+#endif
+#include "llvoiceclient.h"
+
+// WebRTC Includes
+#include <llwebrtc.h>
+
+class LLAvatarName;
+class LLVoiceWebRTCConnection;
+typedef boost::shared_ptr<LLVoiceWebRTCConnection> connectionPtr_t;
+
+extern const std::string WEBRTC_VOICE_SERVER_TYPE;
+
+class LLWebRTCVoiceClient : public LLSingleton<LLWebRTCVoiceClient>,
+ virtual public LLVoiceModuleInterface,
+ public llwebrtc::LLWebRTCDevicesObserver,
+ public LLMuteListObserver
+{
+ LLSINGLETON(LLWebRTCVoiceClient);
+ LOG_CLASS(LLWebRTCVoiceClient);
+ virtual ~LLWebRTCVoiceClient();
+
+public:
+ /// @name LLVoiceModuleInterface virtual implementations
+ /// @see LLVoiceModuleInterface
+ //@{
+ void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector)
+ void terminate() override; // Call this to clean up during shutdown
+
+ static bool isShuttingDown() { return sShuttingDown; }
+
+ const LLVoiceVersionInfo& getVersion() override;
+
+ void updateSettings() override; // call after loading settings and whenever they change
+
+ // Returns true if WebRTC has successfully logged in and is not in error state
+ bool isVoiceWorking() const override;
+
+ std::string sipURIFromID(const LLUUID &id) override;
+
+ /////////////////////
+ /// @name Tuning
+ //@{
+ void tuningStart() override;
+ void tuningStop() override;
+ bool inTuningMode() override;
+
+ void tuningSetMicVolume(float volume) override;
+ void tuningSetSpeakerVolume(float volume) override;
+ float tuningGetEnergy(void) override;
+ //@}
+
+ /////////////////////
+ /// @name Devices
+ //@{
+ // This returns true when it's safe to bring up the "device settings" dialog in the prefs.
+ bool deviceSettingsAvailable() override;
+ bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel.
+
+ // Requery the WebRTC daemon for the current list of input/output devices.
+ // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed
+ // (use this if you want to know when it's done).
+ // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim.
+ void refreshDeviceLists(bool clearCurrentList = true) override;
+
+ void setCaptureDevice(const std::string& name) override;
+ void setRenderDevice(const std::string& name) override;
+
+ LLVoiceDeviceList& getCaptureDevices() override;
+ LLVoiceDeviceList& getRenderDevices() override;
+ //@}
+
+ void getParticipantList(std::set<LLUUID> &participants) override;
+ bool isParticipant(const LLUUID& speaker_id) override;
+
+ // Send a text message to the specified user, initiating the session if necessary.
+ // virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;};
+
+ // Returns true if calling back the session URI after the session has closed is possible.
+ // Currently this will be false only for PSTN P2P calls.
+ // NOTE: this will return true if the session can't be found.
+ bool isSessionCallBackPossible(const LLUUID &session_id) override;
+
+ // WebRTC doesn't preclude text im
+ bool isSessionTextIMPossible(const LLUUID &session_id) override { return true; }
+
+ ////////////////////////////
+ /// @name Channel stuff
+ //@{
+ // returns true iff the user is currently in a proximal (local spatial) channel.
+ // Note that gestures should only fire if this returns true.
+ bool inProximalChannel() override;
+
+ void setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) override
+ {
+ startAdHocSession(channelInfo, notify_on_first_join, hangup_on_last_leave);
+ }
+
+ bool setSpatialChannel(const LLSD &channelInfo) override;
+
+ void leaveNonSpatialChannel() override;
+
+ void processChannels(bool process) override;
+
+ void leaveChannel(bool stopTalking);
+
+ bool isCurrentChannel(const LLSD &channelInfo) override;
+ bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override;
+ //@}
+
+ LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return nullptr; }
+
+ LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override { return nullptr; }
+
+ /////////////////////////
+ /// @name Volume/gain
+ //@{
+ void setVoiceVolume(F32 volume) override;
+ void setMicGain(F32 volume) override;
+ //@}
+
+ /////////////////////////
+ /// @name enable disable voice and features
+ //@{
+ void setVoiceEnabled(bool enabled) override;
+ void setMuteMic(bool muted) override; // Set the mute state of the local mic.
+ //@}
+
+ //////////////////////////
+ /// @name nearby speaker accessors
+ std::string getDisplayName(const LLUUID& id) override;
+ bool isParticipantAvatar(const LLUUID &id) override;
+ bool getIsSpeaking(const LLUUID& id) override;
+ bool getIsModeratorMuted(const LLUUID& id) override;
+ F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is...
+ F32 getUserVolume(const LLUUID& id) override;
+ void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal)
+ //@}
+
+ //////////////////
+ /// @name LLMuteListObserver
+ //@{
+ void onChange() override;
+ void onChangeDetailed(const LLMute& ) override;
+ //@}
+
+ // authorize the user
+ void userAuthorized(const std::string &user_id, const LLUUID &agentID) override {};
+
+
+ void OnConnectionEstablished(const std::string& channelID, const LLUUID& regionID);
+ void OnConnectionShutDown(const std::string &channelID, const LLUUID &regionID);
+ void OnConnectionFailure(const std::string &channelID,
+ const LLUUID &regionID,
+ LLVoiceClientStatusObserver::EStatusType status_type = LLVoiceClientStatusObserver::ERROR_UNKNOWN);
+ void updatePosition(void); // update the internal position state
+ void sendPositionUpdate(bool force); // send the position to the voice server.
+ void updateOwnVolume();
+
+ //////////////////////////////
+ /// @name Status notification
+ //@{
+ void addObserver(LLVoiceClientStatusObserver* observer) override;
+ void removeObserver(LLVoiceClientStatusObserver* observer) override;
+ void addObserver(LLFriendObserver* observer) override;
+ void removeObserver(LLFriendObserver* observer) override;
+ void addObserver(LLVoiceClientParticipantObserver* observer) override;
+ void removeObserver(LLVoiceClientParticipantObserver* observer) override;
+ //@}
+
+ //////////////////////////////
+ /// @name Devices change notification
+ // LLWebRTCDevicesObserver
+ //@{
+ void OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices,
+ const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) override;
+ //@}
+ void OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices,
+ const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices);
+
+ struct participantState
+ {
+ public:
+ participantState(const LLUUID& agent_id, const LLUUID& region);
+
+ bool isAvatar();
+
+ std::string mURI;
+ LLUUID mAvatarID;
+ std::string mDisplayName;
+ LLFrameTimer mSpeakingTimeout;
+ F32 mLevel; // the current audio level of the participant
+ F32 mVolume; // the gain applied to the participant
+ bool mIsSpeaking;
+ bool mIsModeratorMuted;
+ LLUUID mRegion;
+ };
+ typedef boost::shared_ptr<participantState> participantStatePtr_t;
+
+ participantStatePtr_t findParticipantByID(const std::string &channelID, const LLUUID &id);
+ participantStatePtr_t addParticipantByID(const std::string& channelID, const LLUUID &id, const LLUUID& region);
+ void removeParticipantByID(const std::string& channelID, const LLUUID &id, const LLUUID& region);
+
+ protected:
+
+ typedef std::map<const LLUUID, participantStatePtr_t> participantUUIDMap;
+
+ class sessionState
+ {
+ public:
+ typedef boost::shared_ptr<sessionState> ptr_t;
+ typedef boost::weak_ptr<sessionState> wptr_t;
+
+ typedef boost::function<void(const ptr_t &)> sessionFunc_t;
+
+ static void addSession(const std::string &channelID, ptr_t& session);
+ virtual ~sessionState();
+
+ participantStatePtr_t addParticipant(const LLUUID& agent_id, const LLUUID& region);
+ void removeParticipant(const participantStatePtr_t &participant);
+ void removeAllParticipants(const LLUUID& region = LLUUID());
+
+ participantStatePtr_t findParticipantByID(const LLUUID& id);
+
+ static ptr_t matchSessionByChannelID(const std::string& channel_id);
+
+ void shutdownAllConnections();
+ void revive();
+
+ static void processSessionStates();
+
+ virtual bool processConnectionStates();
+
+ virtual void sendData(const std::string &data);
+
+ void setMuteMic(bool muted);
+ void setSpeakerVolume(F32 volume);
+ void setUserVolume(const LLUUID& id, F32 volume);
+
+ void setUserMute(const LLUUID& id, bool mute);
+
+ static void for_each(sessionFunc_t func);
+
+ static void reapEmptySessions();
+
+ bool isEmpty() { return mWebRTCConnections.empty(); }
+
+ virtual bool isSpatial() = 0;
+ virtual bool isEstate() = 0;
+ virtual bool isCallbackPossible() = 0;
+
+ std::string mHandle;
+ std::string mChannelID;
+ std::string mName;
+
+ bool mMuted; // this session is muted.
+ F32 mSpeakerVolume; // volume for this session.
+
+ bool mShuttingDown;
+
+ participantUUIDMap mParticipantsByUUID;
+
+ static bool hasSession(const std::string &sessionID)
+ { return mSessions.find(sessionID) != mSessions.end(); }
+
+ bool mHangupOnLastLeave; // notify observers after the session becomes empty.
+ bool mNotifyOnFirstJoin; // notify observers when the first peer joins.
+
+ protected:
+ sessionState();
+ std::list<connectionPtr_t> mWebRTCConnections;
+
+ private:
+
+ static std::map<std::string, ptr_t> mSessions; // canonical list of outstanding sessions.
+
+ static void for_eachPredicate(const std::pair<std::string,
+ LLWebRTCVoiceClient::sessionState::wptr_t> &a,
+ sessionFunc_t func);
+ };
+
+ typedef boost::shared_ptr<sessionState> sessionStatePtr_t;
+ typedef std::map<std::string, sessionStatePtr_t> sessionMap;
+
+ class estateSessionState : public sessionState
+ {
+ public:
+ estateSessionState();
+ bool processConnectionStates() override;
+
+ bool isSpatial() override { return true; }
+ bool isEstate() override { return true; }
+ bool isCallbackPossible() override { return false; }
+ };
+
+ class parcelSessionState : public sessionState
+ {
+ public:
+ parcelSessionState(const std::string& channelID, S32 parcel_local_id);
+
+ bool isSpatial() override { return true; }
+ bool isEstate() override { return false; }
+ bool isCallbackPossible() override { return false; }
+ };
+
+ class adhocSessionState : public sessionState
+ {
+ public:
+ adhocSessionState(const std::string &channelID,
+ const std::string& credentials,
+ bool notify_on_first_join,
+ bool hangup_on_last_leave);
+
+ bool isSpatial() override { return false; }
+ bool isEstate() override { return false; }
+
+ // only p2p-type adhoc sessions allow callback
+ bool isCallbackPossible() override { return mNotifyOnFirstJoin && mHangupOnLastLeave; }
+
+ // don't send spatial data to adhoc sessions.
+ void sendData(const std::string &data) override { }
+
+ protected:
+ std::string mCredentials;
+ };
+
+
+ ///////////////////////////////////////////////////////
+ // Private Member Functions
+ //////////////////////////////////////////////////////
+
+ static void predSendData(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const std::string& spatial_data);
+ static void predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level);
+ static void predSetMuteMic(const LLWebRTCVoiceClient::sessionStatePtr_t &session, bool mute);
+ static void predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume);
+ static void predShutdownSession(const LLWebRTCVoiceClient::sessionStatePtr_t &session);
+ static void predSetUserMute(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, bool mute);
+ static void predSetUserVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, const LLUUID& id, F32 volume);
+
+ //----------------------------------
+ // devices
+ void clearCaptureDevices();
+ void addCaptureDevice(const LLVoiceDevice& device);
+
+ void clearRenderDevices();
+ void addRenderDevice(const LLVoiceDevice& device);
+ void setDevicesListUpdated(bool state);
+
+ /////////////////////////////
+ // Sending updates of current state
+ void setListenerPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot);
+ void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot);
+
+ LLVector3d getListenerPosition() { return mListenerPosition; }
+ LLVector3d getSpeakerPosition() { return mAvatarPosition; }
+
+ void setEarLocation(S32 loc);
+
+
+ /////////////////////////////
+ // Accessors for data related to nearby speakers
+
+ /////////////////////////////
+ sessionStatePtr_t findP2PSession(const LLUUID &agent_id);
+
+ sessionStatePtr_t addSession(const std::string &channel_id, sessionState::ptr_t session);
+ void deleteSession(const sessionStatePtr_t &session);
+
+ // Does the actual work to get out of the audio session
+ void leaveAudioSession();
+
+ friend class LLWebRTCVoiceClientCapResponder;
+
+
+ void lookupName(const LLUUID &id);
+ void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name);
+ void avatarNameResolved(const LLUUID &id, const std::string &name);
+ static void predAvatarNameResolution(const LLWebRTCVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name);
+
+ boost::signals2::connection mAvatarNameCacheConnection;
+
+private:
+
+ // helper function to retrieve the audio level
+ // Used in multiple places.
+ float getAudioLevel();
+
+ // Coroutine support methods
+ //---
+ void voiceConnectionCoro();
+
+ //---
+ /// Clean up objects created during a voice session.
+ void cleanUp();
+
+ LL::WorkQueue::weak_t mMainQueue;
+
+ bool mTuningMode;
+ F32 mTuningMicGain;
+ int mTuningSpeakerVolume;
+ bool mDevicesListUpdated; // set to true when the device list has been updated
+ // and false when the panelvoicedevicesettings has queried for an update status.
+ std::string mSpatialSessionCredentials;
+
+ std::string mMainSessionGroupHandle; // handle of the "main" session group.
+
+ sessionStatePtr_t mSession; // Session state for the current session
+
+ sessionStatePtr_t mNextSession; // Session state for the session we're trying to join
+
+ llwebrtc::LLWebRTCDeviceInterface *mWebRTCDeviceInterface;
+
+ LLVoiceDeviceList mCaptureDevices;
+ LLVoiceDeviceList mRenderDevices;
+
+ bool startEstateSession();
+ bool startParcelSession(const std::string& channelID, S32 parcelID);
+ bool startAdHocSession(const LLSD &channelInfo, bool notify_on_first_join, bool hangup_on_last_leave);
+
+ bool inSpatialChannel();
+ bool inOrJoiningChannel(const std::string &channelID);
+ bool inEstateChannel();
+
+ LLSD getAudioSessionChannelInfo();
+
+ void setHidden(bool hidden) override; //virtual
+
+ void enforceTether();
+
+ void updateNeighboringRegions();
+ std::set<LLUUID> getNeighboringRegions() { return mNeighboringRegions; }
+
+ LLVoiceVersionInfo mVoiceVersion;
+
+ bool mSpatialCoordsDirty;
+
+ LLVector3d mListenerPosition;
+ LLVector3d mListenerRequestedPosition;
+ LLVector3 mListenerVelocity;
+ LLQuaternion mListenerRot;
+
+ LLVector3d mAvatarPosition;
+ LLVector3 mAvatarVelocity;
+ LLQuaternion mAvatarRot;
+
+ std::set<LLUUID> mNeighboringRegions; // includes current region
+
+ bool mMuteMic;
+ bool mHidden; //Set to true during teleport to hide the agent's position.
+
+ enum
+ {
+ earLocCamera = 0, // ear at camera
+ earLocAvatar, // ear at avatar
+ earLocMixed // ear at avatar location/camera direction
+ };
+
+ S32 mEarLocation;
+
+ float mSpeakerVolume;
+
+ F32 mMicGain;
+
+ bool mVoiceEnabled;
+ bool mProcessChannels;
+
+ typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t;
+ observer_set_t mParticipantObservers;
+
+ void notifyParticipantObservers();
+
+ typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t;
+ status_observer_set_t mStatusObservers;
+
+ void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status);
+
+ bool mIsInTuningMode;
+ bool mIsProcessingChannels;
+ bool mIsCoroutineActive;
+
+ // These variables can last longer than WebRTC in coroutines so we need them as static
+ static bool sShuttingDown;
+
+ LLEventMailDrop mWebRTCPump;
+};
+
+
+class LLVoiceWebRTCStats : public LLSingleton<LLVoiceWebRTCStats>
+{
+ LLSINGLETON(LLVoiceWebRTCStats);
+ LOG_CLASS(LLVoiceWebRTCStats);
+ virtual ~LLVoiceWebRTCStats();
+
+ private:
+ F64SecondsImplicit mStartTime;
+
+ U32 mConnectCycles;
+
+ F64 mConnectTime;
+ U32 mConnectAttempts;
+
+ F64 mProvisionTime;
+ U32 mProvisionAttempts;
+
+ F64 mEstablishTime;
+ U32 mEstablishAttempts;
+
+ public:
+
+ void reset();
+ void connectionAttemptStart();
+ void connectionAttemptEnd(bool success);
+ void provisionAttemptStart();
+ void provisionAttemptEnd(bool success);
+ void establishAttemptStart();
+ void establishAttemptEnd(bool success);
+ LLSD read();
+};
+
+class LLVoiceWebRTCConnection :
+ public llwebrtc::LLWebRTCSignalingObserver,
+ public llwebrtc::LLWebRTCDataObserver
+{
+ public:
+ LLVoiceWebRTCConnection(const LLUUID &regionID, const std::string &channelID);
+
+ virtual ~LLVoiceWebRTCConnection() = 0;
+
+ //////////////////////////////
+ /// @name Signaling notification
+ // LLWebRTCSignalingObserver
+ //@{
+ void OnIceGatheringState(EIceGatheringState state) override;
+ void OnIceCandidate(const llwebrtc::LLWebRTCIceCandidate &candidate) override;
+ void OnOfferAvailable(const std::string &sdp) override;
+ void OnRenegotiationNeeded() override;
+ void OnPeerConnectionClosed() override;
+ void OnAudioEstablished(llwebrtc::LLWebRTCAudioInterface *audio_interface) override;
+ //@}
+
+ /////////////////////////
+ /// @name Data Notification
+ /// LLWebRTCDataObserver
+ //@{
+ void OnDataReceived(const std::string &data, bool binary) override;
+ void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override;
+ //@}
+
+ void OnDataReceivedImpl(const std::string &data, bool binary);
+
+ void sendJoin();
+ void sendData(const std::string &data);
+
+ void processIceUpdates();
+
+ void processIceUpdatesCoro();
+
+ virtual void setMuteMic(bool muted);
+ virtual void setSpeakerVolume(F32 volume);
+
+ void setUserVolume(const LLUUID& id, F32 volume);
+ void setUserMute(const LLUUID& id, bool mute);
+
+ bool connectionStateMachine();
+
+ virtual bool isSpatial() = 0;
+
+ LLUUID getRegionID() { return mRegionID; }
+
+ void shutDown()
+ {
+ mShutDown = true;
+ }
+
+ void OnVoiceConnectionRequestSuccess(const LLSD &body);
+
+ protected:
+ typedef enum e_voice_connection_state
+ {
+ VOICE_STATE_ERROR = 0x0,
+ VOICE_STATE_START_SESSION = 0x1,
+ VOICE_STATE_WAIT_FOR_SESSION_START = 0x2,
+ VOICE_STATE_REQUEST_CONNECTION = 0x4,
+ VOICE_STATE_CONNECTION_WAIT = 0x8,
+ VOICE_STATE_SESSION_ESTABLISHED = 0x10,
+ VOICE_STATE_WAIT_FOR_DATA_CHANNEL = 0x20,
+ VOICE_STATE_SESSION_UP = 0x40,
+ VOICE_STATE_SESSION_RETRY = 0x80,
+ VOICE_STATE_DISCONNECT = 0x100,
+ VOICE_STATE_WAIT_FOR_EXIT = 0x200,
+ VOICE_STATE_SESSION_EXIT = 0x400,
+ VOICE_STATE_WAIT_FOR_CLOSE = 0x800,
+ VOICE_STATE_CLOSED = 0x1000,
+ VOICE_STATE_SESSION_STOPPING = 0x1F80
+ } EVoiceConnectionState;
+
+ EVoiceConnectionState mVoiceConnectionState;
+ LL::WorkQueue::weak_t mMainQueue;
+
+ void setVoiceConnectionState(EVoiceConnectionState new_voice_connection_state)
+ {
+ if (new_voice_connection_state & VOICE_STATE_SESSION_STOPPING)
+ {
+ // the new state is shutdown or restart.
+ mVoiceConnectionState = new_voice_connection_state;
+ return;
+ }
+ if (mVoiceConnectionState & VOICE_STATE_SESSION_STOPPING)
+ {
+ // we're currently shutting down or restarting, so ignore any
+ // state changes.
+ return;
+ }
+
+ mVoiceConnectionState = new_voice_connection_state;
+ }
+ EVoiceConnectionState getVoiceConnectionState()
+ {
+ return mVoiceConnectionState;
+ }
+
+ virtual void requestVoiceConnection() = 0;
+ void requestVoiceConnectionCoro() { requestVoiceConnection(); }
+
+ void breakVoiceConnectionCoro();
+
+ LLVoiceClientStatusObserver::EStatusType mCurrentStatus;
+
+ LLUUID mRegionID;
+ LLUUID mViewerSession;
+ std::string mChannelID;
+
+ std::string mChannelSDP;
+ std::string mRemoteChannelSDP;
+
+ bool mMuted;
+ F32 mSpeakerVolume;
+
+ bool mShutDown;
+ S32 mOutstandingRequests;
+
+ S32 mRetryWaitPeriod; // number of UPDATE_THROTTLE_SECONDS we've
+ // waited since our last attempt to connect.
+ F32 mRetryWaitSecs; // number of seconds to wait before next retry
+
+
+
+ std::vector<llwebrtc::LLWebRTCIceCandidate> mIceCandidates;
+ bool mIceCompleted;
+
+ llwebrtc::LLWebRTCPeerConnectionInterface *mWebRTCPeerConnectionInterface;
+ llwebrtc::LLWebRTCAudioInterface *mWebRTCAudioInterface;
+ llwebrtc::LLWebRTCDataInterface *mWebRTCDataInterface;
+};
+
+
+class LLVoiceWebRTCSpatialConnection :
+ public LLVoiceWebRTCConnection
+{
+ public:
+ LLVoiceWebRTCSpatialConnection(const LLUUID &regionID, S32 parcelLocalID, const std::string &channelID);
+
+ virtual ~LLVoiceWebRTCSpatialConnection();
+
+ void setMuteMic(bool muted) override;
+
+ bool isSpatial() override { return true; }
+
+
+protected:
+
+ void requestVoiceConnection() override;
+
+ S32 mParcelLocalID;
+};
+
+class LLVoiceWebRTCAdHocConnection : public LLVoiceWebRTCConnection
+{
+ public:
+ LLVoiceWebRTCAdHocConnection(const LLUUID &regionID, const std::string &channelID, const std::string& credentials);
+
+ virtual ~LLVoiceWebRTCAdHocConnection();
+
+ bool isSpatial() override { return false; }
+
+ protected:
+ void requestVoiceConnection() override;
+
+ std::string mCredentials;
+};
+
+#define VOICE_ELAPSED LLVoiceTimer(__FUNCTION__);
+
+#endif //LL_WebRTC_VOICE_CLIENT_H
+
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index de62256134..7ca3fbd1ad 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -851,19 +851,9 @@ void LLVOVolume::updateTextureVirtualSize(bool forced)
if (mSculptTexture.notNull())
{
- mSculptTexture->setBoostLevel(llmax((S32)mSculptTexture->getBoostLevel(),
- (S32)LLGLTexture::BOOST_SCULPTED));
mSculptTexture->setForSculpt() ;
- if(!mSculptTexture->isCachedRawImageReady())
- {
- S32 lod = llmin(mLOD, 3);
- F32 lodf = ((F32)(lod + 1.0f)/4.f);
- F32 tex_size = lodf * LLViewerTexture::sMaxSculptRez ;
- mSculptTexture->addTextureStats(2.f * tex_size * tex_size, false);
- }
-
- S32 texture_discard = mSculptTexture->getCachedRawImageLevel(); //try to match the texture
+ S32 texture_discard = mSculptTexture->getRawImageLevel(); //try to match the texture
S32 current_discard = getVolume() ? getVolume()->getSculptLevel() : -2 ;
if (texture_discard >= 0 && //texture has some data available
@@ -1159,7 +1149,9 @@ void LLVOVolume::updateSculptTexture()
LLUUID id = sculpt_params->getSculptTexture();
if (id.notNull())
{
- mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
+ mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_SCULPTED, LLViewerTexture::LOD_TEXTURE);
+ mSculptTexture->forceToSaveRawImage(0, F32_MAX);
+ mSculptTexture->addTextureStats(256.f*256.f);
}
mSkinInfoUnavaliable = false;
@@ -1252,8 +1244,22 @@ void LLVOVolume::sculpt()
S8 sculpt_components = 0;
const U8* sculpt_data = NULL;
- S32 discard_level = mSculptTexture->getCachedRawImageLevel() ;
- LLImageRaw* raw_image = mSculptTexture->getCachedRawImage() ;
+ S32 discard_level = mSculptTexture->getRawImageLevel() ;
+ LLImageRaw* raw_image = mSculptTexture->getRawImage() ;
+
+ if (!raw_image)
+ {
+ raw_image = mSculptTexture->getSavedRawImage();
+ S32 discard_level = mSculptTexture->getSavedRawImageLevel();
+ }
+
+ if (!raw_image)
+ {
+ // last resort, read back from GL
+ mSculptTexture->readbackRawImage();
+ raw_image = mSculptTexture->getRawImage();
+ discard_level = mSculptTexture->getRawImageLevel();
+ }
S32 max_discard = mSculptTexture->getMaxDiscardLevel();
if (discard_level > max_discard)
@@ -1310,8 +1316,6 @@ void LLVOVolume::sculpt()
if(!raw_image)
{
- llassert(discard_level < 0) ;
-
sculpt_width = 0;
sculpt_height = 0;
sculpt_data = NULL ;
@@ -2039,6 +2043,7 @@ bool LLVOVolume::updateGeometry(LLDrawable *drawable)
if (mDrawable->isState(LLDrawable::REBUILD_RIGGED))
{
+ LL_PROFILE_ZONE_NAMED_CATEGORY_VOLUME("rebuild rigged");
updateRiggedVolume(false);
genBBoxes(false);
mDrawable->clearState(LLDrawable::REBUILD_RIGGED);
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index a004dab8c9..07e02e042c 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -306,7 +306,7 @@ public:
bool isReflectionProbe() const override;
F32 getReflectionProbeAmbiance() const;
F32 getReflectionProbeNearClip() const;
- bool getReflectionProbeIsBox() const;
+ bool getReflectionProbeIsBox() const override;
bool getReflectionProbeIsDynamic() const;
bool getReflectionProbeIsMirror() const;
diff --git a/indra/newview/llvowater.cpp b/indra/newview/llvowater.cpp
index 34040e1aca..2bc849a74f 100644
--- a/indra/newview/llvowater.cpp
+++ b/indra/newview/llvowater.cpp
@@ -59,7 +59,6 @@ LLVOWater::LLVOWater(const LLUUID &id,
mbCanSelect = false;
setScale(LLVector3(256.f, 256.f, 0.f)); // Hack for setting scale for bounding boxes/visibility.
- mUseTexture = true;
mIsEdgePatch = false;
}
@@ -101,14 +100,7 @@ LLDrawable *LLVOWater::createDrawable(LLPipeline *pipeline)
LLDrawPoolWater *pool = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER);
- if (mUseTexture)
- {
- mDrawable->setNumFaces(1, pool, mRegionp->getLand().getWaterTexture());
- }
- else
- {
- mDrawable->setNumFaces(1, pool, LLWorld::getInstance()->getDefaultWaterTexture());
- }
+ mDrawable->setNumFaces(1, pool, LLWorld::getInstance()->getDefaultWaterTexture());
return mDrawable;
}
@@ -249,11 +241,6 @@ void setVecZ(LLVector3& v)
v.mV[VZ] = 1;
}
-void LLVOWater::setUseTexture(const bool use_texture)
-{
- mUseTexture = use_texture;
-}
-
void LLVOWater::setIsEdgePatch(const bool edge_patch)
{
mIsEdgePatch = edge_patch;
diff --git a/indra/newview/llvowater.h b/indra/newview/llvowater.h
index adae86691a..ba3da510c4 100644
--- a/indra/newview/llvowater.h
+++ b/indra/newview/llvowater.h
@@ -70,13 +70,10 @@ public:
/*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate.
- void setUseTexture(const bool use_texture);
void setIsEdgePatch(const bool edge_patch);
- bool getUseTexture() const { return mUseTexture; }
bool getIsEdgePatch() const { return mIsEdgePatch; }
protected:
- bool mUseTexture;
bool mIsEdgePatch;
S32 mRenderType;
};
diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp
index 2eadea20ef..6470c81aaa 100644
--- a/indra/newview/llworld.cpp
+++ b/indra/newview/llworld.cpp
@@ -961,7 +961,6 @@ void LLWorld::updateWaterObjects()
if (!getRegionFromHandle(region_handle))
{ // No region at that area, so make water
LLVOWater* waterp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, gAgent.getRegion());
- waterp->setUseTexture(false);
waterp->setPositionGlobal(LLVector3d(x + rwidth/2,
y + rwidth/2,
256.f + water_height));
@@ -1015,7 +1014,6 @@ void LLWorld::updateWaterObjects()
mEdgeWaterObjects[dir] = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_VOID_WATER,
gAgent.getRegion());
waterp = mEdgeWaterObjects[dir];
- waterp->setUseTexture(false);
waterp->setIsEdgePatch(true);
gPipeline.createObject(waterp);
}
diff --git a/indra/newview/llworldmipmap.cpp b/indra/newview/llworldmipmap.cpp
index e226fd4748..d8ea2b884f 100644
--- a/indra/newview/llworldmipmap.cpp
+++ b/indra/newview/llworldmipmap.cpp
@@ -178,6 +178,7 @@ LLPointer<LLViewerFetchedTexture> LLWorldMipmap::getObjectsTile(U32 grid_x, U32
}
}
+//static
LLPointer<LLViewerFetchedTexture> LLWorldMipmap::loadObjectsTile(U32 grid_x, U32 grid_y, S32 level)
{
// Get the grid coordinates
diff --git a/indra/newview/llworldmipmap.h b/indra/newview/llworldmipmap.h
index ab98b55b72..907f24d1e7 100644
--- a/indra/newview/llworldmipmap.h
+++ b/indra/newview/llworldmipmap.h
@@ -74,11 +74,13 @@ public:
// Convert world coordinates to mipmap grid coordinates at a given level
static void globalToMipmap(F64 global_x, F64 global_y, S32 level, U32* grid_x, U32* grid_y);
+ // Load the relevant tile from S3
+ static LLPointer<LLViewerFetchedTexture> loadObjectsTile(U32 grid_x, U32 grid_y, S32 level);
+
private:
// Get a handle (key) from grid coordinates
U64 convertGridToHandle(U32 grid_x, U32 grid_y) { return to_region_handle(grid_x * REGION_WIDTH_UNITS, grid_y * REGION_WIDTH_UNITS); }
- // Load the relevant tile from S3
- LLPointer<LLViewerFetchedTexture> loadObjectsTile(U32 grid_x, U32 grid_y, S32 level);
+
// Clear a level from its "missing" tiles
void cleanMissedTilesFromLevel(S32 level);
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 4819ea4346..6c9c4751d7 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -2436,6 +2436,7 @@ void LLPipeline::doOcclusion(LLCamera& camera)
mCubeVB->setBuffer();
mReflectionMapManager.doOcclusion();
+ mHeroProbeManager.doOcclusion();
gOcclusionCubeProgram.unbind();
gGL.setColorMask(true, true);
@@ -10229,7 +10230,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool
result.clear();
grabReferences(result);
- if (!avatar || !avatar->mDrawable)
+ if (!avatar || avatar->isDead() || !avatar->mDrawable)
{
LL_WARNS_ONCE("AvatarRenderPipeline") << "Avatar is " << (avatar ? "not drawable" : "null") << LL_ENDL;
return;
diff --git a/indra/newview/skins/default/xui/de/menu_viewer.xml b/indra/newview/skins/default/xui/de/menu_viewer.xml
index f6724d4993..8c446c1975 100644
--- a/indra/newview/skins/default/xui/de/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/de/menu_viewer.xml
@@ -42,12 +42,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Voice-Morphing" name="VoiceMorphing">
- <menu_item_check label="Kein Voice-Morphing" name="NoVoiceMorphing"/>
- <menu_item_check label="Vorschau..." name="Preview"/>
- <menu_item_call label="Abonnieren..." name="Subscribe"/>
- <menu_item_call label="Premium-Vorteil..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Gesten..." name="Gestures"/>
<menu_item_check label="Freunde" name="My Friends"/>
<menu_item_check label="Gruppen" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml
new file mode 100644
index 0000000000..b17d0aa5b6
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ can_resize="true"
+ default_tab_group="1"
+ height="530"
+ width="256"
+ min_height="400"
+ min_width="200"
+ layout="topleft"
+ name="gltf asset editor"
+ title="[OBJECT_NAME]">
+ <floater.string name="floater_title" value="GLTF Scene Editor"/>
+ <floater.string name="scene_tittle" value="Scene"/>
+ <floater.string name="node_tittle" value="Node"/>
+ <floater.string name="mesh_tittle" value="Mesh"/>
+ <floater.string name="skin_tittle" value="Skin"/>
+
+ <layout_stack
+ name="main_layout"
+ orientation="vertical"
+ follows="all"
+ bottom="-1"
+ top="16"
+ left="5"
+ right="-1"
+ border_size="0">
+
+ <layout_panel
+ name="top_lp"
+ border="true"
+ bevel_style="in"
+ auto_resize="false"
+ user_resize="true"
+ visible="true"
+ height="200">
+ <panel
+ follows="all"
+ layout="topleft"
+ name="item_list_panel"
+ visible="true"
+ bottom="-1"
+ top="1"
+ left="5"
+ right="-1"/>
+ </layout_panel>
+
+ <layout_panel
+ name="transforms_panel"
+ border="true"
+ bevel_style="in"
+ auto_resize="true"
+ user_resize="true"
+ visible="true"
+ height="150">
+ <menu_button
+ menu_filename="menu_copy_paste_pos.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left="4"
+ top="10"
+ name="clipboard_pos_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ name="label position"
+ tool_tip="Position (meters)"
+ left_pad="8"
+ top_delta="0"
+ width="121">
+ Position (m)
+ </text>
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="512"
+ min_val="-256"
+ name="Pos X"
+ text_enabled_color="1 0 0.3 .7"
+ top_pad="8"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="512"
+ min_val="-256"
+ name="Pos Y"
+ text_enabled_color="EmphasisColor"
+ top_pad="3"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="4096"
+ min_val="-32"
+ name="Pos Z"
+ text_enabled_color="0 0.8 1 .65"
+ top_pad="3"
+ width="87" />
+ <menu_button
+ menu_filename="menu_copy_paste_size.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left_delta="0"
+ top_pad="13"
+ name="clipboard_size_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ left_pad="8"
+ top_delta="0"
+ name="label size"
+ tool_tip="Size (meters)"
+ width="121">
+ Size (m)
+ </text>
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="64"
+ min_val="0.01"
+ name="Scale X"
+ text_enabled_color="1 1 1 1"
+ top_pad="8"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="64"
+ min_val="0.01"
+ name="Scale Y"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <spinner
+ follows="left|top"
+ height="19"
+ increment="0.01"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="64"
+ min_val="0.01"
+ name="Scale Z"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <menu_button
+ menu_filename="menu_copy_paste_rot.xml"
+ follows="top|left"
+ height="11"
+ image_disabled="ClipboardSmallMenu_Disabled"
+ image_selected="ClipboardSmallMenu_Press"
+ image_unselected="ClipboardSmallMenu_Off"
+ layout="topleft"
+ left_delta="0"
+ top_pad="13"
+ name="clipboard_rot_btn"
+ tool_tip="Paste options"
+ width="19"/>
+ <text
+ type="string"
+ length="1"
+ follows="left|top"
+ height="10"
+ layout="topleft"
+ left_pad="8"
+ top_delta="0"
+ name="label rotation"
+ tool_tip="Rotation (degrees)"
+ width="121">
+ Rotation (°)
+ </text>
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="X"
+ label_width="10"
+ layout="topleft"
+ left_delta="-27"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot X"
+ text_enabled_color="1 1 1 1"
+ top_pad="8"
+ width="87" />
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="Y"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot Y"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ <spinner
+ decimal_digits="2"
+ follows="left|top"
+ height="19"
+ increment="1"
+ initial_value="0"
+ label="Z"
+ label_width="10"
+ layout="topleft"
+ left_delta="0"
+ max_val="9999"
+ min_val="-9999"
+ name="Rot Z"
+ text_enabled_color="1 1 1 1"
+ top_pad="3"
+ width="87" />
+ </layout_panel>
+
+ <layout_panel
+ name="blank_panel"
+ border="true"
+ bevel_style="in"
+ auto_resize="true"
+ user_resize="true"
+ visible="false"
+ height="150">
+ </layout_panel>
+ </layout_stack>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/floater_incoming_call.xml b/indra/newview/skins/default/xui/en/floater_incoming_call.xml
index 169d4c9d24..2d8089ee35 100644
--- a/indra/newview/skins/default/xui/en/floater_incoming_call.xml
+++ b/indra/newview/skins/default/xui/en/floater_incoming_call.xml
@@ -12,7 +12,7 @@
width="550">
<floater.string
name="lifetime">
- 5
+ 30
</floater.string>
<floater.string
name="localchat">
diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
index cc4f80a15c..7bc81a1f79 100644
--- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
+++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater
- height="400"
+ height="411"
layout="topleft"
name="prefs_graphics_advanced"
help_topic="Preferences_Graphics_Advanced"
@@ -545,7 +545,7 @@
</text>
<view_border
bevel_style="in"
- height="322"
+ height="357"
layout="topleft"
left="385"
name="vert_border"
@@ -864,21 +864,21 @@
name="HeroProbeUpdateRate"
width="150">
<combo_box.item
- label="Every Frame"
- name="0"
- value="1"/>
+ label="Low"
+ name="6"
+ value="6"/>
+ <combo_box.item
+ label="Medium"
+ name="3"
+ value="3"/>
<combo_box.item
- label="Every 2nd Frame"
+ label="High"
name="1"
value="2"/>
<combo_box.item
- label="Every 3rd Frame"
- name="2"
- value="3"/>
- <combo_box.item
- label="Every 4th Frame"
- name="3"
- value="4"/>
+ label="Ultra"
+ name="0"
+ value="1"/>
</combo_box>
<!-- End of mirror settings -->
@@ -889,7 +889,7 @@
layout="topleft"
left="13"
name="horiz_border"
- top="338"
+ top="373"
top_delta="5"
width="774"/>
<button
@@ -899,7 +899,7 @@
layout="topleft"
left="20"
name="Defaults"
- top_delta="20"
+ top_delta="7"
width="210">
<button.commit_callback
function="Pref.HardwareDefaults" />
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 0e7c522f74..347638249b 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -561,42 +561,6 @@
parameter="conversation" />
</menu_item_check>
<menu_item_separator/>
- <menu
- label="Voice morphing"
- name="VoiceMorphing"
- visibility_control="VoiceMorphingEnabled">
- <menu_item_check
- label="No voice morphing"
- name="NoVoiceMorphing">
- <menu_item_check.on_check
- function="Communicate.VoiceMorphing.NoVoiceMorphing.Check" />
- <menu_item_check.on_click
- function="Communicate.VoiceMorphing.NoVoiceMorphing.Click" />
- </menu_item_check>
- <menu_item_separator/>
- <menu_item_check
- label="Preview..."
- name="Preview">
- <menu_item_check.on_check
- function="Floater.Visible"
- parameter="voice_effect" />
- <menu_item_check.on_click
- function="Floater.Toggle"
- parameter="voice_effect" />
- </menu_item_check>
- <menu_item_call
- label="Subscribe..."
- name="Subscribe">
- <menu_item_call.on_click
- function="Communicate.VoiceMorphing.Subscribe" />
- </menu_item_call>
- <menu_item_call
- label="Premium perk..."
- name="PremiumPerk">
- <menu_item_call.on_click
- function="Communicate.VoiceMorphing.PremiumPerk" />
- </menu_item_call>
- </menu>
<menu_item_check
label="Gestures..."
name="Gestures"
@@ -2866,8 +2830,8 @@ function="World.EnvPreset"
<menu_item_call
label="Open..."
name="Open...">
- <menu_item_call.on_enable
- function="EnableGLTF"/>
+ <!--<menu_item_call.on_enable
+ function="EnableGLTF"/>-->
<menu_item_call.on_click
function="Advanced.ClickGLTFOpen" />
</menu_item_call>
@@ -2887,6 +2851,14 @@ function="World.EnvPreset"
<menu_item_call.on_click
function="Advanced.ClickGLTFUpload" />
</menu_item_call>
+ <menu_item_call
+ label="Edit..."
+ name="Edit...">
+ <menu_item_call.on_enable
+ function="EnableGLTFUpload"/>
+ <menu_item_call.on_click
+ function="Advanced.ClickGLTFEdit" />
+ </menu_item_call>
</menu>
<menu
create_jump_keys="true"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 1584de6880..f86b297395 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -8842,6 +8842,20 @@ New Voice Morphs are available!
</notification>
<notification
+ icon="alertmodal.tga"
+ name="VoiceEffectsNotSupported"
+ sound="UISndAlert"
+ persist="true"
+ type="alertmodal">
+Voice Morphs are not supported by this viewer.
+ <usetemplate
+ notext="Cancel"
+ name="okcancelbuttons"
+ yestext="Disable Voice Morphing"/>
+ <tag>voice</tag>
+ </notification>
+
+ <notification
icon="notifytip.tga"
name="Cannot enter parcel: not a group member"
type="notifytip">
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
index 7eaaaee536..eb38f8bff3 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_sound.xml
@@ -340,7 +340,7 @@
follows="left|top"
top_delta="-6"
layout="topleft"
- left_pad="20"
+ left_pad="10"
width="360"
height="40"
name="media_ear_location">
@@ -422,7 +422,7 @@
control_name="VoiceEarLocation"
follows="left|top"
layout="topleft"
- left_pad="20"
+ left_pad="10"
top_delta="-6"
width="360"
height="40"
@@ -455,11 +455,21 @@
top_pad="10"
width="237"/>
<check_box
+ control_name="VoiceEchoCancellation"
+ height="15"
+ tool_tip="Check to enable voice echo cancellation"
+ label="Echo Cancellation"
+ layout="topleft"
+ left="260"
+ name="enable_echo_cancellation"
+ top_pad="-15"
+ width="200"/>
+ <check_box
follows="top|left"
enabled_control="EnableVoiceChat"
control_name="PushToTalkToggle"
height="15"
- label="Toggle speak on/off when I press button in toolbar"
+ label="Toggle speak on/off with toolbar button"
layout="topleft"
left="20"
name="push_to_talk_toggle_check"
@@ -467,6 +477,16 @@
tool_tip="When in toggle mode, press and release the trigger key ONCE to switch your microphone on or off. When not in toggle mode, the microphone broadcasts your voice only while the trigger is being held down."
top_pad="5"/>
<check_box
+ control_name="VoiceAutomaticGainControl"
+ height="15"
+ tool_tip="Check to enable automatic gain control"
+ label="Automatic Gain Control"
+ layout="topleft"
+ name="voice_automatic_gain_control"
+ left="260"
+ top_pad="-15"
+ width="200"/>
+ <check_box
name="gesture_audio_play_btn"
control_name="EnableGestureSounds"
disabled_control="MuteAudio"
@@ -477,6 +497,44 @@
label="Play sounds from gestures"
top_pad="5"
left="20"/>
+ <text
+ layout="topleft"
+ height="15"
+ left="260"
+ top_pad="-12"
+ width="100"
+ name="noise_suppression_label">
+ Noise Suppression
+ </text>
+ <combo_box
+ control_name="VoiceNoiseSuppressionLevel"
+ layout="topleft"
+ height="23"
+ left_pad="10"
+ top_pad="-18"
+ name="noise_suppression_combo"
+ width="80">
+ <item
+ label="Off"
+ name="noise_suppression_none"
+ value="0"/>
+ <item
+ label="Low"
+ name="noise_suppression_low"
+ value="1"/>
+ <item
+ label="Moderate"
+ name="noise_suppression_moderate"
+ value="2"/>
+ <item
+ label="High"
+ name="noise_suppression_high"
+ value="3"/>
+ <item
+ label="Max"
+ name="noise_suppression_max"
+ value="4"/>
+ </combo_box>
<button
control_name="ShowDeviceSettings"
follows="left|top"
@@ -485,9 +543,9 @@
label="Voice Input/Output devices"
layout="topleft"
left="20"
- top_pad="9"
+ top_pad="0"
name="device_settings_btn"
- width="230">
+ width="200">
</button>
<panel
layout="topleft"
diff --git a/indra/newview/skins/default/xui/es/menu_viewer.xml b/indra/newview/skins/default/xui/es/menu_viewer.xml
index c22311f882..04e43f01da 100644
--- a/indra/newview/skins/default/xui/es/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/es/menu_viewer.xml
@@ -42,12 +42,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Transformación de voz" name="VoiceMorphing">
- <menu_item_check label="Sin transformación de voz" name="NoVoiceMorphing"/>
- <menu_item_check label="Probar..." name="Preview"/>
- <menu_item_call label="Suscribir..." name="Subscribe"/>
- <menu_item_call label="Ventaja Premium..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Gestos..." name="Gestures"/>
<menu_item_check label="Amigos" name="My Friends"/>
<menu_item_check label="Grupos" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/it/menu_viewer.xml b/indra/newview/skins/default/xui/it/menu_viewer.xml
index 043fd28ddb..85999ccbe0 100644
--- a/indra/newview/skins/default/xui/it/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/it/menu_viewer.xml
@@ -42,12 +42,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Manipolazione voce" name="VoiceMorphing">
- <menu_item_check label="Nessuna manipolazione voce" name="NoVoiceMorphing"/>
- <menu_item_check label="Anteprima..." name="Preview"/>
- <menu_item_call label="Abbonati..." name="Subscribe"/>
- <menu_item_call label="Vantaggio Premium..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Gesture..." name="Gestures"/>
<menu_item_check label="Amici" name="My Friends"/>
<menu_item_check label="Gruppi" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/ja/menu_viewer.xml b/indra/newview/skins/default/xui/ja/menu_viewer.xml
index a1f3980df4..f6b10bb121 100644
--- a/indra/newview/skins/default/xui/ja/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/ja/menu_viewer.xml
@@ -74,7 +74,6 @@
<menu_item_check label="ボイスチャット" name="Speak"/>
<menu_item_check name="Conversation Log..." label="会話ログ…"/>
<menu_item_separator/>
- <menu label="ボイスモーフィング" name="VoiceMorphing"/>
<menu_item_check label="ジェスチャー…" name="Gestures"/>
<menu_item_separator/>
<menu_item_check label="フレンド" name="My Friends"/>
diff --git a/indra/newview/skins/default/xui/pl/menu_viewer.xml b/indra/newview/skins/default/xui/pl/menu_viewer.xml
index 4d03e7c780..ee162addd2 100644
--- a/indra/newview/skins/default/xui/pl/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/pl/menu_viewer.xml
@@ -35,11 +35,6 @@
<menu_item_check label="Czat lokalny..." name="Nearby Chat" />
<menu_item_check label="Mowa" name="Speak" />
<menu_item_check name="Conversation Log..." label="Dziennik rozmów..." />
- <menu label="Przekształcanie głosu" name="VoiceMorphing">
- <menu_item_check label="Bez przekształcania" name="NoVoiceMorphing" />
- <menu_item_check label="Podgląd..." name="Preview" />
- <menu_item_call label="Subskrybuj..." name="Subscribe" />
- </menu>
<menu_item_check label="Gesty..." name="Gestures" />
<menu_item_check label="Znajomi" name="My Friends" />
<menu_item_check label="Grupy" name="My Groups" />
diff --git a/indra/newview/skins/default/xui/pt/menu_viewer.xml b/indra/newview/skins/default/xui/pt/menu_viewer.xml
index b70adc3ad2..0f4873d11c 100644
--- a/indra/newview/skins/default/xui/pt/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/pt/menu_viewer.xml
@@ -42,12 +42,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Distorção de voz" name="VoiceMorphing">
- <menu_item_check label="Não distorcer voz" name="NoVoiceMorphing"/>
- <menu_item_check label="Visualizar..." name="Preview"/>
- <menu_item_call label="Assinar..." name="Subscribe"/>
- <menu_item_call label="Benefício Premium..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Gestos..." name="Gestures"/>
<menu_item_check label="Amigos" name="My Friends"/>
<menu_item_check label="Grupos" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/ru/menu_viewer.xml b/indra/newview/skins/default/xui/ru/menu_viewer.xml
index 8361464f4c..4a6390329d 100644
--- a/indra/newview/skins/default/xui/ru/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/ru/menu_viewer.xml
@@ -40,12 +40,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Изменение голоса" name="VoiceMorphing">
- <menu_item_check label="Без изменения голоса" name="NoVoiceMorphing"/>
- <menu_item_check label="Просмотр..." name="Preview"/>
- <menu_item_call label="Подписаться..." name="Subscribe"/>
- <menu_item_call label="Премиум-бонус..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Жесты..." name="Gestures"/>
<menu_item_check label="Друзья" name="My Friends"/>
<menu_item_check label="Группы" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/tr/menu_viewer.xml b/indra/newview/skins/default/xui/tr/menu_viewer.xml
index 1c977ba5ce..fb6111248c 100644
--- a/indra/newview/skins/default/xui/tr/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/tr/menu_viewer.xml
@@ -40,12 +40,6 @@
<menu_item_call label="Facebook..." name="Facebook"/>
<menu_item_call label="Twitter..." name="Twitter"/>
<menu_item_call label="Flickr..." name="Flickr"/>
- <menu label="Ses şekillendirme" name="VoiceMorphing">
- <menu_item_check label="Ses şekillendirme yok" name="NoVoiceMorphing"/>
- <menu_item_check label="Önizleme..." name="Preview"/>
- <menu_item_call label="Abone ol..." name="Subscribe"/>
- <menu_item_call label="Özel üye avantajı..." name="PremiumPerk"/>
- </menu>
<menu_item_check label="Mimikler..." name="Gestures"/>
<menu_item_check label="Arkadaşlar" name="My Friends"/>
<menu_item_check label="Gruplar" name="My Groups"/>
diff --git a/indra/newview/skins/default/xui/zh/menu_viewer.xml b/indra/newview/skins/default/xui/zh/menu_viewer.xml
index 972434dfc5..a048af7b68 100644
--- a/indra/newview/skins/default/xui/zh/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/zh/menu_viewer.xml
@@ -40,12 +40,6 @@
<menu_item_call label="臉書…" name="Facebook"/>
<menu_item_call label="推特…" name="Twitter"/>
<menu_item_call label="Flickr…" name="Flickr"/>
- <menu label="語音變聲" name="VoiceMorphing">
- <menu_item_check label="沒有變聲效果" name="NoVoiceMorphing"/>
- <menu_item_check label="預覽……" name="Preview"/>
- <menu_item_call label="訂閱……" name="Subscribe"/>
- <menu_item_call label="付費會員獨享…" name="PremiumPerk"/>
- </menu>
<menu_item_check label="姿勢…" name="Gestures"/>
<menu_item_check label="朋友" name="My Friends"/>
<menu_item_check label="群組" name="My Groups"/>
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 6e88b2f674..470c8bc8a8 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -547,6 +547,11 @@ class Windows_x86_64_Manifest(ViewerManifest):
# Get shared libs from the shared libs staging directory
with self.prefix(src=os.path.join(self.args['build'], os.pardir,
'sharedlibs', self.args['buildtype'])):
+ # WebRTC libraries
+ for libfile in (
+ 'llwebrtc.dll',
+ ):
+ self.path(libfile)
if self.args['openal'] == 'ON':
# Get openal dll
@@ -984,6 +989,20 @@ class Darwin_x86_64_Manifest(ViewerManifest):
print("Skipping %s" % dst)
return added
+ # WebRTC libraries
+ with self.prefix(src=os.path.join(self.args['build'], os.pardir,
+ 'sharedlibs', self.args['buildtype'], 'Resources')):
+ for libfile in (
+ 'libllwebrtc.dylib',
+ ):
+ self.path(libfile)
+
+ oldpath = os.path.join("@rpath", libfile)
+ self.run_command(
+ ['install_name_tool', '-change', oldpath,
+ '@executable_path/../Resources/%s' % libfile,
+ executable])
+
# dylibs is a list of all the .dylib files we expect to need
# in our bundled sub-apps. For each of these we'll create a
# symlink from sub-app/Contents/Resources to the real .dylib.