diff --git a/CLAUDE.md b/CLAUDE.md
index 65fad33b11..d0320559cc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
-Alchemy Viewer is a third-party client for Second Life, forked from the official Linden Lab viewer. It is a large C++ desktop application (~750 source files in the main viewer module alone) using OpenGL for rendering, with builds targeting Windows, macOS, and Linux.
+Vayu Viewer is a third-party client for Second Life, forked from Alchemy Viewer, which is itself forked from the official Linden Lab viewer. It is a large C++ desktop application (~750 source files in the main viewer module alone) using OpenGL for rendering, with builds targeting Windows, macOS, and Linux.
## Build System
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 80a9ea89c0..a8033eacd6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
# Contributor guidelines
-Thanks for your interest in contributing to Alchemy Viewer! This document
+Thanks for your interest in contributing to Vayu Viewer! This document
summarizes some of the most important points for people looking to contribute
to the project especially those looking to provide bug reports and code
changes.
@@ -14,64 +14,57 @@ changes.
- [Importance of Your Feedback](#importance-of-your-feedback)
- [Our Relationship with Linden Lab and Code Inheritance](#our-relationship-with-linden-lab-and-code-inheritance)
- [Reporting Bugs to Linden Lab](#reporting-bugs-to-linden-lab)
- - [Submitting Feedback to Alchemy Viewer](#submitting-feedback-to-alchemy-viewer)
+ - [Submitting Feedback to Vayu Viewer](#submitting-feedback-to-vayu-viewer)
- [Contributing pull requests](#contributing-pull-requests)
## Communication
-Alchemy Viewer has multiple channels for communication. Some of these channels are
-more end-user focused while others are more tailored for developer-to-developer or support.
+Vayu Viewer is a young fork and does not yet have its own community chat.
-- [Our Discord][discord] is the primary community engagement platform for the project.
- This is where we announce our releases, answer questions from the community,
- and provide support to users. This is also a great place for developers to interact
- with the Alchemy users to determine if their feature is interesting to them.
- [Github issues][] provide a means for developers and contributors to organize
- their work and collaborate with other developers. By default most user-facing
- discussions should happen on [the Discord][discord] so that they are
- visible to more people, and can build consensus.
+ their work and collaborate with other developers.
## Reporting Bugs and Requesting Features
### Importance of Your Feedback
-We value your feedback and invite you to help us identify bugs and suggest new features for Alchemy Viewer.
+We value your feedback and invite you to help us identify bugs and suggest new features for Vayu Viewer.
Your input is crucial in shaping the future of our project.
-Your cooperation with these guidelines will help us improve Alchemy Viewer,
-and ensures that Linden Lab can address broader issues within their codebase.
+Your cooperation with these guidelines will help us improve Vayu Viewer,
+and ensures that Linden Lab and the Alchemy Viewer project can address broader issues within their codebases.
### Our Relationship with Linden Lab and Code Inheritance
It's important to note that while we are not affiliated with or endorsed by Linden Lab,
-the creators of Second Life, our viewer incorporates a significant amount of code from them.
+the creators of Second Life, or by the Alchemy Viewer project from which we are forked,
+our viewer incorporates a significant amount of code from both of them.
-This shared codebase means some issues may originate from the Linden Lab viewer,
-and addressing these effectively benefits both viewers.
+This shared codebase means some issues may originate upstream, in either the Linden Lab
+viewer or Alchemy Viewer, and addressing these effectively benefits all three viewers.
### Reporting Bugs to Linden Lab
If you encounter defects or unwanted behavior that are inherited from the official Second Life viewer,
please report these issues directly to Linden Lab.
-Before submitting a bug report to Alchemy Viewer, kindly check the [official Second Life viewer][lindenviewer]
+Before submitting a bug report to Vayu Viewer, kindly check the [official Second Life viewer][lindenviewer]
to determine if the defect exists there as well.
Reporting it to Linden Lab, if it hasn't already been reported, will ensure they are aware of the issue.
-If you are unsure, you are encouraged to share your findings with an Alchemy team member to receive guidance.
Linden Lab's page for submitting bug reports can be found on [feedback.secondlife.com/bug-reports][lindenbugs].
-### Submitting Feedback to Alchemy Viewer
+### Submitting Feedback to Vayu Viewer
-If the defect is unique to Alchemy Viewer or pertains to features exclusive to our viewer, please proceed to submit your bug report to us on Github. Provide as much detail as possible, including steps to reproduce the issue, logs, and screenshots if available. This information will help us diagnose and address the problem more efficiently.
+If the defect is unique to Vayu Viewer or pertains to features exclusive to our viewer, please proceed to submit your bug report to us on Github. Provide as much detail as possible, including steps to reproduce the issue, logs, and screenshots if available. This information will help us diagnose and address the problem more efficiently.
## Contributing pull requests
If you wish to contribute a new pull request, please ensure that:
- You talk to other developers about how best to implement the work.
-- The functionality is desired. Be sure to talk to users and members of the Alchemy
- team to ensure the work is a good idea and will be accepted.
+- The functionality is desired. Be sure to talk to users and maintainers of Vayu
+ Viewer to ensure the work is a good idea and will be accepted.
- The work is high quality and the PR follows [PR etiquette][]
- You have tested the work locally
@@ -79,7 +72,6 @@ The [Git Style Guide](https://github.com/agis/git-style-guide) is also a good
reference for best git practices.
[PR etiquette]: https://gist.github.com/mikepea/863f63d6e37281e329f8
-[Github issues]: https://github.com/AlchemyViewer/Alchemy/issues
-[discord]: https://discordapp.com/invite/KugCgs6
+[Github issues]: https://github.com/Shadowolf7/Vayu-Viewer/issues
[lindenviewer]: https://releasenotes.secondlife.com/viewer.html
[lindenbugs]: https://feedback.secondlife.com/bug-reports
diff --git a/README.md b/README.md
index 6905874f03..6df6974f3f 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,26 @@
-
-
+
+
-# Alchemy Viewer
+# Vayu Viewer
[](LICENSE)
-[](https://github.com/AlchemyViewer/Alchemy/releases)
-[][discord]
+[](https://github.com/Shadowolf7/Vayu-Viewer/releases)
-[Alchemy Viewer](https://www.alchemyviewer.org) is a third-party client for [Second Life](https://secondlife.com), forked from the official [Linden Lab viewer](https://github.com/secondlife/viewer). We focus on a cohesive, modern experience built on thoughtful defaults — while staying on the bleeding edge of new platform features.
+Vayu Viewer is a third-party client for [Second Life](https://secondlife.com), forked from [Alchemy Viewer](https://www.alchemyviewer.org), which is itself forked from the official [Linden Lab viewer](https://github.com/secondlife/viewer).
## 📥 Download
-Most users install a [pre-built release][releasesgh] for Windows, macOS, or Linux. Release candidates and project viewers are announced on our [Discord server][discord].
+Pre-built releases for Windows, macOS, or Linux are published on the [releases page][releasesgh] once available.
## 🔨 Building from source
-Alchemy uses CMake with vcpkg for dependency management. Platform setup, presets, build options, tests, packaging, and troubleshooting all live in [**doc/BUILD.md**](doc/BUILD.md).
+Vayu Viewer uses CMake with vcpkg for dependency management. Platform setup, presets, build options, tests, packaging, and troubleshooting all live in [**doc/BUILD.md**](doc/BUILD.md).
```
-git clone --recurse-submodules https://github.com/AlchemyViewer/Alchemy.git alchemy
-cd alchemy
+git clone --recurse-submodules https://github.com/Shadowolf7/Vayu-Viewer.git vayu-viewer
+cd vayu-viewer
python3 -m venv .venv && source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
dotnet tool restore # packaging only
@@ -35,21 +34,19 @@ File bug reports, suggest enhancements, or open a pull request — see [CONTRIBU
## 🙏 Acknowledgements
-Alchemy stands on the work of:
+Vayu Viewer stands on the work of:
+- [Alchemy Viewer](https://www.alchemyviewer.org), the project this is forked from
- [Linden Lab](https://www.lindenlab.com/) and the [Second Life Viewer](https://github.com/secondlife/viewer) contributors
- The many open-source libraries that power the viewer (see [`indra/vcpkg.json`](indra/vcpkg.json))
## 🔗 Resources
-- [Website](https://www.alchemyviewer.org)
- [Downloads][releasesgh]
-- [Issue tracker](https://github.com/AlchemyViewer/Alchemy/issues)
-- [Discord][discord]
+- [Issue tracker](https://github.com/Shadowolf7/Vayu-Viewer/issues)
## 📜 License
-Alchemy is licensed under the [GNU Lesser General Public License v2.1](LICENSE), inherited from the upstream Linden Lab viewer.
+Vayu Viewer is licensed under the [GNU Lesser General Public License v2.1](LICENSE), inherited from the upstream Alchemy Viewer and Linden Lab viewer.
-[releasesgh]: https://github.com/AlchemyViewer/Alchemy/releases
-[discord]: https://discordapp.com/invite/KugCgs6
+[releasesgh]: https://github.com/Shadowolf7/Vayu-Viewer/releases
diff --git a/doc/BUILD.md b/doc/BUILD.md
index 02630c4328..ea6110f4c3 100644
--- a/doc/BUILD.md
+++ b/doc/BUILD.md
@@ -1,6 +1,6 @@
-# Building Alchemy Viewer
+# Building Vayu Viewer
-Everything you need to build Alchemy from source: platform setup, presets, options, tests, packaging, and troubleshooting.
+Everything you need to build Vayu Viewer from source: platform setup, presets, options, tests, packaging, and troubleshooting.
- [Prerequisites](#prerequisites)
- [Platform setup](#platform-setup) — Windows, macOS, Linux
@@ -164,11 +164,11 @@ rustup default stable
## Clone and bootstrap
-Alchemy vendors the [Dullahan](https://github.com/AlchemyViewer/dullahan) CEF wrapper — used by the in-world web media plugin — as a git submodule under `indra/dullahan`. It builds from source as part of the tree, so the submodule must be present before you configure. Clone with `--recurse-submodules`, then set up the Python venv and .NET tools:
+Vayu Viewer vendors the [Dullahan](https://github.com/AlchemyViewer/dullahan) CEF wrapper — used by the in-world web media plugin — as a git submodule under `indra/dullahan`. It builds from source as part of the tree, so the submodule must be present before you configure. Clone with `--recurse-submodules`, then set up the Python venv and .NET tools:
```
-git clone --recurse-submodules https://github.com/AlchemyViewer/Alchemy.git alchemy
-cd alchemy
+git clone --recurse-submodules https://github.com/Shadowolf7/Vayu-Viewer.git vayu-viewer
+cd vayu-viewer
python3 -m venv .venv
# Windows: .\.venv\Scripts\Activate.ps1
# Unix: source .venv/bin/activate
@@ -257,10 +257,10 @@ cmake --build --preset ninja-os-release
```
# Visual Studio
-start .\build-Windows-vs2026-os\Alchemy.slnx
+start .\build-Windows-vs2026-os\Vayu.slnx
# Xcode
-open ./build-Darwin-xcode-os-arm64/Alchemy.xcodeproj
+open ./build-Darwin-xcode-os-arm64/Vayu.xcodeproj
```
> `.slnx` is the newer Visual Studio solution format. Requires VS 2022 17.10+ or VS 2026.
@@ -275,7 +275,7 @@ The viewer executable lands under `build--/newview//`:
| macOS | `build-Darwin-/newview//.app` |
| Linux | `build-Linux-/newview//` |
-`` follows `VIEWER_CHANNEL` (default `Alchemy Test` → `AlchemyTest.exe` / `AlchemyTest.app`).
+`` follows `VIEWER_CHANNEL` (default `Vayu Test` → `VayuTest.exe` / `VayuTest.app`).
## Configuration types
@@ -413,7 +413,7 @@ If the run produces no output for a very long time it usually isn't hung — che
### CMake is too old
-Alchemy requires CMake 3.27+. If your distro ships something older, install a newer version via pip inside your venv:
+Vayu Viewer requires CMake 3.27+. If your distro ships something older, install a newer version via pip inside your venv:
```
pip install --upgrade cmake ninja
@@ -454,7 +454,7 @@ cmake -S indra --preset ninja-os -DGCC_DISABLE_FATAL_WARNINGS=TRUE
cmake -S indra --preset ninja-os -DCLANG_DISABLE_FATAL_WARNINGS=TRUE
```
-### Visual Studio doesn't recognize `Alchemy.slnx`
+### Visual Studio doesn't recognize `Vayu.slnx`
`.slnx` is the newer Visual Studio solution format. Use Visual Studio 2022 17.10+ or Visual Studio 2026, or configure with the `vs2022-os` preset on an older compatible edition.
@@ -472,8 +472,7 @@ Double-check the package list for your distro under [Platform setup → Linux](#
### Still stuck?
-- Ask on the [Discord](https://discordapp.com/invite/KugCgs6).
-- File a build bug at .
+- File a build bug at .
## See also
diff --git a/doc/vayu_logo.png b/doc/vayu_logo.png
new file mode 100644
index 0000000000..fe88e541d5
Binary files /dev/null and b/doc/vayu_logo.png differ
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 08f28b1131..1ca39d5dca 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -8,11 +8,56 @@ cmake_minimum_required(VERSION 3.27...4.2 FATAL_ERROR)
include(CMakeDependentOption)
-set(ROOT_PROJECT_NAME "Alchemy" CACHE STRING
- "The root project/makefile/solution name. Defaults to Alchemy.")
+# Git worktrees do not inherit submodule checkouts from the main clone, so a
+# freshly-added worktree has indra/dullahan and vcpkg both empty. Left alone
+# that produces two unhelpful failures: a bare "does not contain a
+# CMakeLists.txt" from add_subdirectory(dullahan) ~1100 lines into the log
+# (after a full vcpkg install), or a missing-toolchain-file error from the
+# preset's CMAKE_TOOLCHAIN_FILE. Neither names the actual cause. Check up
+# front, before project() loads the toolchain, and fix it instead.
+#
+# This must stay ahead of the first project() call. On a normal checkout it is
+# two EXISTS tests and nothing else.
+set(_required_submodules
+ "${CMAKE_CURRENT_SOURCE_DIR}/dullahan/CMakeLists.txt"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../vcpkg/scripts/buildsystems/vcpkg.cmake"
+)
+set(_missing_submodules FALSE)
+foreach(_marker IN LISTS _required_submodules)
+ if(NOT EXISTS "${_marker}")
+ set(_missing_submodules TRUE)
+ endif()
+endforeach()
+
+if(_missing_submodules)
+ set(_submodule_hint "Run 'git submodule update --init --recursive' from the repository root, then re-run cmake.")
+ find_package(Git QUIET)
+ if(NOT Git_FOUND)
+ message(FATAL_ERROR
+ "Required git submodules are not checked out, and git was not found to "
+ "check them out automatically.\n${_submodule_hint}")
+ endif()
+ message(STATUS "Required git submodules are not checked out - initialising them (this may take a while)")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" submodule update --init --recursive
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
+ RESULT_VARIABLE _submodule_result
+ )
+ foreach(_marker IN LISTS _required_submodules)
+ if(NOT EXISTS "${_marker}")
+ message(FATAL_ERROR
+ "Failed to check out the required git submodules automatically "
+ "(git exited ${_submodule_result}; '${_marker}' is still missing)."
+ "\n${_submodule_hint}")
+ endif()
+ endforeach()
+endif()
+
+set(ROOT_PROJECT_NAME "Vayu" CACHE STRING
+ "The root project/makefile/solution name. Defaults to Vayu.")
# Set our base channel
-set(VIEWER_CHANNEL "Alchemy Test" CACHE STRING "Viewer Channel Name")
+set(VIEWER_CHANNEL "Vayu Test" CACHE STRING "Viewer Channel Name")
# Default deploy grid
set(GRID agni CACHE STRING "Target Grid")
@@ -42,6 +87,7 @@ option(USE_LTO "Enable Link Time Optimization" OFF)
option(USE_NVAPI "Use NVidia NVAPI library for gpu profile support (Windows Only)" ON)
option(USE_OPENXR "Enable building with OpenXR support (Experimental)" OFF)
option(USE_PRECOMPILED_HEADERS "Enable use of precompiled header directives where supported." ON)
+option(USE_CCACHE "Use ccache to speed up recompilation if it is available" ON)
# Audio Engines
option(USE_FMODSTUDIO "Enable support for the FMOD Studio audio engine" OFF)
@@ -104,7 +150,7 @@ option(USE_SENTRY "Use the Sentry crash reporting system" OFF)
# Default Tracy profiling on for test builds, but off for all others
set(BUILDING_TEST_CHANNEL OFF)
string(TOLOWER "${VIEWER_CHANNEL}" channel_lower)
-if(channel_lower MATCHES "^alchemy test")
+if(channel_lower MATCHES "^vayu test")
set(BUILDING_TEST_CHANNEL ON)
endif()
cmake_dependent_option(USE_TRACY "Enable support for Tracy profiler" ON "BUILDING_TEST_CHANNEL" OFF)
@@ -207,6 +253,23 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_BINAR
# Setup VCPKG toolchains and targets
include(BootstrapVcpkg)
+# Wire up ccache (if present) before project()/enable_language(), since that's
+# when CMake does compiler detection and starts honoring
+# CMAKE__COMPILER_LAUNCHER. Silently a no-op if ccache isn't installed,
+# so this is safe to leave on by default for everyone.
+if(USE_CCACHE)
+ find_program(CCACHE_PROGRAM ccache)
+ if(CCACHE_PROGRAM)
+ message(STATUS "Using ccache: ${CCACHE_PROGRAM}")
+ set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ if(DARWIN)
+ set(CMAKE_OBJC_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ set(CMAKE_OBJCXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ endif()
+ endif()
+endif()
+
# Generate base project after setting pre-generation variables
project(${ROOT_PROJECT_NAME})
diff --git a/indra/cmake/FMODSTUDIO.cmake b/indra/cmake/FMODSTUDIO.cmake
index fae726c2f9..5bd8f78248 100644
--- a/indra/cmake/FMODSTUDIO.cmake
+++ b/indra/cmake/FMODSTUDIO.cmake
@@ -25,7 +25,9 @@ if (USE_FMODSTUDIO)
if(WINDOWS)
set(fmod_lib_paths "${FMODSTUDIO_SDK_DIR}/api/core/lib/x64")
elseif(LINUX)
- set(fmod_lib_paths "${FMODSTUDIO_SDK_DIR}/api/core/lib/x64")
+ set(fmod_lib_paths
+ "${FMODSTUDIO_SDK_DIR}/api/core/lib/x86_64"
+ "${FMODSTUDIO_SDK_DIR}/api/core/lib/x64")
endif()
set(fmod_inc_paths "${FMODSTUDIO_SDK_DIR}/api/core/inc")
diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
index b651fd2f2a..5c83922261 100644
--- a/indra/lib/python/indra/util/llmanifest.py
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -91,7 +91,7 @@ def get_default_platform(dummy):
}[sys.platform]
DEFAULT_SRCTREE = os.path.dirname(sys.argv[0])
-CHANNEL_VENDOR_BASE = 'Alchemy'
+CHANNEL_VENDOR_BASE = 'Vayu'
RELEASE_CHANNEL = CHANNEL_VENDOR_BASE + ' Release'
BASE_ARGUMENTS=[
@@ -117,7 +117,7 @@ def get_default_platform(dummy):
dict(name='buildtype', description='Build type (i.e. Debug, Release, RelWithDebInfo).', default=None),
dict(name='bundleid',
description="""The macOS Bundle identifier.""",
- default="org.alchemyviewer.viewer"),
+ default="org.vayuviewer.viewer"),
dict(name='channel',
description="""The channel to use for updates, packaging, settings name, etc.""",
default='CHANNEL UNSET'),
diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp
index bc9109c97b..98bbd5bcf1 100644
--- a/indra/llaudio/llaudioengine_fmodstudio.cpp
+++ b/indra/llaudio/llaudioengine_fmodstudio.cpp
@@ -53,6 +53,17 @@ float LLAudioEngine_FMODSTUDIO::sReverbSendScale = 0.0f;
namespace
{
+ // Reinterpret-casting an LLVector3's float array to FMOD_VECTOR* (as
+ // this file used to do at its set3DAttributes call site) is
+ // type-punning that violates strict aliasing -- GCC flags it as an
+ // error under -Werror=strict-aliasing. FMOD_VECTOR is layout-compatible
+ // (three floats), so building one by value sidesteps the violation.
+ // Mirrors lllistener_fmodstudio.cpp's identical helper.
+ FMOD_VECTOR to_fmod_vector(const LLVector3& v)
+ {
+ return FMOD_VECTOR{ v.mV[0], v.mV[1], v.mV[2] };
+ }
+
// Serialise an FMOD_GUID into the canonical Windows-style
// "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form. Used as the stable
// device id in the picker UI / settings; round-trips via simple
@@ -802,7 +813,9 @@ void LLAudioChannelFMODSTUDIO::update3DPosition()
LLVector3 float_pos;
float_pos.setVec(mCurrentSourcep->getPositionGlobal());
- FMOD_RESULT result = mChannelp->set3DAttributes((FMOD_VECTOR*)float_pos.mV, (FMOD_VECTOR*)mCurrentSourcep->getVelocity().mV);
+ FMOD_VECTOR pos = to_fmod_vector(float_pos);
+ FMOD_VECTOR vel = to_fmod_vector(mCurrentSourcep->getVelocity());
+ FMOD_RESULT result = mChannelp->set3DAttributes(&pos, &vel);
Check_FMOD_Error(result, "FMOD::Channel::set3DAttributes");
}
}
@@ -1026,10 +1039,16 @@ FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, floa
// outbuffer = the buffer passed from the previous DSP unit.
// length = length in samples at this mix time.
- LLWindGen *windgen = nullptr;
FMOD::DSP *thisdsp = (FMOD::DSP *)dsp_state->instance;
- thisdsp->getUserData((void **)&windgen);
+ // Receive as void* and convert the pointer *value* afterward, rather
+ // than handing FMOD a (void**) alias of an LLWindGen<...>* lvalue --
+ // the latter is type-punning that GCC flags under
+ // -Werror=strict-aliasing.
+ void *userdata = nullptr;
+ thisdsp->getUserData(&userdata);
+ LLWindGen *windgen =
+ static_cast*>(userdata);
if (windgen)
{
diff --git a/indra/llaudio/lllistener_fmodstudio.cpp b/indra/llaudio/lllistener_fmodstudio.cpp
index 9f77a3e42e..00a13af90b 100644
--- a/indra/llaudio/lllistener_fmodstudio.cpp
+++ b/indra/llaudio/lllistener_fmodstudio.cpp
@@ -30,6 +30,21 @@
#include "lllistener_fmodstudio.h"
#include
+namespace
+{
+ // Reinterpret-casting an LLVector3's float array to FMOD_VECTOR* and
+ // dereferencing it (as this file used to do at every call site below)
+ // is type-punning that violates strict aliasing -- GCC flags it as an
+ // error under -Werror=strict-aliasing. FMOD_VECTOR is layout-compatible
+ // (three floats), so building one by value sidesteps the aliasing
+ // violation entirely instead of relying on the cast being "safe enough
+ // in practice".
+ FMOD_VECTOR to_fmod_vector(const LLVector3& v)
+ {
+ return FMOD_VECTOR{ v.mV[0], v.mV[1], v.mV[2] };
+ }
+}
+
//-----------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------
@@ -58,7 +73,10 @@ void LLListener_FMODSTUDIO::translate(LLVector3 offset)
{
LLListener::translate(offset);
- mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, nullptr, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
+ FMOD_VECTOR pos = to_fmod_vector(mPosition);
+ FMOD_VECTOR at = to_fmod_vector(mListenAt);
+ FMOD_VECTOR up = to_fmod_vector(mListenUp);
+ mSystem->set3DListenerAttributes(0, &pos, nullptr, &at, &up);
}
//-----------------------------------------------------------------------
@@ -66,7 +84,10 @@ void LLListener_FMODSTUDIO::setPosition(LLVector3 pos)
{
LLListener::setPosition(pos);
- mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, nullptr, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
+ FMOD_VECTOR fmod_pos = to_fmod_vector(mPosition);
+ FMOD_VECTOR at = to_fmod_vector(mListenAt);
+ FMOD_VECTOR up = to_fmod_vector(mListenUp);
+ mSystem->set3DListenerAttributes(0, &fmod_pos, nullptr, &at, &up);
}
//-----------------------------------------------------------------------
@@ -74,7 +95,10 @@ void LLListener_FMODSTUDIO::setVelocity(LLVector3 vel)
{
LLListener::setVelocity(vel);
- mSystem->set3DListenerAttributes(0, nullptr, (FMOD_VECTOR*)mVelocity.mV, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
+ FMOD_VECTOR fmod_vel = to_fmod_vector(mVelocity);
+ FMOD_VECTOR at = to_fmod_vector(mListenAt);
+ FMOD_VECTOR up = to_fmod_vector(mListenUp);
+ mSystem->set3DListenerAttributes(0, nullptr, &fmod_vel, &at, &up);
}
//-----------------------------------------------------------------------
@@ -85,7 +109,9 @@ void LLListener_FMODSTUDIO::orient(LLVector3 up, LLVector3 at)
// at = -at; by default Fmod studio is 'left-handed' but we are providing
// flag FMOD_INIT_3D_RIGHTHANDED so no correction are needed
- mSystem->set3DListenerAttributes(0, nullptr, nullptr, (FMOD_VECTOR*)at.mV, (FMOD_VECTOR*)up.mV);
+ FMOD_VECTOR fmod_at = to_fmod_vector(at);
+ FMOD_VECTOR fmod_up = to_fmod_vector(up);
+ mSystem->set3DListenerAttributes(0, nullptr, nullptr, &fmod_at, &fmod_up);
}
//-----------------------------------------------------------------------
@@ -109,8 +135,10 @@ void LLListener_FMODSTUDIO::setRolloffFactor(F32 factor)
if (mRolloffFactor != factor)
{
LLVector3 pos = mPosition - LLVector3(0.f, 0.f, .1f);
- mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)pos.mV, nullptr, nullptr, nullptr);
- mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, nullptr, nullptr, nullptr);
+ FMOD_VECTOR fmod_pos = to_fmod_vector(pos);
+ FMOD_VECTOR fmod_mPosition = to_fmod_vector(mPosition);
+ mSystem->set3DListenerAttributes(0, &fmod_pos, nullptr, nullptr, nullptr);
+ mSystem->set3DListenerAttributes(0, &fmod_mPosition, nullptr, nullptr, nullptr);
}
mRolloffFactor = factor;
mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor);
diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h
index 2a191fa550..3761457f8b 100644
--- a/indra/llcorehttp/_httpinternal.h
+++ b/indra/llcorehttp/_httpinternal.h
@@ -157,6 +157,9 @@ constexpr long HTTP_PIPELINING_MAX = 20L;
constexpr bool HTTP_USE_RETRY_AFTER_DEFAULT = true;
constexpr long HTTP_THROTTLE_RATE_DEFAULT = 0L;
+// Content-Encoding auto-decode: on by default, matching historical behavior
+constexpr long HTTP_CONTENT_DECODING_DEFAULT = 1L;
+
// Tuning parameters
// Time worker thread sleeps after a pass through the
diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp
index b3f6cd27be..52d445c87f 100644
--- a/indra/llcorehttp/_httpoprequest.cpp
+++ b/indra/llcorehttp/_httpoprequest.cpp
@@ -278,9 +278,12 @@ void HttpOpRequest::visitNotifier(HttpRequest * request)
HttpResponse::TransferStats::ptr_t stats = std::make_shared();
- curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_DOWNLOAD, &stats->mSizeDownload);
+ curl_off_t size_download = 0, speed_download = 0;
+ curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_DOWNLOAD_T, &size_download);
curl_easy_getinfo(mCurlHandle, CURLINFO_TOTAL_TIME, &stats->mTotalTime);
- curl_easy_getinfo(mCurlHandle, CURLINFO_SPEED_DOWNLOAD, &stats->mSpeedDownload);
+ curl_easy_getinfo(mCurlHandle, CURLINFO_SPEED_DOWNLOAD_T, &speed_download);
+ stats->mSizeDownload = static_cast(size_download);
+ stats->mSpeedDownload = static_cast(speed_download);
response->setTransferStats(stats);
@@ -519,7 +522,24 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
check_curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
check_curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
check_curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, getHandle());
- check_curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
+ // Auto-negotiate and auto-decode Content-Encoding, unless this class
+ // disables it (PO_CONTENT_DECODING=0): some asset servers send a
+ // non-encoding value here (e.g. a MIME type where a transfer coding
+ // was meant), which libcurl treats as a hard failure
+ // (CURLE_BAD_CONTENT_ENCODING) once content decoding is enabled.
+ // CURLOPT_HTTP_CONTENT_DECODING is the actual gate on whether libcurl
+ // inspects/validates the response's Content-Encoding at all -- it
+ // defaults to enabled independently of CURLOPT_ENCODING, so clearing
+ // just CURLOPT_ENCODING (as a prior version of this fix did) left
+ // libcurl still validating the header and still failing. Both are
+ // set explicitly (not just left unset) on every request, not only
+ // when disabling: easy handles are recycled from a pool
+ // (HandleCache::getHandle()) with no reset, so a handle last used by
+ // a decoding-enabled class would otherwise carry that setting forward.
+ check_curl_easy_setopt(mCurlHandle, CURLOPT_HTTP_CONTENT_DECODING,
+ cpolicy.mContentDecoding ? 1L : 0L);
+ check_curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING,
+ cpolicy.mContentDecoding ? "" : static_cast(NULL));
check_curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
check_curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
@@ -622,7 +642,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
case HOR_POST:
{
check_curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
- check_curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
+ // CURLOPT_ENCODING already set above for all methods.
long data_size(0);
if (mReqBody)
{
diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp
index 811b004032..6ad255d27a 100644
--- a/indra/llcorehttp/_httppolicyclass.cpp
+++ b/indra/llcorehttp/_httppolicyclass.cpp
@@ -37,7 +37,8 @@ HttpPolicyClass::HttpPolicyClass()
: mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT),
mPipelining(HTTP_PIPELINING_DEFAULT),
- mThrottleRate(HTTP_THROTTLE_RATE_DEFAULT)
+ mThrottleRate(HTTP_THROTTLE_RATE_DEFAULT),
+ mContentDecoding(HTTP_CONTENT_DECODING_DEFAULT)
{}
@@ -53,6 +54,7 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other)
mPerHostConnectionLimit = other.mPerHostConnectionLimit;
mPipelining = other.mPipelining;
mThrottleRate = other.mThrottleRate;
+ mContentDecoding = other.mContentDecoding;
}
return *this;
}
@@ -62,7 +64,8 @@ HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other)
: mConnectionLimit(other.mConnectionLimit),
mPerHostConnectionLimit(other.mPerHostConnectionLimit),
mPipelining(other.mPipelining),
- mThrottleRate(other.mThrottleRate)
+ mThrottleRate(other.mThrottleRate),
+ mContentDecoding(other.mContentDecoding)
{}
@@ -86,6 +89,10 @@ HttpStatus HttpPolicyClass::set(HttpRequest::EPolicyOption opt, long value)
mThrottleRate = llclamp(value, 0L, 1000000L);
break;
+ case HttpRequest::PO_CONTENT_DECODING:
+ mContentDecoding = value;
+ break;
+
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
@@ -114,6 +121,10 @@ HttpStatus HttpPolicyClass::get(HttpRequest::EPolicyOption opt, long * value) co
*value = mThrottleRate;
break;
+ case HttpRequest::PO_CONTENT_DECODING:
+ *value = mContentDecoding;
+ break;
+
default:
return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG);
}
diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h
index 5bf0fa1fa7..ce281835db 100644
--- a/indra/llcorehttp/_httppolicyclass.h
+++ b/indra/llcorehttp/_httppolicyclass.h
@@ -64,6 +64,7 @@ class HttpPolicyClass
long mPerHostConnectionLimit;
long mPipelining;
long mThrottleRate;
+ long mContentDecoding;
}; // end class HttpPolicyClass
} // end namespace LLCore
diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp
index e14a533479..6ed96c41de 100644
--- a/indra/llcorehttp/_httpservice.cpp
+++ b/indra/llcorehttp/_httpservice.cpp
@@ -62,7 +62,8 @@ const HttpService::OptionDescriptor HttpService::sOptionDesc[] =
{ true, true, true, false, false }, // PO_TRACE
{ true, true, false, true, false }, // PO_ENABLE_PIPELINING
{ true, true, false, true, false }, // PO_THROTTLE_RATE
- { false, false, true, false, true } // PO_SSL_VERIFY_CALLBACK
+ { false, false, true, false, true }, // PO_SSL_VERIFY_CALLBACK
+ { true, true, false, true, false } // PO_CONTENT_DECODING
};
HttpService * HttpService::sInstance(NULL);
volatile HttpService::EState HttpService::sState(NOT_INITIALIZED);
diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h
index f7516f127a..431f95f902 100644
--- a/indra/llcorehttp/httprequest.h
+++ b/indra/llcorehttp/httprequest.h
@@ -232,6 +232,22 @@ class HttpRequest
/// Global only
PO_SSL_VERIFY_CALLBACK,
+ /// Long value controlling whether libcurl is asked to
+ /// negotiate and auto-decode HTTP Content-Encoding for this
+ /// class (non-zero, the default) or left entirely alone
+ /// (zero). When left alone, libcurl neither advertises
+ /// Accept-Encoding nor inspects/validates any Content-Encoding
+ /// the server sends back -- the payload is delivered as-is.
+ ///
+ /// Use zero for classes whose servers are known to send a
+ /// non-encoding value in Content-Encoding (e.g. a MIME type
+ /// where a transfer coding was meant): libcurl treats an
+ /// unrecognized value as a hard transfer failure
+ /// (CURLE_BAD_CONTENT_ENCODING) when decoding is requested.
+ ///
+ /// Per-class only
+ PO_CONTENT_DECODING,
+
PO_LAST // Always at end
};
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 9b883322ba..4b9896d40f 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -1647,6 +1647,12 @@ void LLGLManager::initExtensions()
mHasAnisotropic = mGLExtensions.contains("GL_EXT_texture_filter_anisotropic");
}
+ mHasBPTCTextureCompression = mGLVersion >= 4.19f;
+ if (!mHasBPTCTextureCompression)
+ {
+ mHasBPTCTextureCompression = mGLExtensions.contains("GL_ARB_texture_compression_bptc");
+ }
+
mHasNVXGpuMemoryInfo = mGLExtensions.contains("GL_NVX_gpu_memory_info");
mHasATIMemInfo = mGLExtensions.contains("GL_ATI_meminfo"); //Basic AMD method, also see mHasAMDAssociations
mHasEXTMemoryObject = mGLExtensions.contains("GL_EXT_memory_object");
diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h
index cbf440ebe4..8635c011d6 100644
--- a/indra/llrender/llgl.h
+++ b/indra/llrender/llgl.h
@@ -100,6 +100,7 @@ class LLGLManager
bool mHasDebugOutput = false;
bool mHasTransformFeedback = false;
bool mHasAnisotropic = false;
+ bool mHasBPTCTextureCompression = false;
// Vendor-specific extensions
bool mHasAMDAssociations = false;
diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp
index 0ea1bd1c8a..61d71ebcbb 100644
--- a/indra/llrender/llimagegl.cpp
+++ b/indra/llrender/llimagegl.cpp
@@ -356,6 +356,9 @@ S32 LLImageGL::dataFormatBits(S32 dataformat)
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: return 8;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 8;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: return 8;
+ // BC7/BPTC: fixed 16 bytes per 4x4 block, same 8 bits/pixel rate as DXT3/DXT5
+ case GL_COMPRESSED_RGBA_BPTC_UNORM: return 8;
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: return 8;
case GL_LUMINANCE: return 8;
case GL_LUMINANCE8: return 8;
case GL_ALPHA: return 8;
@@ -408,6 +411,10 @@ S64 LLImageGL::dataFormatBytes(S32 dataformat, S32 width, S32 height)
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_RGBA_BPTC_UNORM:
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
+ // All 4x4-block formats: a texture smaller than one block still
+ // consumes a full block's worth of storage.
if (width < 4) width = 4;
if (height < 4) height = 4;
break;
@@ -447,6 +454,10 @@ S64 LLImageGL::dataFormatVRAMBytes(S32 dataformat, S32 width, S32 height)
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_RGBA_BPTC_UNORM:
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
+ // All 4x4-block formats: a texture smaller than one block still
+ // consumes a full block's worth of storage.
if (width < 4) width = 4;
if (height < 4) height = 4;
break;
@@ -469,6 +480,8 @@ S32 LLImageGL::dataFormatComponents(S32 dataformat)
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: return 4;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 4;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: return 4;
+ case GL_COMPRESSED_RGBA_BPTC_UNORM: return 4;
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: return 4;
case GL_LUMINANCE: return 1;
case GL_ALPHA: return 1;
case GL_RED: return 1;
@@ -1246,6 +1259,20 @@ bool LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
dump();
LL_ERRS() << "setSubImage called with mipmapped image (not supported)" << LL_ENDL;
}
+ if (isCompressed() || willCompressOnUpload())
+ {
+ // glTexSubImage2D is illegal on a compressed internal format --
+ // GL requires glCompressedTexSubImage2D instead, and for the
+ // generic driver-chosen compression path (willCompressOnUpload)
+ // there isn't even a way to implement that: the driver encodes
+ // the whole image opaquely on upload, and there's no API to ask
+ // it to compress just this sub-rectangle and splice it in. A
+ // real fix only exists for source data compressed via a format
+ // we control the encoder for. Until then, refuse rather than
+ // issue a GL call that silently corrupts or no-ops the update.
+ dump();
+ LL_ERRS() << "setSubImage called with a compressed (or to-be-compressed) image (not supported)" << LL_ENDL;
+ }
llassert_always(mCurrentDiscardLevel == 0);
llassert_always(x_pos >= 0 && y_pos >= 0);
@@ -1293,7 +1320,7 @@ bool LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3
if (!res) LL_ERRS() << "LLImageGL::setSubImage(): bindTexture failed" << LL_ENDL;
stop_glerror();
- const bool use_sub_image = should_stagger_image_set(isCompressed());
+ const bool use_sub_image = should_stagger_image_set(isCompressed() || willCompressOnUpload());
if (!use_sub_image)
{
// *TODO: Why does this work here, in setSubImage, but not in
@@ -1334,6 +1361,14 @@ bool LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S3
// Copy sub image from frame buffer
bool LLImageGL::setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height)
{
+ if (isCompressed() || willCompressOnUpload())
+ {
+ // Same issue as the setSubImage() guard above: glCopyTexSubImage2D
+ // is illegal on a compressed internal format, for the same reasons.
+ dump();
+ LL_ERRS() << "setSubImageFromFrameBuffer called on a compressed (or to-be-compressed) image (not supported)" << LL_ENDL;
+ }
+
if (gGL.getTexUnit(0)->bind(this, false, true))
{
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x_pos, y_pos, fb_x, fb_y, width, height);
@@ -1407,6 +1442,69 @@ void LLImageGL::deleteTextures(S32 numTextures, const U32 *textures)
}
}
+// Generic (driver-chosen) GPU compression only applies to a fixed set of
+// uncompressed internal formats -- this is the single source of truth for
+// that set, shared between the swap performed in setManualImage and
+// LLImageGL::willCompressOnUpload()'s prediction of it. Keeping one table
+// here avoids the two silently drifting apart, which is how
+// willCompressOnUpload() (folded into isCompressed() in an earlier version
+// of this fix) missed this compression path entirely: setManualImage's swap
+// is local to that call and never written back to mFormatInternal, so
+// nothing could observe it without duplicating this logic.
+static bool compressed_internal_format(S32 intformat, S32& out_compressed)
+{
+ switch (intformat)
+ {
+ case GL_RED:
+ case GL_R8:
+ out_compressed = GL_COMPRESSED_RED;
+ return true;
+ case GL_RG:
+ case GL_RG8:
+ out_compressed = GL_COMPRESSED_RG;
+ return true;
+ case GL_RGB:
+ case GL_RGB8:
+ // BC7/BPTC has no RGB-only (alpha-less) variant -- only the RGBA
+ // form exists. Promoting an alpha-less source to it is harmless
+ // (same as the pre-existing GL_COMPRESSED_RGB generic case, which
+ // is also driver-chosen and may itself pad an implicit alpha), and
+ // BC7's fixed 16 bytes/4x4 block cost doesn't change either way.
+ out_compressed = gGLManager.mHasBPTCTextureCompression ?
+ GL_COMPRESSED_RGBA_BPTC_UNORM : GL_COMPRESSED_RGB;
+ return true;
+ case GL_SRGB:
+ case GL_SRGB8:
+ out_compressed = gGLManager.mHasBPTCTextureCompression ?
+ GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : GL_COMPRESSED_SRGB;
+ return true;
+ case GL_RGBA:
+ case GL_RGBA8:
+ out_compressed = gGLManager.mHasBPTCTextureCompression ?
+ GL_COMPRESSED_RGBA_BPTC_UNORM : GL_COMPRESSED_RGBA;
+ return true;
+ case GL_SRGB_ALPHA:
+ case GL_SRGB8_ALPHA8:
+ out_compressed = gGLManager.mHasBPTCTextureCompression ?
+ GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM : GL_COMPRESSED_SRGB_ALPHA;
+ return true;
+ case GL_LUMINANCE:
+ case GL_LUMINANCE8:
+ out_compressed = GL_COMPRESSED_LUMINANCE;
+ return true;
+ case GL_LUMINANCE_ALPHA:
+ case GL_LUMINANCE8_ALPHA8:
+ out_compressed = GL_COMPRESSED_LUMINANCE_ALPHA;
+ return true;
+ case GL_ALPHA:
+ case GL_ALPHA8:
+ out_compressed = GL_COMPRESSED_ALPHA;
+ return true;
+ default:
+ return false;
+ }
+}
+
// static
void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 width, S32 height, U32 pixformat, U32 pixtype, const void* pixels, bool allow_compression, bool has_mips)
{
@@ -1540,47 +1638,21 @@ void LLImageGL::setManualImage(U32 target, S32 miplevel, S32 intformat, S32 widt
const bool compress = LLImageGL::sCompressTextures && allow_compression;
if (compress)
{
- switch (intformat)
+ S32 compressed_format;
+ if (compressed_internal_format(intformat, compressed_format))
+ {
+ // Logged once so RenderCompressTextures can be verified from a
+ // log file instead of guessed at -- which format actually gets
+ // used matters here: BC7 and driver-generic compression are
+ // very different encode-cost profiles on the upload hot path.
+ LL_INFOS_ONCE("Texture") << "Texture compression active, using "
+ << (gGLManager.mHasBPTCTextureCompression ? "BC7/BPTC" : "generic driver-chosen")
+ << " compressed formats" << LL_ENDL;
+ intformat = compressed_format;
+ }
+ else
{
- case GL_RED:
- case GL_R8:
- intformat = GL_COMPRESSED_RED;
- break;
- case GL_RG:
- case GL_RG8:
- intformat = GL_COMPRESSED_RG;
- break;
- case GL_RGB:
- case GL_RGB8:
- intformat = GL_COMPRESSED_RGB;
- break;
- case GL_SRGB:
- case GL_SRGB8:
- intformat = GL_COMPRESSED_SRGB;
- break;
- case GL_RGBA:
- case GL_RGBA8:
- intformat = GL_COMPRESSED_RGBA;
- break;
- case GL_SRGB_ALPHA:
- case GL_SRGB8_ALPHA8:
- intformat = GL_COMPRESSED_SRGB_ALPHA;
- break;
- case GL_LUMINANCE:
- case GL_LUMINANCE8:
- intformat = GL_COMPRESSED_LUMINANCE;
- break;
- case GL_LUMINANCE_ALPHA:
- case GL_LUMINANCE8_ALPHA8:
- intformat = GL_COMPRESSED_LUMINANCE_ALPHA;
- break;
- case GL_ALPHA:
- case GL_ALPHA8:
- intformat = GL_COMPRESSED_ALPHA;
- break;
- default:
LL_WARNS() << "Could not compress format: " << std::hex << intformat << std::dec << LL_ENDL;
- break;
}
}
@@ -2612,6 +2684,23 @@ bool LLImageGL::isCompressed()
return is_compressed;
}
+bool LLImageGL::willCompressOnUpload()
+{
+ // setManualImage swaps mFormatInternal to a compressed GL_COMPRESSED_*
+ // internal format at upload time when RenderCompressTextures is enabled
+ // -- BC7/BPTC where the driver supports it, generic driver-chosen
+ // compression otherwise -- but that swap is local to the call and never
+ // written back to mFormatInternal here. Predict it via the same table
+ // setManualImage uses. Distinct from isCompressed(), which
+ // answers "is my source data already GPU-compressed blocks" (the S3TC/
+ // DXT-container case) -- conflating the two broke setImage()'s
+ // data_hasmips branch, which uses isCompressed() to decide whether the
+ // incoming bytes are raw pixels or pre-compressed blocks.
+ S32 compressed_format;
+ return LLImageGL::sCompressTextures && mAllowCompression &&
+ compressed_internal_format(mFormatInternal, compressed_format);
+}
+
//----------------------------------------------------------------------------
void LLImageGL::updatePickMask(S32 width, S32 height, const U8* data_in)
{
diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h
index b819462484..0f8320480e 100644
--- a/indra/llrender/llimagegl.h
+++ b/indra/llrender/llimagegl.h
@@ -190,9 +190,12 @@ class LLImageGL : public LLRefCount
bool createGLTexture(S32 discard_level, const U8* data, bool data_hasmips = false, S32 usename = 0, bool defer_copy = false, LLGLuint* tex_name = nullptr);
void setImage(const LLImageRaw* imageraw);
bool setImage(const U8* data_in, bool data_hasmips = false, S32 usename = 0);
- // *TODO: This function may not work if the textures is compressed (i.e.
- // RenderCompressTextures is 0). Partial image updates do not work on
- // compressed textures.
+ // Partial (non-whole-image) sub-region updates do not work on
+ // compressed textures -- glTexSubImage2D is illegal on a compressed
+ // internal format, and there is no way to implement the generic
+ // driver-compression case at all (see the LL_ERRS in the .cpp).
+ // A whole-image call (x=0, y=0, width/height == getWidth()/getHeight())
+ // redirects to setImage() instead, which does handle compression.
bool setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, bool force_fast_update = false, LLGLuint use_name = 0, bool skip_unbind = false);
bool setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool force_fast_update = false, LLGLuint use_name = 0, bool skip_unbind = false);
bool setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height);
@@ -294,6 +297,7 @@ class LLImageGL : public LLRefCount
U32 createPickMask(S32 pWidth, S32 pHeight);
void freePickMask();
bool isCompressed();
+ bool willCompressOnUpload();
LLPointer mSaveData; // used for destroyGL/restoreGL
LL::WorkQueue::weak_t mMainQueue;
diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt
index 8864795625..9a9aca3bd2 100644
--- a/indra/media_plugins/cef/CMakeLists.txt
+++ b/indra/media_plugins/cef/CMakeLists.txt
@@ -91,8 +91,8 @@ else ()
set_target_properties(media_plugin_cef PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/SLPluginCEF-Info.plist.in
- MACOSX_BUNDLE_GUI_IDENTIFIER "org.alchemyviewer.viewer.mediaplugincef"
- XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.alchemyviewer.viewer.mediaplugincef"
+ MACOSX_BUNDLE_GUI_IDENTIFIER "org.vayuviewer.viewer.mediaplugincef"
+ XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.vayuviewer.viewer.mediaplugincef"
)
endif ()
endif ()
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 42c23a6fc6..7413f06c3e 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -6,7 +6,7 @@ include(CMakeCopyIfDifferent)
include(Velopack)
include(TemplateCheck)
-set(VIEWER_BINARY_NAME "alchemy-bin" CACHE STRING
+set(VIEWER_BINARY_NAME "vayu-bin" CACHE STRING
"The name of the viewer executable to create.")
# Generate version vars header
@@ -56,33 +56,33 @@ endforeach()
set(ICON_PATH "test")
set(VIEWER_MACOSX_PHASE "d")
string(TOLOWER ${VIEWER_CHANNEL} channel_lower)
-if(channel_lower MATCHES "^alchemy release")
+if(channel_lower MATCHES "^vayu release")
set(ICON_PATH "release")
set(VIEWER_MACOSX_PHASE "f")
-elseif(channel_lower MATCHES "^alchemy beta")
+elseif(channel_lower MATCHES "^vayu beta")
set(ICON_PATH "beta")
set(VIEWER_MACOSX_PHASE "b")
-elseif(channel_lower MATCHES "^alchemy project")
+elseif(channel_lower MATCHES "^vayu project")
set(ICON_PATH "project")
set(VIEWER_MACOSX_PHASE "a")
endif()
message(STATUS "Copying icons for ${ICON_PATH}")
-set(BRANDING_SOURCE_DIR "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/share/alchemy-branding")
+set(BRANDING_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/branding")
if (WINDOWS)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy.ico"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu.ico"
"${CMAKE_CURRENT_SOURCE_DIR}/res/ll_icon.ico"
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu_256.BMP"
"${CMAKE_CURRENT_SOURCE_DIR}/res/ll_icon.BMP"
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu_256.BMP"
"${CMAKE_CURRENT_SOURCE_DIR}/res-sdl/ll_icon.BMP"
)
execute_process(
@@ -98,13 +98,13 @@ if (WINDOWS)
elseif(DARWIN)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy.icns"
- "${CMAKE_CURRENT_SOURCE_DIR}/alchemy.icns"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu.icns"
+ "${CMAKE_CURRENT_SOURCE_DIR}/vayu.icns"
)
elseif(LINUX)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.BMP"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu_256.BMP"
"${CMAKE_CURRENT_SOURCE_DIR}/res-sdl/ll_icon.BMP"
)
endif ()
@@ -114,8 +114,8 @@ endif ()
# finds it regardless of which backend the package ends up using.
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
- "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/alchemy_256.png"
- "${CMAKE_CURRENT_SOURCE_DIR}/app_settings/alchemy_logo.png"
+ "${BRANDING_SOURCE_DIR}/viewer/icons/${ICON_PATH}/vayu_256.png"
+ "${CMAKE_CURRENT_SOURCE_DIR}/app_settings/vayu_logo.png"
)
if (HAVOK)
@@ -205,9 +205,11 @@ set(viewer_SOURCE_FILES
alunzip.cpp
alviewermenu.cpp
fsfloaterposer.cpp
+ fsfloaterposestand.cpp
fsjointpose.cpp
fsmaniprotatejoint.cpp
fsmaniptranslatejoint.cpp
+ fspose.cpp
fsposeranimator.cpp
fsposestate.cpp
fsposingmotion.cpp
@@ -973,9 +975,11 @@ set(viewer_HEADER_FILES
alunzip.h
alviewermenu.h
fsfloaterposer.h
+ fsfloaterposestand.h
fsjointpose.h
fsmaniprotatejoint.h
fsmaniptranslatejoint.h
+ fspose.h
fsposeranimator.h
fsposestate.h
fsposingmotion.h
@@ -1698,7 +1702,7 @@ if (DARWIN)
# Add resource files to the project.
set(viewer_RESOURCE_FILES
- alchemy.icns
+ vayu.icns
# CMake doesn't seem to support Xcode language variants well just yet
English.lproj/InfoPlist.strings
English.lproj/language.txt
@@ -1710,7 +1714,7 @@ if (DARWIN)
LIST(APPEND viewer_SOURCE_FILES llappviewersdl.cpp)
LIST(APPEND viewer_HEADER_FILES llappviewersdl.h)
LIST(APPEND viewer_RESOURCE_FILES
- Info-AlchemySDL.plist
+ Info-VayuSDL.plist
)
set_source_files_properties(llappviewersdl.cpp PROPERTIES LANGUAGE OBJCXX)
@@ -2049,8 +2053,8 @@ set(VIEWER_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}$
- COMMENT "Generating Alchemy.dSYM")
+ COMMENT "Generating Vayu Viewer.dSYM")
endif()
endif()
@@ -2170,8 +2174,8 @@ elseif(LINUX)
endif()
string(TOLOWER ${VIEWER_CHANNEL} channel_lower)
- if(channel_lower MATCHES "^alchemy release")
- set(OUTPUT_PRODUCT_NAME "AlchemyViewer")
+ if(channel_lower MATCHES "^vayu release")
+ set(OUTPUT_PRODUCT_NAME "VayuViewer")
else()
set(OUTPUT_PRODUCT_NAME "${VIEWER_CHANNEL_ONEWORD}")
endif()
@@ -2181,18 +2185,45 @@ elseif(LINUX)
set(MANIFEST_PACKAGE_OUT_FILE ${product}.tar.xz)
endif()
+# FMOD Studio isn't a vcpkg package (see FMODSTUDIO.cmake) so viewer_manifest.py
+# needs to be told directly where to find its shared libraries to bundle them
+# on Linux. Empty when FMOD Studio isn't in use.
+set(FMOD_MANIFEST_LIB_DIR "")
+if(LINUX AND USE_FMODSTUDIO)
+ set(FMOD_MANIFEST_LIB_DIR "${FMODSTUDIO_SDK_DIR}/api/core/lib/x86_64")
+endif()
+
# Viewer Manifest Copy
+#
+# --actions must include both "copy" and "package": is_packaging_viewer()
+# (which gates whether shaders/fonts/skins/app_settings XML etc. are even
+# considered in construct()) checks for "package" in the actions list, but
+# the actual file-write only happens via copy_action() -- package_action()
+# on its own is a no-op. Passing "copy" alone (as this used to) silently
+# produced a package missing everything gated by is_packaging_viewer(),
+# which doesn't fail the build -- it just makes the result fail to launch.
+# strip_binaries() (invoked by package_finish()) is a no-op for anything
+# other than a Release buildtype, so this doesn't strip debug info from
+# the more common Debug/RelWithDebInfo/OptDebug dev builds.
+#
+# Pass this as ONE quoted CMake argument with a literal space, not with
+# embedded \"...\" -- escaped-quote content inside an ARGS string is not
+# reliably preserved through Ninja's own re-escaping, and previously
+# arrived at viewer_manifest.py as "copy\" package" (note the stray
+# backslash), which made its actions.split() miss "copy" and silently
+# skip is_packaging_viewer()'s file staging on every dev build.
add_custom_command(
TARGET ${VIEWER_BINARY_NAME} POST_BUILD
COMMAND ${Python3_EXECUTABLE}
ARGS
${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
- --actions=copy
+ "--actions=copy package"
--arch=${ARCH}
--artwork=${ARTWORK_DIR}
"--bugsplat=${BUGSPLAT_DB}"
"--discord=${USE_DISCORD}"
"--openal=${USE_OPENAL}"
+ "--fmod=${FMOD_MANIFEST_LIB_DIR}"
"--tracy=${USE_TRACY}"
--build=${CMAKE_CURRENT_BINARY_DIR}
--buildtype=$
@@ -2220,12 +2251,13 @@ if(IS_DIRECTORY ${LOCAL_DIST_DIR})
COMMAND ${Python3_EXECUTABLE}
ARGS
${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py
- "--actions=\"copy package unpacked\""
+ "--actions=copy package unpacked"
--arch=${ARCH}
--artwork=${ARTWORK_DIR}
"--bugsplat=${BUGSPLAT_DB}"
"--discord=${USE_DISCORD}"
"--openal=${USE_OPENAL}"
+ "--fmod=${FMOD_MANIFEST_LIB_DIR}"
"--tracy=${USE_TRACY}"
--build=${CMAKE_CURRENT_BINARY_DIR}
--buildtype=$
@@ -2265,6 +2297,7 @@ if (PACKAGE)
--bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER}
"--discord=${USE_DISCORD}"
"--openal=${USE_OPENAL}"
+ "--fmod=${FMOD_MANIFEST_LIB_DIR}"
"--tracy=${USE_TRACY}"
"--velopack=${USE_VELOPACK}"
--build=${CMAKE_CURRENT_BINARY_DIR}
diff --git a/indra/newview/Info-AlchemySDL.plist b/indra/newview/Info-VayuSDL.plist
similarity index 94%
rename from indra/newview/Info-AlchemySDL.plist
rename to indra/newview/Info-VayuSDL.plist
index d63ee1ac08..22dc4080c9 100644
--- a/indra/newview/Info-AlchemySDL.plist
+++ b/indra/newview/Info-VayuSDL.plist
@@ -27,7 +27,7 @@
NSHumanReadableCopyright
${MACOSX_BUNDLE_COPYRIGHT}
NSMicrophoneUsageDescription
- For voice chat, you must grant permission for Alchemy to use the microphone.
+ For voice chat, you must grant permission for Vayu Viewer to use the microphone.
CFBundleDocumentTypes
@@ -36,7 +36,7 @@
slurl
CFBundleTypeIconFile
- alchemy.icns
+ vayu.icns
CFBundleTypeMIMETypes
application/x-grid-location-info
diff --git a/indra/newview/alpanelquicksettings.cpp b/indra/newview/alpanelquicksettings.cpp
index c8928f25eb..373eb8e29c 100644
--- a/indra/newview/alpanelquicksettings.cpp
+++ b/indra/newview/alpanelquicksettings.cpp
@@ -27,6 +27,8 @@
#include "alpanelquicksettings.h"
+#include
+
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
@@ -34,17 +36,59 @@
#include "llspinctrl.h"
#include "llagent.h"
+#include "llenvironment.h"
+#include "llinventoryfunctions.h"
+#include "llinventorymodel.h"
+#include "llinventorysettings.h"
#include "llviewercontrol.h"
#include "llviewerregion.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"
+#include "rlvhandler.h"
+
+namespace
+{
+ // Sentinel LLSD value (not a UUID) stored in each preset combo for the
+ // "no local override, use the region's environment" entry.
+ const std::string REGION_DEFAULT_VALUE = "__region_default__";
+
+ // Filters inventory for non-marketplace settings (sky/water/day-cycle) items,
+ // deduplicated by asset id.
+ class ALQuickSettingsCollector : public LLInventoryCollectFunctor
+ {
+ public:
+ ALQuickSettingsCollector()
+ {
+ mMarketplaceFolderUUID = gInventory.getMarketplaceListingsUUID();
+ }
+
+ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) override
+ {
+ if (item && item->getType() == LLAssetType::AT_SETTINGS &&
+ !gInventory.isObjectDescendentOf(item->getUUID(), mMarketplaceFolderUUID) &&
+ mSeen.find(item->getAssetUUID()) == mSeen.end())
+ {
+ mSeen.insert(item->getAssetUUID());
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ LLUUID mMarketplaceFolderUUID;
+ std::set mSeen;
+ };
+}
static LLPanelInjector t_quick_settings("quick_settings");
ALPanelQuickSettings::ALPanelQuickSettings()
: LLPanel(),
mHoverSlider(nullptr),
- mHoverSpinner(nullptr)
+ mHoverSpinner(nullptr),
+ mSkyPresetCombo(nullptr),
+ mWaterPresetCombo(nullptr),
+ mDayPresetCombo(nullptr)
{
}
@@ -87,6 +131,20 @@ bool ALPanelQuickSettings::postBuild()
// Set up based on initial region.
onRegionChanged();
+ // Environment quick-select
+ mSkyPresetCombo = getChild("sky_preset_combo");
+ mSkyPresetCombo->setCommitCallback(boost::bind(&ALPanelQuickSettings::onSelectSkyPreset, this));
+
+ mWaterPresetCombo = getChild("water_preset_combo");
+ mWaterPresetCombo->setCommitCallback(boost::bind(&ALPanelQuickSettings::onSelectWaterPreset, this));
+
+ mDayPresetCombo = getChild("day_preset_combo");
+ mDayPresetCombo->setCommitCallback(boost::bind(&ALPanelQuickSettings::onSelectDayPreset, this));
+
+ getChild("reset_environment_btn")->setCommitCallback(boost::bind(&ALPanelQuickSettings::onResetEnvironment, this));
+
+ loadEnvironmentPresets();
+
return LLPanel::postBuild();
}
@@ -167,3 +225,106 @@ void ALPanelQuickSettings::updateEditHoverEnabled()
syncFromPreferenceSetting();
}
}
+
+void ALPanelQuickSettings::loadEnvironmentPresets()
+{
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ ALQuickSettingsCollector collector;
+ gInventory.collectDescendentsIf(LLUUID::null, cats, items, LLInventoryModel::EXCLUDE_TRASH, collector);
+
+ std::multimap sky_map;
+ std::multimap water_map;
+ std::multimap day_map;
+
+ for (const auto& item : items)
+ {
+ switch (LLSettingsType::fromInventoryFlags(item->getFlags()))
+ {
+ case LLSettingsType::ST_SKY:
+ sky_map.emplace(item->getName(), item->getAssetUUID());
+ break;
+ case LLSettingsType::ST_WATER:
+ water_map.emplace(item->getName(), item->getAssetUUID());
+ break;
+ case LLSettingsType::ST_DAYCYCLE:
+ day_map.emplace(item->getName(), item->getAssetUUID());
+ break;
+ default:
+ break;
+ }
+ }
+
+ auto fill_combo = [](LLComboBox* combo, const std::multimap& preset_map)
+ {
+ combo->removeall();
+ combo->add("Region Default", LLSD(REGION_DEFAULT_VALUE));
+ combo->addSeparator();
+ for (const auto& [name, asset_id] : preset_map)
+ {
+ if (!name.empty())
+ {
+ combo->add(name, LLSD(asset_id));
+ }
+ }
+ combo->setCurrentByIndex(0);
+ };
+
+ fill_combo(mSkyPresetCombo, sky_map);
+ fill_combo(mWaterPresetCombo, water_map);
+ fill_combo(mDayPresetCombo, day_map);
+}
+
+void ALPanelQuickSettings::onSelectSkyPreset()
+{
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SETENV))
+ {
+ return;
+ }
+ LLSD value = mSkyPresetCombo->getSelectedValue();
+ if (value.isUUID() && value.asUUID().notNull())
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, value.asUUID());
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
+ }
+}
+
+void ALPanelQuickSettings::onSelectWaterPreset()
+{
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SETENV))
+ {
+ return;
+ }
+ LLSD value = mWaterPresetCombo->getSelectedValue();
+ if (value.isUUID() && value.asUUID().notNull())
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, value.asUUID());
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
+ }
+}
+
+void ALPanelQuickSettings::onSelectDayPreset()
+{
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SETENV))
+ {
+ return;
+ }
+ LLSD value = mDayPresetCombo->getSelectedValue();
+ if (value.isUUID() && value.asUUID().notNull())
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, value.asUUID());
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL);
+ }
+}
+
+void ALPanelQuickSettings::onResetEnvironment()
+{
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SETENV))
+ {
+ return;
+ }
+ mSkyPresetCombo->setValue(LLSD(REGION_DEFAULT_VALUE));
+ mWaterPresetCombo->setValue(LLSD(REGION_DEFAULT_VALUE));
+ mDayPresetCombo->setValue(LLSD(REGION_DEFAULT_VALUE));
+ LLEnvironment::instance().setSharedEnvironment();
+}
diff --git a/indra/newview/alpanelquicksettings.h b/indra/newview/alpanelquicksettings.h
index e6fd54a2ad..98c50311cb 100644
--- a/indra/newview/alpanelquicksettings.h
+++ b/indra/newview/alpanelquicksettings.h
@@ -52,9 +52,20 @@ class ALPanelQuickSettings final : public LLPanel
void onSimulatorFeaturesReceived(const LLUUID ®ion_id);
void updateEditHoverEnabled();
+ // Environment quick-select (sky/water/day-cycle presets)
+ void loadEnvironmentPresets();
+ void onSelectSkyPreset();
+ void onSelectWaterPreset();
+ void onSelectDayPreset();
+ void onResetEnvironment();
+
LLSlider* mHoverSlider;
LLSpinCtrl* mHoverSpinner;
+ LLComboBox* mSkyPresetCombo;
+ LLComboBox* mWaterPresetCombo;
+ LLComboBox* mDayPresetCombo;
+
boost::signals2::connection mRegionChangedSlot;
};
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index c7374b81d2..9eea887550 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -249,6 +249,18 @@
is_running_function="Floater.IsOpen"
is_running_parameters="fs_poser"
/>
+
+
+
\ No newline at end of file
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 364e55d56d..b3219748b4 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -7403,6 +7403,17 @@
Value
-1
+ QAModeHttpTrace
+
+ Comment
+ HTTP tracing level for llcorehttp/libcurl (0=off, 1=basic, 2=libcurl verbose headers, 3=verbose plus body content)
+ Persist
+ 0
+ Type
+ U32
+ Value
+ 0
+
QAModeTermCode
Comment
@@ -7780,6 +7791,17 @@
Value
0
+ RenderPBRMaterials
+
+ Comment
+ Shade faces with a PBR (metallic-roughness) material using physically based rendering. When disabled, such faces are shown with their legacy fallback diffuse texture instead, shaded like an ordinary Blinn-Phong material.
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 1
+
RenderCanUseGLTFPBROpaqueShaders
Comment
@@ -8270,6 +8292,17 @@
Value
0.5
+ RenderInterleavedAlpha
+
+ Comment
+ Depth-interleave rigged attachment alpha with the distance-sorted world alpha so transparent objects composite correctly both in front of and behind avatars. Disable to restore two-pass (all rigged first) order.
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 1
+
RenderDebugGLSession
Comment
@@ -17215,5 +17248,29 @@
0.5
+
+ FSPoseStandLock
+
+ Comment
+ When enabled, posestand will lock the avatar to the ground.
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 0
+
+ FSPoseStandLastSelectedPose
+
+ Comment
+ Last selected pose in the pose stand
+ Persist
+ 1
+ Type
+ String
+ Value
+
+
+
diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml
index f0251c620f..45dbca586c 100644
--- a/indra/newview/app_settings/settings_alchemy.xml
+++ b/indra/newview/app_settings/settings_alchemy.xml
@@ -46,6 +46,39 @@
Value
1
+ ALDrawDistanceVRAMOptimization
+
+ Comment
+ Reduce draw distance automatically when the texture VRAM budget is exceeded. On by default (matches this viewer's prior unconditional behavior); disable to always render at full draw distance regardless of VRAM pressure.
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 1
+
+ ALFrameRateLimit
+
+ Comment
+ Target upper limit of frames per second when ALLimitFramerate is enabled.
+ Persist
+ 1
+ Type
+ U32
+ Value
+ 60
+
+ ALLimitFramerate
+
+ Comment
+ Sleep at the end of each frame to cap the frame rate at ALFrameRateLimit.
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 0
+
ALSceneExplorerActivateAction
Comment
@@ -876,6 +909,28 @@
Value
1
+ AlchemyMouselookDecoupleVehicleTilt
+
+ Comment
+ Keep the mouselook view level instead of tilting with a vehicle's roll and pitch while seated
+ Persist
+ 1
+ Type
+ Boolean
+ Value
+ 0
+
+ AlchemyMouselookEyeHeightOffset
+
+ Comment
+ Vertical offset in meters applied to the mouselook camera eye position
+ Persist
+ 1
+ Type
+ F32
+ Value
+ 0.0
+
AlchemyMouselookIFF
Comment
diff --git a/indra/newview/app_settings/vayu_logo.png b/indra/newview/app_settings/vayu_logo.png
new file mode 100644
index 0000000000..214049571e
Binary files /dev/null and b/indra/newview/app_settings/vayu_logo.png differ
diff --git a/indra/newview/branding/installer/icons/install_icon.ico b/indra/newview/branding/installer/icons/install_icon.ico
new file mode 100644
index 0000000000..105b7a043d
Binary files /dev/null and b/indra/newview/branding/installer/icons/install_icon.ico differ
diff --git a/indra/newview/branding/installer/splash/beta/install_splash.gif b/indra/newview/branding/installer/splash/beta/install_splash.gif
new file mode 100644
index 0000000000..90254b8e75
Binary files /dev/null and b/indra/newview/branding/installer/splash/beta/install_splash.gif differ
diff --git a/indra/newview/branding/installer/splash/project/install_splash.gif b/indra/newview/branding/installer/splash/project/install_splash.gif
new file mode 100644
index 0000000000..90254b8e75
Binary files /dev/null and b/indra/newview/branding/installer/splash/project/install_splash.gif differ
diff --git a/indra/newview/branding/installer/splash/release/install_splash.gif b/indra/newview/branding/installer/splash/release/install_splash.gif
new file mode 100644
index 0000000000..90254b8e75
Binary files /dev/null and b/indra/newview/branding/installer/splash/release/install_splash.gif differ
diff --git a/indra/newview/branding/installer/splash/test/install_splash.gif b/indra/newview/branding/installer/splash/test/install_splash.gif
new file mode 100644
index 0000000000..90254b8e75
Binary files /dev/null and b/indra/newview/branding/installer/splash/test/install_splash.gif differ
diff --git a/indra/newview/branding/viewer/icons/beta/vayu.icns b/indra/newview/branding/viewer/icons/beta/vayu.icns
new file mode 100644
index 0000000000..2d9432dc24
Binary files /dev/null and b/indra/newview/branding/viewer/icons/beta/vayu.icns differ
diff --git a/indra/newview/branding/viewer/icons/beta/vayu.ico b/indra/newview/branding/viewer/icons/beta/vayu.ico
new file mode 100644
index 0000000000..105b7a043d
Binary files /dev/null and b/indra/newview/branding/viewer/icons/beta/vayu.ico differ
diff --git a/indra/newview/branding/viewer/icons/beta/vayu_256.BMP b/indra/newview/branding/viewer/icons/beta/vayu_256.BMP
new file mode 100644
index 0000000000..057ebd01e3
Binary files /dev/null and b/indra/newview/branding/viewer/icons/beta/vayu_256.BMP differ
diff --git a/indra/newview/branding/viewer/icons/beta/vayu_256.png b/indra/newview/branding/viewer/icons/beta/vayu_256.png
new file mode 100644
index 0000000000..214049571e
Binary files /dev/null and b/indra/newview/branding/viewer/icons/beta/vayu_256.png differ
diff --git a/indra/newview/branding/viewer/icons/project/vayu.icns b/indra/newview/branding/viewer/icons/project/vayu.icns
new file mode 100644
index 0000000000..2d9432dc24
Binary files /dev/null and b/indra/newview/branding/viewer/icons/project/vayu.icns differ
diff --git a/indra/newview/branding/viewer/icons/project/vayu.ico b/indra/newview/branding/viewer/icons/project/vayu.ico
new file mode 100644
index 0000000000..105b7a043d
Binary files /dev/null and b/indra/newview/branding/viewer/icons/project/vayu.ico differ
diff --git a/indra/newview/branding/viewer/icons/project/vayu_256.BMP b/indra/newview/branding/viewer/icons/project/vayu_256.BMP
new file mode 100644
index 0000000000..057ebd01e3
Binary files /dev/null and b/indra/newview/branding/viewer/icons/project/vayu_256.BMP differ
diff --git a/indra/newview/branding/viewer/icons/project/vayu_256.png b/indra/newview/branding/viewer/icons/project/vayu_256.png
new file mode 100644
index 0000000000..214049571e
Binary files /dev/null and b/indra/newview/branding/viewer/icons/project/vayu_256.png differ
diff --git a/indra/newview/branding/viewer/icons/release/vayu.icns b/indra/newview/branding/viewer/icons/release/vayu.icns
new file mode 100644
index 0000000000..2d9432dc24
Binary files /dev/null and b/indra/newview/branding/viewer/icons/release/vayu.icns differ
diff --git a/indra/newview/branding/viewer/icons/release/vayu.ico b/indra/newview/branding/viewer/icons/release/vayu.ico
new file mode 100644
index 0000000000..105b7a043d
Binary files /dev/null and b/indra/newview/branding/viewer/icons/release/vayu.ico differ
diff --git a/indra/newview/branding/viewer/icons/release/vayu_256.BMP b/indra/newview/branding/viewer/icons/release/vayu_256.BMP
new file mode 100644
index 0000000000..057ebd01e3
Binary files /dev/null and b/indra/newview/branding/viewer/icons/release/vayu_256.BMP differ
diff --git a/indra/newview/branding/viewer/icons/release/vayu_256.png b/indra/newview/branding/viewer/icons/release/vayu_256.png
new file mode 100644
index 0000000000..214049571e
Binary files /dev/null and b/indra/newview/branding/viewer/icons/release/vayu_256.png differ
diff --git a/indra/newview/branding/viewer/icons/test/vayu.icns b/indra/newview/branding/viewer/icons/test/vayu.icns
new file mode 100644
index 0000000000..2d9432dc24
Binary files /dev/null and b/indra/newview/branding/viewer/icons/test/vayu.icns differ
diff --git a/indra/newview/branding/viewer/icons/test/vayu.ico b/indra/newview/branding/viewer/icons/test/vayu.ico
new file mode 100644
index 0000000000..105b7a043d
Binary files /dev/null and b/indra/newview/branding/viewer/icons/test/vayu.ico differ
diff --git a/indra/newview/branding/viewer/icons/test/vayu_256.BMP b/indra/newview/branding/viewer/icons/test/vayu_256.BMP
new file mode 100644
index 0000000000..057ebd01e3
Binary files /dev/null and b/indra/newview/branding/viewer/icons/test/vayu_256.BMP differ
diff --git a/indra/newview/branding/viewer/icons/test/vayu_256.png b/indra/newview/branding/viewer/icons/test/vayu_256.png
new file mode 100644
index 0000000000..214049571e
Binary files /dev/null and b/indra/newview/branding/viewer/icons/test/vayu_256.png differ
diff --git a/indra/newview/fsfloaterposestand.cpp b/indra/newview/fsfloaterposestand.cpp
new file mode 100644
index 0000000000..b67bb10a52
--- /dev/null
+++ b/indra/newview/fsfloaterposestand.cpp
@@ -0,0 +1,138 @@
+/*
+ * @file fsfloaterposestand.cpp
+ * @brief It's a pose stand!
+ *
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Cinder Roxley wrote this file. As long as you retain this notice you can do
+ * whatever you want with this stuff. If we meet some day, and you think this
+ * stuff is worth it, you can buy me a beer in return
+ * ----------------------------------------------------------------------------
+ * Ported from Phoenix Firestorm Viewer to Vayu Viewer.
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "fsfloaterposestand.h"
+
+#include "fspose.h"
+#include "llagent.h"
+#include "llvoavatarself.h"
+#include "llsdserialize.h"
+#include "lltrans.h"
+#include "llviewercontrol.h"
+#include "rlvhandler.h"
+
+FSFloaterPoseStand::FSFloaterPoseStand(const LLSD& key)
+: LLFloater(key),
+ mComboPose(nullptr),
+ mPoseStandLock(false),
+ mAOPaused(false)
+{
+}
+
+bool FSFloaterPoseStand::postBuild()
+{
+ mComboPose = getChild("pose_combo");
+ mComboPose->setCommitCallback(boost::bind(&FSFloaterPoseStand::onCommitCombo, this));
+ loadPoses();
+
+ return true;
+}
+
+// virtual
+void FSFloaterPoseStand::onOpen(const LLSD& key)
+{
+ if (!isAgentAvatarValid())
+ {
+ return;
+ }
+
+ if (gSavedPerAccountSettings.getBOOL("AlchemyAOEnable"))
+ {
+ gSavedPerAccountSettings.setBOOL("AlchemyAOEnable", false);
+ mAOPaused = true;
+ }
+
+ if (gSavedSettings.getBOOL("FSPoseStandLock")
+ && !gAgentAvatarp->isSitting()
+ && !gRlvHandler.hasBehaviour(RLV_BHVR_SIT))
+ {
+ setLock(true);
+ }
+ gAgent.stopCurrentAnimations(true);
+ gAgent.setCustomAnim(true);
+ gFocusMgr.setKeyboardFocus(nullptr);
+ gFocusMgr.setMouseCapture(nullptr);
+ std::string last_pose = gSavedSettings.getString("FSPoseStandLastSelectedPose");
+ if (!last_pose.empty())
+ {
+ mComboPose->setSelectedByValue(last_pose, true);
+ }
+ onCommitCombo();
+}
+
+// virtual
+void FSFloaterPoseStand::onClose(bool app_quitting)
+{
+ if (!isAgentAvatarValid())
+ {
+ return;
+ }
+
+ if (mPoseStandLock && gAgentAvatarp->isSitting())
+ {
+ setLock(false);
+ gAgent.standUp();
+ }
+ gAgent.setCustomAnim(false);
+ FSPose::getInstance()->stopPose();
+ gAgent.stopCurrentAnimations(true);
+ if (mAOPaused && !gSavedPerAccountSettings.getBOOL("AlchemyAOEnable"))
+ {
+ gSavedPerAccountSettings.setBOOL("AlchemyAOEnable", true);
+ mAOPaused = false;
+ }
+}
+
+void FSFloaterPoseStand::loadPoses()
+{
+ const std::string pose_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "posestand.xml");
+ llifstream pose_file(pose_filename.c_str());
+ LLSD poses;
+ if (pose_file.is_open())
+ {
+ if (LLSDSerialize::fromXML(poses, pose_file) >= 1)
+ {
+ for (LLSD::map_iterator p_itr = poses.beginMap(); p_itr != poses.endMap(); ++p_itr)
+ {
+ LLUUID anim_id(p_itr->first);
+ if (anim_id.notNull())
+ {
+ mComboPose->add(LLTrans::getString(p_itr->second["name"].asStringRef()), anim_id);
+ }
+ }
+ }
+ pose_file.close();
+ }
+ mComboPose->sortByName();
+}
+
+void FSFloaterPoseStand::onCommitCombo()
+{
+ std::string selected_pose = mComboPose->getValue();
+ gSavedSettings.setString("FSPoseStandLastSelectedPose", selected_pose);
+ FSPose::getInstance()->setPose(selected_pose);
+}
+
+void FSFloaterPoseStand::setLock(bool enabled)
+{
+ if (enabled)
+ {
+ gAgent.sitDown();
+ }
+ else
+ {
+ gAgent.standUp();
+ }
+ mPoseStandLock = enabled;
+}
diff --git a/indra/newview/fsfloaterposestand.h b/indra/newview/fsfloaterposestand.h
new file mode 100644
index 0000000000..9d072198bc
--- /dev/null
+++ b/indra/newview/fsfloaterposestand.h
@@ -0,0 +1,41 @@
+ /*
+ * @file fsfloaterposestand.h
+ * @brief Pose stand definitions
+ *
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Cinder Roxley wrote this file. As long as you retain this notice you can do
+ * whatever you want with this stuff. If we meet some day, and you think this
+ * stuff is worth it, you can buy me a beer in return
+ * ----------------------------------------------------------------------------
+ * Ported from Phoenix Firestorm Viewer to Vayu Viewer.
+ */
+
+#ifndef FS_FLOATERPOSESTAND_H
+#define FS_FLOATERPOSESTAND_H
+
+#include "llfloater.h"
+#include "llcombobox.h"
+
+class FSFloaterPoseStand
+: public LLFloater
+{
+ LOG_CLASS(FSFloaterPoseStand);
+public:
+ FSFloaterPoseStand(const LLSD& key);
+ bool postBuild() override;
+ void setLock(bool enabled);
+ void onCommitCombo();
+
+private:
+ ~FSFloaterPoseStand() = default;
+ void onOpen(const LLSD& key) override;
+ void onClose(bool app_quitting) override;
+ void loadPoses();
+
+ bool mAOPaused;
+ bool mPoseStandLock;
+ LLComboBox* mComboPose;
+};
+
+#endif // FS_FLOATERPOSESTAND_H
diff --git a/indra/newview/linux_tools/install.sh b/indra/newview/linux_tools/install.sh
index 8dfcb8e7c6..85006f51ef 100755
--- a/indra/newview/linux_tools/install.sh
+++ b/indra/newview/linux_tools/install.sh
@@ -48,8 +48,8 @@ function warn()
function homedir_install()
{
warn "You are not running as a privileged user, so you will only be able"
- warn "to install the Alchemy Viewer in your home directory. If you"
- warn "would like to install the Alchemy Viewer system-wide, please run"
+ warn "to install the Vayu Viewer in your home directory. If you"
+ warn "would like to install the Vayu Viewer system-wide, please run"
warn "this script as the root user, or with the 'sudo' command."
echo
@@ -58,13 +58,13 @@ function homedir_install()
exit 0
fi
- install_to_prefix "$HOME/.alchemy-install"
- $HOME/.alchemy-install/etc/refresh_desktop_app_entry.sh
+ install_to_prefix "$HOME/.vayu-install"
+ $HOME/.vayu-install/etc/refresh_desktop_app_entry.sh
}
function root_install()
{
- local default_prefix="/opt/alchemy-install"
+ local default_prefix="/opt/vayu-install"
echo -n "Enter the desired installation directory [${default_prefix}]: ";
read -r
diff --git a/indra/newview/linux_tools/refresh_desktop_app_entry.sh b/indra/newview/linux_tools/refresh_desktop_app_entry.sh
index aa6a70eb1f..f9e7d10457 100755
--- a/indra/newview/linux_tools/refresh_desktop_app_entry.sh
+++ b/indra/newview/linux_tools/refresh_desktop_app_entry.sh
@@ -5,39 +5,88 @@ RUN_PATH=$(dirname "${SCRIPTSRC}" || echo .)
install_prefix="$(realpath -- "${RUN_PATH}/..")"
-function install_desktop_entry()
+# Installs $desktop_entries_dir/$filename from $content, but only if $content
+# has actually changed since we last wrote it (tracked via a hidden sha256
+# stamp file next to the .desktop entry -- hidden so desktop environments'
+# *.desktop directory scans ignore it). desktop-file-install and
+# update-desktop-database are real work (they rewrite desktop/MIME caches);
+# this script used to run them unconditionally on every single launch of the
+# viewer, which was pointless I/O for the overwhelmingly common case where
+# nothing changed since last time. Still self-healing: if the .desktop file
+# is missing (e.g. the user deleted it) or its content would differ (e.g. an
+# upgrade changed the Exec path), it gets reinstalled.
+function install_desktop_entry_if_changed()
+{
+ local filename="$1"
+ local desktop_entries_dir="$2"
+ local content="$3"
+
+ local target="${desktop_entries_dir}/${filename}"
+ local stamp="${desktop_entries_dir}/.${filename}.sha256"
+ local new_hash
+ new_hash="$(echo -e "$content" | sha256sum | cut -d' ' -f1)"
+
+ if [ -f "$target" ] && [ -f "$stamp" ] && [ "$(cat "$stamp")" == "$new_hash" ]; then
+ return
+ fi
+
+ echo " - Installing menu entry ${filename} in ${desktop_entries_dir}"
+ WORK_DIR=$(mktemp -d)
+ echo -e "$content" > "${WORK_DIR}/${filename}" || echo "Failed to install application menu!"
+ desktop-file-install --dir="${desktop_entries_dir}" "${WORK_DIR}/${filename}"
+ rm -r "$WORK_DIR"
+
+ echo "$new_hash" > "$stamp"
+ UPDATED=1
+}
+
+function install_desktop_entries()
{
local installation_prefix="$1"
local desktop_entries_dir="$2"
- local desktop_entry="\
+ UPDATED=
+
+ # Actions= + a [Desktop Action ...] section is the freedesktop Desktop
+ # Actions spec -- it surfaces as a right-click context-menu item on the
+ # launcher icon (GNOME Shell, KDE Plasma, etc.), rather than a second,
+ # separate app entry. GPU selection (switcherooctl) and GameMode are
+ # handled explicitly by the "vayu" wrapper script itself rather than via
+ # the PrefersNonDefaultGPU hint here, since that hint is only honored by
+ # some desktop environments and does nothing when launching from a
+ # terminal.
+ local main_entry="\
[Desktop Entry]\n\
-Name=Alchemy\n\
-GenericName=Alchemy Viewer\n\
+Name=Vayu Viewer\n\
+GenericName=Vayu Viewer\n\
Comment=Client for the On-line Virtual World, Second Life\n\
+Exec=${installation_prefix}/vayu\n\
Path=${installation_prefix}\n\
-Exec=${installation_prefix}/alchemy\n\
-Icon=${installation_prefix}/alchemy_icon.png\n\
+Icon=${installation_prefix}/vayu_icon.png\n\
Terminal=false\n\
Type=Application\n\
Categories=Game;Simulation;\n\
StartupNotify=true\n\
-StartupWMClass="org.alchemyviewer.viewer"\n\
-X-Desktop-File-Install-Version=3.0"
+StartupWMClass="org.vayuviewer.viewer"\n\
+Actions=Zink;\n\
+X-Desktop-File-Install-Version=3.0\n\
+\n\
+[Desktop Action Zink]\n\
+Name=Launch with Zink (OpenGL-over-Vulkan)\n\
+Exec=${installation_prefix}/vayu-zink\n\
+Icon=${installation_prefix}/vayu_icon.png"
- echo " - Installing menu entries in ${desktop_entries_dir}"
- WORK_DIR=$(mktemp -d)
- echo -e "$desktop_entry" > "${WORK_DIR}/alchemy-viewer.desktop" || "Failed to install application menu!"
- desktop-file-install --dir="${desktop_entries_dir}" "${WORK_DIR}/alchemy-viewer.desktop"
- rm -r "$WORK_DIR"
+ install_desktop_entry_if_changed "vayu-viewer.desktop" "$desktop_entries_dir" "$main_entry"
- update-desktop-database "${desktop_entries_dir}"
+ if [ -n "$UPDATED" ]; then
+ update-desktop-database "${desktop_entries_dir}"
+ fi
}
if [ "$UID" == "0" ]; then
# system-wide
- install_desktop_entry "$install_prefix" /usr/local/share/applications
+ install_desktop_entries "$install_prefix" /usr/local/share/applications
else
# user-specific
- install_desktop_entry "$install_prefix" "$HOME/.local/share/applications"
+ install_desktop_entries "$install_prefix" "$HOME/.local/share/applications"
fi
diff --git a/indra/newview/linux_tools/register_secondlifeprotocol.sh b/indra/newview/linux_tools/register_secondlifeprotocol.sh
index 70f2c638b4..fb091bd027 100755
--- a/indra/newview/linux_tools/register_secondlifeprotocol.sh
+++ b/indra/newview/linux_tools/register_secondlifeprotocol.sh
@@ -24,16 +24,16 @@ function install_desktop_entry()
local desktop_entry="\
[Desktop Entry]\n\
-Name=Second Life SLURL handler for Alchemy\n\
+Name=Second Life SLURL handler for Vayu Viewer\n\
Path=${installation_prefix}\n\
Exec=${HANDLER} %u\n\
-Icon=${installation_prefix}/alchemy_icon.png\n\
+Icon=${installation_prefix}/vayu_icon.png\n\
Terminal=false\n\
Type=Application\n\
StartupNotify=true\n\
-StartupWMClass="org.alchemyviewer.viewer"\n\
+StartupWMClass="org.vayuviewer.viewer"\n\
NoDisplay=true\n\
-MimeType=x-scheme-handler/alchemy\n\
+MimeType=x-scheme-handler/vayu\n\
X-Desktop-File-Install-Version=3.0"
echo " - Installing protocol entries in ${desktop_entries_dir}"
diff --git a/indra/newview/linux_tools/wrapper-zink.sh b/indra/newview/linux_tools/wrapper-zink.sh
new file mode 100755
index 0000000000..f74ff5cb59
--- /dev/null
+++ b/indra/newview/linux_tools/wrapper-zink.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+## Force Mesa's Zink (OpenGL-over-Vulkan) driver instead of the GPU's
+## native GL driver. Everything else -- including GameMode and GPU
+## selection -- lives in the regular "vayu" wrapper, which this script
+## hands off to below.
+export MESA_LOADER_DRIVER_OVERRIDE=zink
+
+SCRIPTSRC=$(readlink -f "$0" || echo "$0")
+RUN_PATH=$(dirname "${SCRIPTSRC}" || echo .)
+
+exec "${RUN_PATH}/vayu" "$@"
diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh
index a5e91e5da3..68120436d9 100755
--- a/indra/newview/linux_tools/wrapper.sh
+++ b/indra/newview/linux_tools/wrapper.sh
@@ -8,6 +8,22 @@
## GL Driver Options
export mesa_glthread=true
+## --- AMD PERFORMANCE TUNING (carried over from the Firestorm High
+## Performance launcher) ---
+## AMD_DEBUG=lowprecision trades a small amount of rendering precision
+## for throughput on radeonsi/radv. Harmless no-op on non-AMD Mesa
+## drivers. Comment out if you see visual artifacts.
+export AMD_DEBUG=lowprecision
+
+## Swap in mimalloc as the allocator -- measurably lower allocator
+## overhead than glibc malloc for this workload. Path assumes an
+## RPM-family distro layout (openSUSE/Fedora); adjust for others
+## (e.g. /usr/lib/x86_64-linux-gnu/libmimalloc.so.3 on Debian/Ubuntu).
+## A missing path here just produces a harmless ld.so warning, it
+## doesn't stop the viewer from starting.
+export LD_PRELOAD=/usr/lib64/libmimalloc.so.3
+# export LD_PRELOAD=/usr/lib64/libjemalloc.so.2
+
## Everything below this line is just for advanced troubleshooters.
##-------------------------------------------------------------------
@@ -24,6 +40,15 @@ export mesa_glthread=true
## Nothing worth editing below this line.
##-------------------------------------------------------------------
+# Our statically-linked OpenSSL still reads the system's /etc/ssl/openssl.cnf
+# (compiled-in OPENSSLDIR). On distros whose crypto-policy include file uses
+# properties our vendored OpenSSL doesn't recognize (e.g. openSUSE/Fedora's
+# rh-allow-sha1-signatures), the very first TLS handshake of the process
+# fails config parsing and curl misreports it as CURLE_OUT_OF_MEMORY; every
+# handshake after that succeeds since OpenSSL doesn't retry the failed load.
+# Skip the system config entirely so we always get OpenSSL's built-in defaults.
+export OPENSSL_CONF=/dev/null
+
SCRIPTSRC=$(readlink -f "$0" || echo "$0")
RUN_PATH=$(dirname "${SCRIPTSRC}" || echo .)
echo "Running from ${RUN_PATH}"
@@ -52,11 +77,36 @@ for ARG in "$@"; do
fi
done
+# gamemoderun asks the gamemode daemon for performance-mode tuning (CPU
+# governor, GPU power profile, etc.) for the process tree below it.
+# switcherooctl launch runs that process tree on the system's preferred
+# (usually discrete/more powerful) GPU via switcheroo-control. We do this
+# directly here rather than relying on desktop-entry hints like
+# PrefersNonDefaultGPU, since those are only honored by some desktop
+# environments and don't cover launching from a terminal at all. Both are
+# optional performance tools: skip whichever isn't installed rather than
+# failing to launch, and skip both under LL_WRAPPER (gdb/valgrind/etc.)
+# since advanced troubleshooters want a plain, unwrapped process tree.
+CMD=()
+if [ -z "$LL_WRAPPER" ]; then
+ if command -v gamemoderun >/dev/null 2>&1; then
+ CMD+=(gamemoderun)
+ else
+ echo "gamemoderun not found; launching without GameMode." >&2
+ fi
+ if command -v switcherooctl >/dev/null 2>&1; then
+ CMD+=(switcherooctl launch)
+ else
+ echo "switcherooctl not found; launching without explicit GPU selection." >&2
+ fi
+fi
+
# Run the program.
# Don't quote $LL_WRAPPER because, if empty, it should simply vanish from the
# command line. But DO quote "${ARGS[@]}": preserve separate args as
# individually quoted.
-$LL_WRAPPER bin/alchemy-bin "${ARGS[@]}"
+CMD+=($LL_WRAPPER bin/vayu-bin "${ARGS[@]}")
+"${CMD[@]}"
LL_RUN_ERR=$?
# Handle any resulting errors
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp
index a0e2434c10..797ff4e66d 100644
--- a/indra/newview/llagent.cpp
+++ b/indra/newview/llagent.cpp
@@ -312,7 +312,11 @@ bool LLAgent::isActionAllowed(const LLSD& sdname)
const std::string& param = sdname.asString();
- if (param == "speak")
+ if (param == "not_sitting")
+ {
+ retval = isAgentAvatarValid() && !gAgentAvatarp->isSitting();
+ }
+ else if (param == "speak")
{
bool allow_agent_voice = false;
LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel();
@@ -4763,7 +4767,7 @@ void LLAgent::setTeleportState(ETeleportState state)
}
}
-void LLAgent::stopCurrentAnimations()
+void LLAgent::stopCurrentAnimations(bool force_keep_script_perms)
{
LL_DEBUGS("Avatar") << "Stopping current animations" << LL_ENDL;
@@ -4802,6 +4806,7 @@ void LLAgent::stopCurrentAnimations()
// Revoke all animation permissions
if (mRegionp &&
+ !force_keep_script_perms &&
gSavedSettings.getBOOL("RevokePermsOnStopAnimation"))
{
U32 permissions = SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TRIGGER_ANIMATION].permbit | SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS].permbit;
diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h
index f107976fc6..4eda4d0507 100644
--- a/indra/newview/llagent.h
+++ b/indra/newview/llagent.h
@@ -515,7 +515,7 @@ class LLAgent : public LLOldEvents::LLObservable
// Animations
//--------------------------------------------------------------------
public:
- void stopCurrentAnimations();
+ void stopCurrentAnimations(bool force_keep_script_perms = false);
void requestStopMotion(LLMotion* motion);
void onAnimStop(const LLUUID& id);
void sendAnimationRequests(const std::vector &anim_ids, EAnimRequest request);
diff --git a/indra/newview/llagentbenefits.cpp b/indra/newview/llagentbenefits.cpp
index 1301bbeef7..d817d7c65e 100644
--- a/indra/newview/llagentbenefits.cpp
+++ b/indra/newview/llagentbenefits.cpp
@@ -235,6 +235,12 @@ bool LLAgentBenefits::findUploadCost(const LLAssetType::EType& asset_type, S32&
cost = getAnimationUploadCost();
succ = true;
}
+ else if (asset_type == LLAssetType::AT_NOTECARD || asset_type == LLAssetType::AT_LSL_TEXT)
+ {
+ // Notecards and scripts have always been free to create.
+ cost = 0;
+ succ = true;
+ }
return succ;
}
diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp
index 41f349b148..c013732649 100644
--- a/indra/newview/llagentcamera.cpp
+++ b/indra/newview/llagentcamera.cpp
@@ -1604,9 +1604,14 @@ void LLAgentCamera::updateCamera()
}
}
- LLViewerCamera::getInstance()->updateCameraLocation(head_pos,
+ // Apply the same vertical eye-height offset used by calcCameraPositionTargetGlobal()
+ // to both the camera origin and the look-at target, so it doesn't introduce pitch.
+ static LLCachedControl mouselook_eye_height_offset(gSavedSettings, "AlchemyMouselookEyeHeightOffset", 0.f);
+ LLVector3 eye_height_offset(0.f, 0.f, mouselook_eye_height_offset);
+
+ LLViewerCamera::getInstance()->updateCameraLocation(head_pos + eye_height_offset,
mCameraUpVector,
- gAgentAvatarp->mHeadp->getWorldPosition() +
+ gAgentAvatarp->mHeadp->getWorldPosition() + eye_height_offset +
LLVector3(1.0, 0.0, 0.0) * agent_rot);
}
else
@@ -1702,7 +1707,8 @@ LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal()
if (isAgentAvatarValid() && gAgentAvatarp->getParent())
{
LLViewerObject* root_object = (LLViewerObject*)gAgentAvatarp->getRoot();
- if (!root_object->flagCameraDecoupled())
+ static LLCachedControl decouple_vehicle_tilt(gSavedSettings, "AlchemyMouselookDecoupleVehicleTilt", false);
+ if (!root_object->flagCameraDecoupled() && !decouple_vehicle_tilt)
{
agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation();
}
@@ -1884,6 +1890,10 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(bool *hit_limit)
}
head_offset.clearVec();
+
+ static LLCachedControl mouselook_eye_height_offset(gSavedSettings, "AlchemyMouselookEyeHeightOffset", 0.f);
+ head_offset[VZ] += mouselook_eye_height_offset;
+
F32 fixup;
if (gAgentAvatarp->hasPelvisFixup(fixup) && !gAgentAvatarp->isSitting())
{
diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp
index ece18fb5cc..95860a9420 100644
--- a/indra/newview/llappcorehttp.cpp
+++ b/indra/newview/llappcorehttp.cpp
@@ -59,57 +59,63 @@ static const struct
U32 mMax;
U32 mRate;
bool mPipelined;
+ bool mContentDecoding;
std::string mKey;
const char * mUsage;
} init_data[LLAppCoreHttp::AP_COUNT] =
{
{ // AP_DEFAULT
- 8, 8, 8, 0, false,
+ 8, 8, 8, 0, false, true,
"",
"other"
},
{ // AP_TEXTURE
- 8, 1, 12, 0, true,
+ // Binary asset fetch direct from the asset CDN, which is known to
+ // send a bogus Content-Encoding (a MIME type, not a transfer
+ // coding) on every response -- libcurl treats that as a hard
+ // failure once auto-decoding is requested at all, so leave
+ // Content-Encoding alone for this class (see PO_CONTENT_DECODING).
+ 8, 1, 12, 0, true, false,
"TextureFetchConcurrency",
"texture fetch"
},
{ // AP_MESH1
- 32, 1, 128, 0, false,
+ 32, 1, 128, 0, false, false,
"MeshMaxConcurrentRequests",
"mesh fetch"
},
{ // AP_MESH2
- 8, 1, 32, 0, true,
+ 8, 1, 32, 0, true, false,
"Mesh2MaxConcurrentRequests",
"mesh2 fetch"
},
{ // AP_LARGE_MESH
- 2, 1, 8, 0, false,
+ 2, 1, 8, 0, false, false,
"",
"large mesh fetch"
},
{ // AP_UPLOADS
- 2, 1, 8, 0, false,
+ 2, 1, 8, 0, false, true,
"",
"asset upload"
},
{ // AP_LONG_POLL
- 32, 32, 32, 0, false,
+ 32, 32, 32, 0, false, true,
"",
"long poll"
},
{ // AP_INVENTORY
- 4, 1, 4, 0, false,
+ 4, 1, 4, 0, false, true,
"",
"inventory"
},
{ // AP_MATERIALS
- 2, 1, 8, 0, false,
+ 2, 1, 8, 0, false, true,
"RenderMaterials",
"material manager requests"
},
{ // AP_AGENT
- 2, 1, 32, 0, false,
+ 2, 1, 32, 0, false, true,
"Agent",
"Agent requests"
}
@@ -417,6 +423,23 @@ void LLAppCoreHttp::refreshSettings(bool initial)
}
}
+ if (! init_data[i].mContentDecoding)
+ {
+ // This class's server is known to send a bogus
+ // Content-Encoding; don't ask libcurl to negotiate or
+ // auto-decode it (see PO_CONTENT_DECODING).
+ status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CONTENT_DECODING,
+ mHttpClasses[app_policy].mPolicy,
+ 0L,
+ NULL);
+ if (! status)
+ {
+ LL_WARNS("Init") << "Unable to disable content decoding for " << init_data[i].mUsage
+ << ". Reason: " << status.toString()
+ << LL_ENDL;
+ }
+ }
+
}
// Init- or run-time settings. Must use the queued request API.
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b635e6e4dc..0f082557a3 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1586,6 +1586,26 @@ bool LLAppViewer::doFrame()
}
}
+ // Optional hard frame-rate cap: sleep off whatever's left of the
+ // target frame time. Ported from Firestorm's FSLimitFramerate.
+ {
+ static LLTimer s_frame_limit_timer;
+ static LLCachedControl limit_framerate(gSavedSettings, "ALLimitFramerate", false);
+ static LLCachedControl frame_rate_limit(gSavedSettings, "ALFrameRateLimit", 60);
+ if (limit_framerate && LLStartUp::getStartupState() == STATE_STARTED
+ && !gTeleportDisplay && !logoutRequestSent() && frame_rate_limit > 0)
+ {
+ F32 min_frame_time = 1.f / (F32)frame_rate_limit;
+ S32 milliseconds_to_sleep = llclamp((S32)((min_frame_time - s_frame_limit_timer.getElapsedTimeF64()) * 1000.f), 0, 1000);
+ if (milliseconds_to_sleep > 0)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_APP("FPS limiter sleep");
+ ms_sleep(milliseconds_to_sleep);
+ }
+ }
+ s_frame_limit_timer.reset();
+ }
+
if (mRandomizeFramerate)
{
ms_sleep(rand() % 200);
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index e2b636e55d..a38d71e0f3 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -243,7 +243,7 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
bool create_app_mutex()
{
bool result = true;
- LPCWSTR unique_mutex_name = L"AlchemyAppMutex";
+ LPCWSTR unique_mutex_name = L"VayuAppMutex";
HANDLE hMutex;
hMutex = CreateMutex(NULL, TRUE, unique_mutex_name);
if (GetLastError() == ERROR_ALREADY_EXISTS)
@@ -278,7 +278,7 @@ void ll_nvapi_init(NvDRSSessionHandle hSession)
}
NvAPI_UnicodeString profile_name;
- std::wstring w_app_name = TEXT("Alchemy Viewer");
+ std::wstring w_app_name = TEXT("Vayu Viewer");
wsprintf(reinterpret_cast(profile_name), L"%s", w_app_name.c_str());
NvDRSProfileHandle hProfile = 0;
// (3) Check if we already have an application profile for the viewer
@@ -291,7 +291,7 @@ void ll_nvapi_init(NvDRSSessionHandle hSession)
else if (status == NVAPI_PROFILE_NOT_FOUND)
{
// Don't have an application profile yet - create one
- LL_INFOS() << "Creating Alchemy Viewer profile for NVIDIA driver" << LL_ENDL;
+ LL_INFOS() << "Creating Vayu Viewer profile for NVIDIA driver" << LL_ENDL;
NVDRS_PROFILE profileInfo;
profileInfo.version = NVDRS_PROFILE_VER;
diff --git a/indra/newview/llcontrolavatar.cpp b/indra/newview/llcontrolavatar.cpp
index 660fb1b41a..e9e6b73352 100644
--- a/indra/newview/llcontrolavatar.cpp
+++ b/indra/newview/llcontrolavatar.cpp
@@ -366,6 +366,21 @@ void LLControlAvatar::idleUpdate(LLAgent &agent, const F64 &time)
else
{
LLVOAvatar::idleUpdate(agent,time);
+
+ // stamp the animesh bridge like LLVOAvatar::idleUpdateMisc stamps
+ // attachments. Worn animesh is skipped: the wearer's loop stamps this
+ // same bridge with its own depth and order. Bridge fetched from the
+ // drawable -- mControlAVBridge can go stale (see markForDeath).
+ if (mRootVolp && mRootVolp->mDrawable && !getAttachedAvatar())
+ {
+ LLSpatialBridge* bridge = mRootVolp->mDrawable->getSpatialBridge();
+ if (bridge)
+ {
+ bridge->mAvatarp = this;
+ bridge->mRenderOrder = 0;
+ bridge->mAvatarDepth = calcRiggedAlphaDepth();
+ }
+ }
}
}
diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp
index 8045dfcff5..fe756070a6 100644
--- a/indra/newview/lldrawpoolalpha.cpp
+++ b/indra/newview/lldrawpoolalpha.cpp
@@ -26,6 +26,8 @@
#include "llviewerprecompiledheaders.h"
+#include
+
#include "lldrawpoolalpha.h"
#include "llglheaders.h"
@@ -161,6 +163,15 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
// prepare shaders
llassert(LLPipeline::sRenderDeferred);
+ const bool interleave = LLPipeline::canUseInterleavedAlpha() &&
+ getType() == LLDrawPool::POOL_ALPHA_POST_WATER;
+ if (interleave)
+ {
+ // postSort deliberately leaves the shared lists in legacy order for
+ // pre-water; switch them only for the consumer that performs the merge
+ gPipeline.sortAlphaGroupsForInterleaving();
+ }
+
emissive_shader = &gDeferredEmissiveProgram;
prepare_alpha_shader(emissive_shader, false, water_sign);
@@ -197,14 +208,25 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
// already being setup for rendering
LLGLSLShader::unbind();
- if (!LLPipeline::sRenderingHUDs)
+ if (interleave)
{
- // first pass, render rigged objects only and render to depth buffer
- forwardRender(true);
+ // single pass: depth-interleave whole avatars with the distance-sorted
+ // alpha groups so world alpha composites correctly around avatars.
+ // The centralized eligibility gate limits the merged walk to the world
+ // camera; HUD, cube, and reflection renders keep legacy two-pass order.
+ forwardRender(EAlphaStream::INTERLEAVED);
}
+ else
+ {
+ if (!LLPipeline::sRenderingHUDs)
+ {
+ // first pass, render rigged objects only and render to depth buffer
+ forwardRender(EAlphaStream::RIGGED);
+ }
- // second pass, regular forward alpha rendering
- forwardRender();
+ // second pass, regular forward alpha rendering
+ forwardRender();
+ }
// final pass, render to depth for depth of field effects
if (!LLPipeline::sImpostorRender && LLPipeline::RenderDepthOfField && !gCubeSnapshot && !LLPipeline::sRenderingHUDs && getType() == LLDrawPool::POOL_ALPHA_POST_WATER)
@@ -227,7 +249,7 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
}
}
-void LLDrawPoolAlpha::forwardRender(bool rigged)
+void LLDrawPoolAlpha::forwardRender(EAlphaStream stream)
{
gPipeline.enableLightsDynamic();
@@ -236,7 +258,7 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
//enable writing to alpha for emissive effects
gGL.setColorMask(true, true);
- bool write_depth = rigged ||
+ bool write_depth = stream == EAlphaStream::RIGGED ||
LLDrawPoolWater::sSkipScreenCopy
// we want depth written so that rendered alpha will
// contribute to the alpha mask used for impostors
@@ -244,7 +266,8 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
|| getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER; // needed for accurate water fog
- LLGLDepthTest depth(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE);
+ // in interleaved mode depth writes are decided per group inside renderAlpha
+ LLGLDepthTest depth(GL_TRUE, (write_depth && stream != EAlphaStream::INTERLEAVED) ? GL_TRUE : GL_FALSE);
mColorSFactor = LLRender::BF_SOURCE_ALPHA; // } regular alpha blend
mColorDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // }
@@ -254,11 +277,11 @@ void LLDrawPoolAlpha::forwardRender(bool rigged)
// If the face is more than 90% transparent, then don't update the Depth buffer for Dof
// We don't want the nearly invisible objects to cause of DoF effects
- renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged);
+ renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, stream);
gGL.setColorMask(true, false);
- if (!rigged && getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)
+ if (stream != EAlphaStream::RIGGED && getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)
{ //render "highlight alpha" on final non-rigged pass
// NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender"
// so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state
@@ -552,9 +575,13 @@ void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector& emissiv
}
}
-void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
+void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, EAlphaStream stream)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
+ const bool merged = stream == EAlphaStream::INTERLEAVED;
+ // stream of the current group; flips per group in the interleaved walk
+ bool rigged = stream == EAlphaStream::RIGGED;
+
bool initialized_lighting = false;
bool light_enabled = true;
@@ -563,18 +590,21 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
const LLGLSLShader* lastAvatarShader = nullptr;
bool skipLastSkin = false;
- LLCullResult::sg_iterator begin;
- LLCullResult::sg_iterator end;
+ LLCullResult::sg_iterator iter = nullptr;
+ LLCullResult::sg_iterator iter_end = nullptr;
+ LLCullResult::sg_iterator rigged_iter = nullptr;
+ LLCullResult::sg_iterator rigged_end = nullptr;
- if (rigged)
+ if (merged || rigged)
{
- begin = gPipeline.beginRiggedAlphaGroups();
- end = gPipeline.endRiggedAlphaGroups();
+ rigged_iter = gPipeline.beginRiggedAlphaGroups();
+ rigged_end = gPipeline.endRiggedAlphaGroups();
}
- else
+
+ if (merged || !rigged)
{
- begin = gPipeline.beginAlphaGroups();
- end = gPipeline.endAlphaGroups();
+ iter = gPipeline.beginAlphaGroups();
+ iter_end = gPipeline.endAlphaGroups();
}
LLEnvironment& env = LLEnvironment::instance();
@@ -587,10 +617,57 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
}
- for (LLCullResult::sg_iterator i = begin; i != end; ++i)
+ // depth-write conditions that apply to every group in this pass; in merged
+ // mode, stamped rigged groups additionally write depth (see below)
+ const bool write_depth_always = LLDrawPoolWater::sSkipScreenCopy ||
+ LLPipeline::sImpostorRenderAlphaDepthPass ||
+ getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER;
+
+ // merged mode: per-group depth-write guard, re-emplaced only when the
+ // write state changes (one glDepthMask per run, not per group)
+ std::optional depth_state;
+ bool depth_state_writes = false;
+
+ while (iter != iter_end || rigged_iter != rigged_end)
{
LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group");
- LLSpatialGroup* group = *i;
+
+ if (merged)
+ { // take the farther of the two stream heads; an ensemble's groups
+ // share one avatar depth, so each avatar drains contiguously --
+ // rigged run first (ties go rigged), then its unrigged attachment
+ // groups, which composite over it
+ if (rigged_iter == rigged_end)
+ {
+ rigged = false;
+ }
+ else if (iter == iter_end)
+ {
+ rigged = true;
+ }
+ else
+ {
+ F32 rigged_depth = (*rigged_iter)->mAvatarDepth;
+ F32 world_depth = (*iter)->worldAlphaDepth();
+ if (rigged_depth != world_depth)
+ {
+ rigged = rigged_depth > world_depth;
+ }
+ else
+ {
+ // equal depth: keep each avatar's ensemble contiguous. Its
+ // own rigged draws before its own unrigged; a plain world
+ // group composites over it (rigged first); two coincident
+ // avatars drain in the sorts' std::less identity order.
+ const LLVOAvatar* world_av = (*iter)->mAvatarp;
+ const LLVOAvatar* rigged_av = (*rigged_iter)->mAvatarp;
+ rigged = !rigged_av || !world_av || world_av == rigged_av ||
+ std::less()(rigged_av, world_av);
+ }
+ }
+ }
+
+ LLSpatialGroup* group = rigged ? *rigged_iter++ : *iter++;
llassert(group);
llassert(group->getSpatialPartition());
@@ -619,6 +696,20 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged)
}
}
+ // merged mode: stamped rigged groups write depth so attachment-order
+ // layering holds; an unstamped group has no defined position in the
+ // rigged order and must not depth-reject geometry behind it (it
+ // still blends). Non-merged passes keep the caller's depth state.
+ if (merged)
+ {
+ bool write_depth = write_depth_always || (rigged && group->mAvatarp != nullptr);
+ if (!depth_state || write_depth != depth_state_writes)
+ {
+ depth_state.emplace(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE);
+ depth_state_writes = write_depth;
+ }
+ }
+
static std::vector emissives;
static std::vector rigged_emissives;
static std::vector pbr_emissives;
diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h
index 25044beda0..571e3f1555 100644
--- a/indra/newview/lldrawpoolalpha.h
+++ b/indra/newview/lldrawpoolalpha.h
@@ -58,13 +58,22 @@ class LLDrawPoolAlpha final: public LLRenderPass
/*virtual*/ void renderPostDeferred(S32 pass);
/*virtual*/ S32 getNumPasses() { return 1; }
- void forwardRender(bool write_depth = false);
+ // which alpha stream(s) a pass draws
+ enum class EAlphaStream
+ {
+ WORLD, // the distance-sorted alpha groups
+ RIGGED, // the rigged alpha groups only (depth-writing prepass)
+ INTERLEAVED // both streams in one back-to-front walk; depth writes
+ // decided per group inside renderAlpha
+ };
+
+ void forwardRender(EAlphaStream stream = EAlphaStream::WORLD);
/*virtual*/ void prerender();
void renderDebugAlpha();
void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, bool texture = true);
- void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false);
+ void renderAlpha(U32 mask, bool depth_only = false, EAlphaStream stream = EAlphaStream::WORLD);
void renderAlphaHighlight();
static bool sShowDebugAlpha;
diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp
index 33325e352f..189d7cd0a8 100644
--- a/indra/newview/lldynamictexture.cpp
+++ b/indra/newview/lldynamictexture.cpp
@@ -103,6 +103,10 @@ void LLViewerDynamicTexture::generateGLTexture(LLGLint internal_format, LLGLenum
createGLTexture(0, raw_image, 0, true, LLGLTexture::DYNAMIC_TEX);
setAddressMode((mClamp) ? LLTexUnit::TAM_CLAMP : LLTexUnit::TAM_WRAP);
mGLTexturep->setGLTextureCreated(false);
+ // Dynamic textures are refreshed via setSubImageFromFrameBuffer(), which
+ // cannot work on a compressed texture (partial/copy updates are illegal
+ // on compressed internal formats -- see LLImageGL::setSubImageFromFrameBuffer).
+ mGLTexturep->setAllowCompression(false);
}
//-----------------------------------------------------------------------------
diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp
index e50f615c7c..ce4095e35d 100644
--- a/indra/newview/llface.cpp
+++ b/indra/newview/llface.cpp
@@ -1180,7 +1180,8 @@ bool LLFace::canRenderAsMask()
return false;
}
- if (te->getGLTFRenderMaterial())
+ static LLCachedControl render_pbr_materials(gSavedSettings, "RenderPBRMaterials", true);
+ if (render_pbr_materials && te->getGLTFRenderMaterial())
{
return false;
}
diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp
index d270364a45..bfd44e88a5 100644
--- a/indra/newview/llfilepicker.cpp
+++ b/indra/newview/llfilepicker.cpp
@@ -65,6 +65,7 @@ LLFilePicker LLFilePicker::sInstance;
#define HDRI_FILTER L"HDRI Files (*.exr)\0*.exr\0"
#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0"
#define SCRIPT_FILTER L"Script files (*.lsl; *.lua; *.luau)\0*.lsl;*.lua;*.luau\0"
+#define NOTECARD_FILTER L"Notecards (*.txt)\0*.txt\0"
#define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0"
#define ZIP_FILTER L"ZIP files (*.zip)\0*.zip\0"
#endif
@@ -230,6 +231,9 @@ namespace
case LLFilePicker::FFLOAD_SCRIPT:
filter_vec.push_back({ "Script files (*.lsl; *.lua; *.luau)", "lsl;lua;luau" });
break;
+ case LLFilePicker::FFLOAD_NOTECARD:
+ filter_vec.push_back({ "Notecards (*.txt)", "txt" });
+ break;
case LLFilePicker::FFLOAD_DICTIONARY:
filter_vec.push_back({ "Dictionary files (*.dic; *.xcu)", "dic;xcu" });
break;
@@ -639,6 +643,10 @@ bool LLFilePicker::setupFilter(ELoadFilter filter)
mOFN.lpstrFilter = SCRIPT_FILTER \
L"\0";
break;
+ case FFLOAD_NOTECARD:
+ mOFN.lpstrFilter = NOTECARD_FILTER \
+ L"\0";
+ break;
case FFLOAD_DICTIONARY:
mOFN.lpstrFilter = DICTIONARY_FILTER \
L"\0";
@@ -1128,6 +1136,9 @@ std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadF
allowedv->push_back("lua");
allowedv->push_back("luau");
break;
+ case FFLOAD_NOTECARD:
+ allowedv->push_back("txt");
+ break;
case FFLOAD_DICTIONARY:
allowedv->push_back("dic");
allowedv->push_back("xcu");
diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h
index f2e2bdce3c..a6d31dc8e8 100644
--- a/indra/newview/llfilepicker.h
+++ b/indra/newview/llfilepicker.h
@@ -80,7 +80,8 @@ class LLFilePicker
FFLOAD_MATERIAL = 15,
FFLOAD_MATERIAL_TEXTURE = 16,
FFLOAD_HDRI = 17,
- FFLOAD_ZIP = 18
+ FFLOAD_ZIP = 18,
+ FFLOAD_NOTECARD = 19
};
enum ESaveFilter
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 81691b65d4..14112d66b9 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -1166,7 +1166,17 @@ void LLMeshRepoThread::run()
}
else
{
- LL_DEBUGS() << "mHeaderReqQ failed: " << req.mMeshParams << LL_ENDL;
+ // too many fails -- can't get the header so none of the LODs will
+ // be available either. Without this, objects waiting on this
+ // header never learn it's gone and stay at their placeholder
+ // shape forever (mirrors LLMeshHeaderHandler::processFailure).
+ LL_WARNS() << "mHeaderReqQ failed too many times: " << req.mMeshParams << " , skip" << LL_ENDL;
+
+ LLMutexLock lock(mLoadedMutex);
+ for (int i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i)
+ {
+ mUnavailableQ.push_back(LODRequest(req.mMeshParams, i));
+ }
}
}
}
diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp
index 77120b93f3..c9470229d7 100644
--- a/indra/newview/llpreviewnotecard.cpp
+++ b/indra/newview/llpreviewnotecard.cpp
@@ -65,6 +65,8 @@
#include "lllineeditor.h"
#include "lluictrlfactory.h"
#include "llviewerassetupload.h"
+#include "llviewermenufile.h"
+#include "llfontgl.h"
///----------------------------------------------------------------------------
/// Class LLPreviewNotecard
@@ -112,6 +114,9 @@ bool LLPreviewNotecard::postBuild()
mEditBtn = getChild("Edit");
mEditBtn->setCommitCallback(boost::bind(&LLPreviewNotecard::openInExternalEditor, this));
+ mLoadBtn = getChild("Load");
+ mLoadBtn->setCommitCallback(boost::bind(&LLPreviewNotecard::loadFromFile, this));
+
const LLInventoryItem* item = getItem();
std::string note_name = mNoteName;
@@ -434,6 +439,7 @@ void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid,
previewEditor->setText(LLStringExplicit(&buffer[0]));
}
+ preview->detectAndApplyRulerWidth();
previewEditor->makePristine();
bool modifiable = preview->canModify(preview->mObjectID, preview->getItem());
preview->setEnabled(modifiable);
@@ -893,6 +899,23 @@ void LLPreviewNotecard::openInExternalEditor()
}
}
+void LLPreviewNotecard::loadFromFile()
+{
+ LLHandle handle = getDerivedHandle();
+ LLFilePickerReplyThread::startPicker(
+ [handle](const std::vector& filenames, LLFilePicker::ELoadFilter, LLFilePicker::ESaveFilter)
+ {
+ LLPreviewNotecard* self = handle.get();
+ if (!self || filenames.empty())
+ {
+ return;
+ }
+ self->loadNotecardText(filenames[0]);
+ },
+ LLFilePicker::FFLOAD_ALL,
+ false);
+}
+
bool LLPreviewNotecard::onExternalChange(const std::string& filename)
{
if (!loadNotecardText(filename))
@@ -905,6 +928,72 @@ bool LLPreviewNotecard::onExternalChange(const std::string& filename)
return true;
}
+// Some notecard authors include a line made of one repeated character,
+// solely to indicate the character width the content was formatted for
+// (word-wrap/ASCII-art alignment assumes that width). Detect the longest
+// such line and, if found, switch to a monospace font and size the
+// floater to match -- the convention is meaningless in a proportional
+// font, since character widths vary.
+void LLPreviewNotecard::detectAndApplyRulerWidth()
+{
+ // Ordinary short dividers (e.g. "-----") are common and are not width
+ // guides; a genuine ruler line spans close to the intended full width.
+ static const size_t RULER_MIN_LENGTH = 20;
+
+ std::string text = mEditor->getText();
+
+ size_t best_len = 0;
+ char best_char = 0;
+ size_t line_start = 0;
+ while (line_start <= text.size())
+ {
+ size_t line_end = text.find('\n', line_start);
+ if (line_end == std::string::npos)
+ {
+ line_end = text.size();
+ }
+
+ size_t len = line_end - line_start;
+ if (len > 0 && !LLStringOps::isSpace(text[line_start]))
+ {
+ char c = text[line_start];
+ bool uniform = true;
+ for (size_t i = 1; i < len; ++i)
+ {
+ if (text[line_start + i] != c)
+ {
+ uniform = false;
+ break;
+ }
+ }
+ if (uniform && len >= RULER_MIN_LENGTH && len > best_len)
+ {
+ best_len = len;
+ best_char = c;
+ }
+ }
+
+ line_start = line_end + 1;
+ }
+
+ if (best_len == 0)
+ {
+ return;
+ }
+
+ LLFontGL* mono_font = LLFontGL::getFontMonospace();
+ mEditor->setFont(mono_font);
+
+ std::string ruler(best_len, best_char);
+ S32 target_text_width = mono_font->getWidth(ruler);
+ S32 width_delta = target_text_width - mEditor->getRect().getWidth();
+ if (width_delta > 0)
+ {
+ LLRect floater_rect = getRect();
+ reshape(floater_rect.getWidth() + width_delta, floater_rect.getHeight());
+ }
+}
+
bool LLPreviewNotecard::loadNotecardText(const std::string& filename)
{
if (filename.empty())
@@ -934,10 +1023,23 @@ bool LLPreviewNotecard::loadNotecardText(const std::string& filename)
fclose(file);
std::string text = std::string(buffer);
+ delete[] buffer;
+
+ // Strip a UTF-8 byte order mark and CR characters (CRLF or lone-CR
+ // line endings) some external editors/OSes add: this may be an
+ // arbitrary file picked off disk, not one of our own tmp-file round
+ // trips. This also applies to the external-editor sync path, which
+ // shares this function.
+ static const std::string utf8_bom("\xEF\xBB\xBF");
+ if (text.compare(0, utf8_bom.size(), utf8_bom) == 0)
+ {
+ text.erase(0, utf8_bom.size());
+ }
+ LLStringUtil::removeCRLF(text);
LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab());
mEditor->setText(text);
- delete[] buffer;
+ detectAndApplyRulerWidth();
return true;
}
diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h
index 2b4884ba3b..e37c3e08ee 100644
--- a/indra/newview/llpreviewnotecard.h
+++ b/indra/newview/llpreviewnotecard.h
@@ -121,9 +121,11 @@ class LLPreviewNotecard : public LLPreview, public LLVOInventoryListener
static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId);
void openInExternalEditor();
+ void loadFromFile();
bool onExternalChange(const std::string& filename);
bool loadNotecardText(const std::string& filename);
bool writeToFile(const std::string& filename);
+ void detectAndApplyRulerWidth();
std::string getCleanNameForTmpFile() const;
std::string getTmpFileName(const std::string& note_name) const;
@@ -132,6 +134,7 @@ class LLPreviewNotecard : public LLPreview, public LLVOInventoryListener
LLLineEditor* mDescEditor = nullptr;
LLButton* mSaveBtn = nullptr;
LLButton* mEditBtn = nullptr;
+ LLButton* mLoadBtn = nullptr;
LLButton* mDeleteBtn = nullptr;
LLUICtrl* mLockBtn = nullptr;
diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp
index 8b5d32b417..359191ac11 100644
--- a/indra/newview/llpreviewscript.cpp
+++ b/indra/newview/llpreviewscript.cpp
@@ -1357,24 +1357,33 @@ void LLScriptEdCore::onBtnLoadFromFile( void* data )
void LLScriptEdCore::loadScriptFromFile(const std::vector& filenames, void* data)
{
+ if (filenames.empty())
+ {
+ return;
+ }
std::string filename = filenames[0];
- llifstream fin(filename.c_str());
-
- std::string line;
- std::string text;
- std::string linetotal;
- while (!fin.eof())
+ // Open in binary mode: this may be an arbitrary file picked off disk
+ // (e.g. authored on another OS), not one of our own tmp-file round
+ // trips, so line-ending translation must be handled explicitly below
+ // rather than left to the stream's platform-default text mode.
+ llifstream fin(filename.c_str(), std::ios::binary);
+ if (!fin.is_open())
{
- getline(fin, line);
- text += line;
- if (!fin.eof())
- {
- text += "\n";
- }
+ return;
}
+
+ std::string text((std::istreambuf_iterator(fin)), std::istreambuf_iterator());
fin.close();
+ // Strip a UTF-8 byte order mark some external editors/OSes add.
+ static const std::string utf8_bom("\xEF\xBB\xBF");
+ if (text.compare(0, utf8_bom.size(), utf8_bom) == 0)
+ {
+ text.erase(0, utf8_bom.size());
+ }
+ LLStringUtil::removeCRLF(text);
+
// Only replace the script if there is something to replace with.
LLScriptEdCore* self = (LLScriptEdCore*)data;
if (self && (text.length() > 0))
diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h
index b3d2c1c541..1eee0d1e54 100644
--- a/indra/newview/llspatialpartition.h
+++ b/indra/newview/llspatialpartition.h
@@ -43,6 +43,7 @@
#include "llvoavatar.h"
#include "llfetchedgltfmaterial.h"
+#include
#include
#define SG_STATE_INHERIT_MASK (OCCLUDED)
@@ -265,19 +266,92 @@ class alignas(16) LLSpatialGroup : public LLOcclusionCullingGroup
}
};
+ // interleaved world-alpha sort key: a stamped attachment's groups sort at
+ // the wearer's avatar depth, everything else at bounds depth
+ F32 worldAlphaDepth() const { return mAvatarp ? mAvatarDepth : mDepth; }
+
+ struct CompareWorldAlphaDepth
+ {
+ bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs)
+ {
+ F32 lhs_depth = lhs->worldAlphaDepth();
+ F32 rhs_depth = rhs->worldAlphaDepth();
+ if (lhs_depth != rhs_depth)
+ {
+ return lhs_depth > rhs_depth;
+ }
+
+ if (lhs->mAvatarp != rhs->mAvatarp)
+ {
+ // at an exact depth tie, drain stamped ensembles before plain
+ // world groups so a null head cannot split every ensemble
+ if (!lhs->mAvatarp)
+ {
+ return false;
+ }
+ if (!rhs->mAvatarp)
+ {
+ return true;
+ }
+ // std::less: raw < on unrelated pointers is unspecified
+ return std::less()(lhs->mAvatarp, rhs->mAvatarp);
+ }
+
+ // groups of one ensemble keep their bounds order among themselves
+ return lhs->mDepth > rhs->mDepth;
+ }
+ };
+
struct CompareRenderOrder
{
bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs)
{
if (lhs->mAvatarp != rhs->mAvatarp)
{
- return lhs->mAvatarp < rhs->mAvatarp;
+ // std::less: raw < on unrelated pointers is unspecified
+ return std::less()(lhs->mAvatarp, rhs->mAvatarp);
}
return lhs->mRenderOrder > rhs->mRenderOrder;
}
};
+ struct CompareDepthRenderOrder
+ {
+ bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs)
+ {
+ // farther avatars draw first; all of an avatar's groups share one
+ // depth, keeping its run contiguous and in attachment order
+ if (lhs->mAvatarDepth != rhs->mAvatarDepth)
+ {
+ return lhs->mAvatarDepth > rhs->mAvatarDepth;
+ }
+
+ if (lhs->mAvatarp != rhs->mAvatarp)
+ {
+ // An unstamped rigged group has no ensemble key; keep it first
+ // at an exact depth tie, matching the merge's legacy fallback.
+ if (!lhs->mAvatarp)
+ {
+ return true;
+ }
+ if (!rhs->mAvatarp)
+ {
+ return false;
+ }
+ return std::less()(lhs->mAvatarp, rhs->mAvatarp);
+ }
+
+ if (lhs->mRenderOrder != rhs->mRenderOrder)
+ {
+ return lhs->mRenderOrder > rhs->mRenderOrder;
+ }
+
+ // bounds depth: stable back-to-front order within one attachment
+ return lhs->mDepth > rhs->mDepth;
+ }
+ };
+
typedef enum
{
GEOM_DIRTY = LLViewerOctreeGroup::INVALID_STATE,
@@ -380,6 +454,9 @@ class alignas(16) LLSpatialGroup : public LLOcclusionCullingGroup
//used by LLVOAVatar to set render order in alpha draw pool to preserve legacy render order behavior
LLVOAvatar* mAvatarp = nullptr;
U32 mRenderOrder = 0;
+ // avatar-ensemble sort key, fanned out from the bridge in
+ // LLPipeline::postSort; mDepth stays bounds-derived
+ F32 mAvatarDepth = 0.f;
// Reflection Probe associated with this node (if any)
LLPointer mReflectionProbe = nullptr;
};
@@ -487,6 +564,14 @@ class LLSpatialBridge : public LLDrawable, public LLSpatialPartition
//transform agent space bounding box into this Spatial Bridge's coordinate frame
void transformExtents(const LLVector4a* src, LLVector4a* dst);
LLDrawable* mDrawable;
+
+ // alpha draw-order stamp, set by LLVOAvatar::idleUpdateMisc /
+ // LLControlAvatar::idleUpdate and fanned out to this bridge's alpha
+ // groups in LLPipeline::postSort. mAvatarp is only ever compared,
+ // never dereferenced (avatar may die first).
+ LLVOAvatar* mAvatarp = nullptr;
+ U32 mRenderOrder = 0;
+ F32 mAvatarDepth = 0.f;
};
class LLCullResult
@@ -789,4 +874,3 @@ extern const F32 SG_MAX_OBJ_RAD;
#endif //LL_LLSPATIALPARTITION_H
-
diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp
index 28c376d350..973431dc5e 100644
--- a/indra/newview/llviewerassetupload.cpp
+++ b/indra/newview/llviewerassetupload.cpp
@@ -353,6 +353,16 @@ bool LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(const std::string& e
asset_type = LLAssetType::AT_ANIMATION;
succ = true;
}
+ else if (exten_lc == "txt")
+ {
+ asset_type = LLAssetType::AT_NOTECARD;
+ succ = true;
+ }
+ else if (exten_lc == "lsl" || exten_lc == "luau")
+ {
+ asset_type = LLAssetType::AT_LSL_TEXT;
+ succ = true;
+ }
return succ;
}
@@ -582,6 +592,61 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile()
}
}
}
+ else if (assetType == LLAssetType::AT_NOTECARD || assetType == LLAssetType::AT_LSL_TEXT)
+ {
+ // Plain text asset types need no transcoding -- just BOM/CRLF
+ // normalization, since this may be an arbitrary file off disk
+ // rather than one of the viewer's own tmp-file round trips.
+ LLFile infile(getFileName(), LLFile::in | LLFile::binary, ec);
+ S64 size = ec ? 0 : infile.size(ec);
+ if (ec || size <= 0)
+ {
+ errorMessage = llformat("Couldn't open file for reading: %s", getFileName().c_str());
+ errorLabel = "ProblemWithFile";
+ error = true;
+ }
+ else
+ {
+ std::vector buf(size + 1);
+ S64 size_read = infile.read(buf.data(), size, ec);
+ if (ec || size_read != size)
+ {
+ errorMessage = llformat("Failed to read file %s", getFileName().c_str());
+ errorLabel = "ProblemWithFile";
+ error = true;
+ }
+ else
+ {
+ buf[size] = '\0';
+ std::string text(buf.data());
+
+ static const std::string utf8_bom("\xEF\xBB\xBF");
+ if (text.compare(0, utf8_bom.size(), utf8_bom) == 0)
+ {
+ text.erase(0, utf8_bom.size());
+ }
+ LLStringUtil::removeCRLF(text);
+
+ LLFile outfile(filename, LLFile::out | LLFile::trunc | LLFile::binary, ec);
+ if (ec)
+ {
+ errorMessage = llformat("Failed saving temporary file for %s", getFileName().c_str());
+ errorLabel = "ProblemWithFile";
+ error = true;
+ }
+ else
+ {
+ outfile.write(text.data(), (S64)text.size(), ec);
+ if (ec)
+ {
+ errorMessage = llformat("Failed saving temporary file for %s", getFileName().c_str());
+ errorLabel = "ProblemWithFile";
+ error = true;
+ }
+ }
+ }
+ }
+ }
else
{
// Unknown extension
diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp
index 677cdd495a..856162e682 100644
--- a/indra/newview/llviewerdisplay.cpp
+++ b/indra/newview/llviewerdisplay.cpp
@@ -223,7 +223,11 @@ void display_update_camera()
}
else if (LLViewerTexture::sDesiredDiscardBias > 2.f)
{
- final_far = llmax(32.f, final_far / (LLViewerTexture::sDesiredDiscardBias - 1.f));
+ static LLCachedControl vram_draw_distance_optimization(gSavedSettings, "ALDrawDistanceVRAMOptimization", true);
+ if (vram_draw_distance_optimization)
+ {
+ final_far = llmax(32.f, final_far / (LLViewerTexture::sDesiredDiscardBias - 1.f));
+ }
}
LLViewerCamera::getInstance()->setFar(final_far);
LLVOAvatar::sRenderDistance = llclamp(final_far, 16.f, 256.f);
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index e4b68b6733..54a3e09b75 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -51,6 +51,7 @@
#include "allegacynotificationwellwindow.h"
#include "fsfloaterposer.h" // [FIRE-30873]: Poser
+#include "fsfloaterposestand.h"
#include "llcommandhandler.h"
#include "llcompilequeue.h"
@@ -611,6 +612,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("webprofile", "floater_al_web_profile.xml", (LLFloaterBuildFunc)&ALFloaterWebProfile::create);
LLFloaterReg::add("fs_poser", "floater_fs_poser.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // [FIRE-30873]: Poser
+ LLFloaterReg::add("fs_posestand", "floater_fs_posestand.xml", (LLFloaterBuildFunc)&LLFloaterReg::build);
// *NOTE: Please keep these alphabetized for easier merges
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 65880c511c..d894c5cce4 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -1553,6 +1553,9 @@ class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
dim = 1 << U32(std::ceil(std::log2(dim)));
LLPointer image_raw = new LLImageRaw(dim,dim,3);
LLPointer tex = LLViewerTextureManager::getLocalTexture(image_raw.get(), true);
+ // Baking uses setSubImageFromFrameBuffer(), which cannot work on a
+ // compressed texture (see LLImageGL::setSubImageFromFrameBuffer).
+ tex->getGLTexture()->setAllowCompression(false);
const bool success = LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(*region, *tex);
// This calls gLocalTerrainMaterials.setPaintType
gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index e5930ba767..0a97e795ce 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -385,6 +385,8 @@ static std::string IMAGE_EXTENSIONS = "tga bmp jpg jpeg png webp avif";
static std::string ANIM_EXTENSIONS = "bvh anim";
static std::string XML_EXTENSIONS = "xml";
static std::string SLOBJECT_EXTENSIONS = "slobject";
+static std::string SCRIPT_EXTENSIONS = "lsl lua luau";
+static std::string NOTECARD_EXTENSIONS = "txt";
#endif
static std::string ALL_FILE_EXTENSIONS = "*.*";
static std::string MODEL_EXTENSIONS = "dae";
@@ -409,6 +411,10 @@ std::string build_extensions_string(LLFilePicker::ELoadFilter filter)
return MATERIAL_EXTENSIONS;
case LLFilePicker::FFLOAD_XML:
return XML_EXTENSIONS;
+ case LLFilePicker::FFLOAD_SCRIPT:
+ return SCRIPT_EXTENSIONS;
+ case LLFilePicker::FFLOAD_NOTECARD:
+ return NOTECARD_EXTENSIONS;
case LLFilePicker::FFLOAD_ALL:
case LLFilePicker::FFLOAD_EXE:
return ALL_FILE_EXTENSIONS;
@@ -704,6 +710,50 @@ void do_bulk_upload(std::vector filenames, bool allow_2k, const LLU
}
}
+// Notecards and scripts have no upload-preview floater (unlike image/sound/anim), and
+// are always free, so this uploads directly on pick -- the same path do_bulk_upload
+// already takes for these two asset types when they show up in a bulk selection.
+void upload_single_notecard_or_script(
+ const std::vector& filenames,
+ LLFilePicker::ELoadFilter type,
+ const LLUUID& dest)
+{
+ std::string filename = filenames[0];
+ if (!check_file_extension(filename, type)) return;
+ if (filename.empty()) return;
+
+ std::string name = gDirUtilp->getBaseFileName(filename, true);
+ std::string asset_name = name;
+ LLStringUtil::replaceNonstandardASCII(asset_name, '?');
+ LLStringUtil::replaceChar(asset_name, '|', '?');
+ LLStringUtil::stripNonprintable(asset_name);
+ LLStringUtil::trim(asset_name);
+
+ std::string ext = gDirUtilp->getExtension(filename);
+ LLAssetType::EType asset_type;
+ U32 codec;
+ S32 expected_upload_cost = 0;
+
+ if (!LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec) ||
+ !LLAgentBenefitsMgr::current().findUploadCost(asset_type, expected_upload_cost))
+ {
+ return;
+ }
+
+ LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared(
+ filename,
+ asset_name,
+ asset_name, 0,
+ LLFolderType::FT_NONE, LLInventoryType::IT_NONE,
+ LLFloaterPerms::getNextOwnerPerms("Uploads"),
+ LLFloaterPerms::getGroupPerms("Uploads"),
+ LLFloaterPerms::getEveryonePerms("Uploads"),
+ expected_upload_cost,
+ dest);
+
+ upload_new_resource(uploadInfo);
+}
+
void do_bulk_upload(std::vector filenames, bool allow_2k, const LLSD& notification, const LLSD& response, const LLUUID& dest)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
@@ -979,6 +1029,32 @@ class LLFileUploadBulk : public view_listener_t
}
};
+class LLFileUploadNotecard : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ if (gAgentCamera.cameraMouselook())
+ {
+ gAgentCamera.changeCameraToDefault();
+ }
+ LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_notecard_or_script, _1, _2, LLUUID::null), LLFilePicker::FFLOAD_NOTECARD, false);
+ return true;
+ }
+};
+
+class LLFileUploadScript : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ if (gAgentCamera.cameraMouselook())
+ {
+ gAgentCamera.changeCameraToDefault();
+ }
+ LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_notecard_or_script, _1, _2, LLUUID::null), LLFilePicker::FFLOAD_SCRIPT, false);
+ return true;
+ }
+};
+
void upload_error(const std::string& error_message, const std::string& label, const std::string& filename, const LLSD& args)
{
LL_WARNS() << error_message << LL_ENDL;
@@ -1480,6 +1556,8 @@ void init_menu_file()
view_listener_t::addCommit(new LLFileUploadModel(), "File.UploadModel");
view_listener_t::addCommit(new LLFileUploadMaterial(), "File.UploadMaterial");
view_listener_t::addCommit(new LLFileUploadBulk(), "File.UploadBulk");
+ view_listener_t::addCommit(new LLFileUploadNotecard(), "File.UploadNotecard");
+ view_listener_t::addCommit(new LLFileUploadScript(), "File.UploadScript");
view_listener_t::addCommit(new LLFileCloseWindow(), "File.CloseWindow");
view_listener_t::addCommit(new LLFileCloseAllWindows(), "File.CloseAllWindows");
view_listener_t::addEnable(new LLFileEnableCloseWindow(), "File.EnableCloseWindow");
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 7334477a8c..3c04e12a22 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -3000,6 +3000,27 @@ static void override_bbox(LLDrawable* drawable, LLVector4a* extents)
drawable->movePartition();
}
+// camera-axis depth of this avatar, stamped onto every rigged alpha bridge
+// (attachments below, animesh in LLControlAvatar::idleUpdate) so its alpha
+// interleaves with world alpha (LLDrawPoolAlpha::renderAlpha). From the
+// animated extents, so avatars sharing a sit target still order
+// deterministically; pulled a quarter of the bounds toward the camera to match
+// the metric LLSpatialPartition::calcDistance stamps onto world alpha groups.
+F32 LLVOAvatar::calcRiggedAlphaDepth() const
+{
+ LLViewerCamera* camera = LLViewerCamera::getInstance();
+ const LLVector3& at_axis = camera->getAtAxis();
+ if (mLastAnimExtents[0] == LLVector3() || mLastAnimExtents[1] == LLVector3())
+ { // extents not computed yet
+ return (getRenderPosition() - camera->getOrigin()) * at_axis;
+ }
+
+ const LLVector3 center = (mLastAnimExtents[0] + mLastAnimExtents[1]) * 0.5f;
+ const LLVector3 half = (mLastAnimExtents[1] - mLastAnimExtents[0]) * 0.5f;
+ return (center - camera->getOrigin()) * at_axis -
+ 0.25f * (half * at_axis.scaledVec(at_axis));
+}
+
void LLVOAvatar::idleUpdateMisc(bool detailed_update)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
@@ -3017,6 +3038,7 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
if (detailed_update)
{
U32 draw_order = 0;
+ const F32 rigged_depth = calcRiggedAlphaDepth();
bool attachment_selected = LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 0 && LLSelectMgr::getInstance()->getSelection()->isAttachment();
for (const auto& [attachment_point_id, attachment] : mAttachmentPoints)
{
@@ -3077,13 +3099,19 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update)
bridge->setState(LLDrawable::MOVE_UNDAMPED);
bridge->updateMove();
bridge->setState(LLDrawable::EARLY_MOVE);
+ }
- LLSpatialGroup* group = attached_object->mDrawable->getSpatialGroup();
- if (group)
- { //set draw order of group
- group->mAvatarp = this;
- group->mRenderOrder = draw_order++;
- }
+ //stamp the attachment's draw order; LLPipeline::postSort
+ //fans it out to the bridge's alpha groups, sorting the
+ //wearer's whole ensemble at one avatar depth. HUD alpha
+ //sorts in HUD space, so unrigged HUDs stay unstamped.
+ //Stale-safe: mUpdatePeriod > 1 implies isImpostor(),
+ //whose attachments never enter the live alpha streams
+ if (rigged || !attached_object->isHUDAttachment())
+ {
+ bridge->mAvatarp = this;
+ bridge->mRenderOrder = draw_order++;
+ bridge->mAvatarDepth = rigged_depth;
}
}
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index 8473b24f4f..2bf7d582aa 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -283,6 +283,7 @@ class LLVOAvatar :
void updateRootPositionAndRotation(LLAgent &agent, F32 speed, bool was_sit_ground_constrained);
void idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position);
+ F32 calcRiggedAlphaDepth() const;
void idleUpdateMisc(bool detailed_update);
virtual void idleUpdateAppearanceAnimation();
void idleUpdateLipSync(bool voice_enabled);
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 6cda8f5709..812f36063e 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -5494,7 +5494,8 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep,
LLUUID mat_id;
- auto* gltf_mat = (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial();
+ static LLCachedControl render_pbr_materials(gSavedSettings, "RenderPBRMaterials", true);
+ auto* gltf_mat = render_pbr_materials ? (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial() : nullptr;
llassert(gltf_mat == nullptr || dynamic_cast(te->getGLTFRenderMaterial()) != nullptr);
if (gltf_mat != nullptr)
{
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index af6fb3283e..cc8c002546 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -2045,7 +2045,8 @@ U32 LLPipeline::getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* ima
}
LLMaterial* mat = te->getMaterialParams().get();
- LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial();
+ static LLCachedControl render_pbr_materials(gSavedSettings, "RenderPBRMaterials", true);
+ LLGLTFMaterial* gltf_mat = render_pbr_materials ? te->getGLTFRenderMaterial() : nullptr;
bool color_alpha = te->getColor().mV[3] < 0.999f;
bool alpha = color_alpha;
@@ -3825,6 +3826,19 @@ void renderSoundHighlights(LLDrawable *drawablep)
}
}
+bool LLPipeline::canUseInterleavedAlpha()
+{
+ static LLCachedControl interleaved_alpha(gSavedSettings, "RenderInterleavedAlpha", true);
+ return interleaved_alpha && !sRenderingHUDs && !sShadowRender && !gCubeSnapshot &&
+ LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD;
+}
+
+void LLPipeline::sortAlphaGroupsForInterleaving()
+{
+ std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareWorldAlphaDepth());
+ std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareDepthRenderOrder());
+}
+
void LLPipeline::postSort(LLCamera &camera)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
@@ -3896,11 +3910,22 @@ void LLPipeline::postSort(LLCamera &camera)
if (hasRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA))
{
+ LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge();
+
+ // fan the attachment's stamp (LLVOAvatar::idleUpdateMisc) out from
+ // the bridge to every visible alpha group of the linkset; the shared
+ // mAvatarDepth keys the wearer's ensemble as one block in the walk
+ if (bridge && bridge->mAvatarp)
+ {
+ group->mAvatarp = bridge->mAvatarp;
+ group->mRenderOrder = bridge->mRenderOrder;
+ group->mAvatarDepth = bridge->mAvatarDepth;
+ }
+
LLSpatialGroup::draw_map_t::iterator alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA);
if (alpha != group->mDrawMap.end())
{ // store alpha groups for sorting
- LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge();
if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot)
{
if (bridge)
@@ -3945,10 +3970,9 @@ void LLPipeline::postSort(LLCamera &camera)
if (!sShadowRender)
{
- // order alpha groups by distance
+ // Legacy order is the baseline for every consumer. Post-water replaces
+ // it with the interleaved order only when it actually uses the merge.
std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater());
-
- // order rigged alpha groups by avatar attachment order
std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder());
}
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index ad58b79901..2b06ca2332 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -363,6 +363,7 @@ class LLPipeline
LLCullResult::sg_iterator endAlphaGroups();
LLCullResult::sg_iterator beginRiggedAlphaGroups();
LLCullResult::sg_iterator endRiggedAlphaGroups();
+ void sortAlphaGroupsForInterleaving();
void addTrianglesDrawn(S32 index_count);
void recordTrianglesDrawn();
@@ -378,6 +379,7 @@ class LLPipeline
bool hasAnyRenderType(const U32 type, ...) const;
static bool isWaterClip();
+ static bool canUseInterleavedAlpha();
void setRenderTypeMask(U32 type, ...);
// This is equivalent to 'setRenderTypeMask'
diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc
index 0e89ab4fb9..20b379e7eb 100644
--- a/indra/newview/res/viewerRes.rc
+++ b/indra/newview/res/viewerRes.rc
@@ -67,7 +67,7 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE
FONT 8, "MS Sans Serif"
BEGIN
ICON IDI_LL_ICON,IDC_STATIC,7,7,20,20
- LTEXT "Loading Alchemy...",666,36,13,211,8
+ LTEXT "Loading Vayu Viewer...",666,36,13,211,8
END
@@ -152,13 +152,13 @@ BEGIN
BEGIN
BLOCK "040904b0"
BEGIN
- VALUE "CompanyName", "Alchemy Development Group"
- VALUE "FileDescription", "Alchemy Viewer"
+ VALUE "CompanyName", "Vayu Viewer Contributors"
+ VALUE "FileDescription", "Vayu Viewer"
VALUE "FileVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}"
- VALUE "InternalName", "Alchemy Viewer"
- VALUE "LegalCopyright", "Copyright (C) 2013-2026 Alchemy Development Group"
- VALUE "OriginalFilename", "AlchemyViewer.exe"
- VALUE "ProductName", "Alchemy Viewer"
+ VALUE "InternalName", "Vayu Viewer"
+ VALUE "LegalCopyright", "Copyright (C) 2026 Vayu Viewer Contributors"
+ VALUE "OriginalFilename", "VayuViewer.exe"
+ VALUE "ProductName", "Vayu Viewer"
VALUE "ProductVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}"
END
END
diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml
index da19a06371..4d12da708f 100644
--- a/indra/newview/skins/default/textures/textures.xml
+++ b/indra/newview/skins/default/textures/textures.xml
@@ -166,6 +166,7 @@ with the same filename but different name
+
diff --git a/indra/newview/skins/default/textures/toolbar_icons/posestand.png b/indra/newview/skins/default/textures/toolbar_icons/posestand.png
index 22c3fbba06..d8f280e425 100644
Binary files a/indra/newview/skins/default/textures/toolbar_icons/posestand.png and b/indra/newview/skins/default/textures/toolbar_icons/posestand.png differ
diff --git a/indra/newview/skins/default/xui/en/floater_fs_posestand.xml b/indra/newview/skins/default/xui/en/floater_fs_posestand.xml
new file mode 100644
index 0000000000..7e52c300d9
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_fs_posestand.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
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 8b8aa95768..10e167e137 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 @@
(requires restart)
+
+
+
+
+