Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions indra/newview/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ if(AL_BUILD_VIEWER)
alassetblocklist.cpp
alavataractions.cpp
alavatargroups.cpp
albakeproxies.cpp
alchatautocomplete.cpp
alchatbar.cpp
alchatcommand.cpp
Expand Down Expand Up @@ -1011,6 +1012,7 @@ if(AL_BUILD_VIEWER)
alassetblocklist.h
alavataractions.h
alavatargroups.h
albakeproxies.h
alchatautocomplete.h
alchatbar.h
alchatcommand.h
Expand Down Expand Up @@ -2535,6 +2537,7 @@ if(AL_BUILD_TESTS)
PROJECT ${test_project}
SOURCES
tests/alagentlocationformat_test.cpp
tests/albakeproxies_test.cpp
tests/alchattimestamp_test.cpp
tests/alcolorwheelmodel_test.cpp
tests/alcrashreporter_test.cpp
Expand Down Expand Up @@ -2609,6 +2612,7 @@ if(AL_BUILD_TESTS)
tests/noise_test.cpp
UNIT
alagentlocationformat.cpp
albakeproxies.cpp
alchattimestamp.cpp
alcolorwheelmodel.cpp
alcrashreporter.cpp
Expand Down
69 changes: 69 additions & 0 deletions indra/newview/albakeproxies.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @file albakeproxies.cpp
* @brief The bakes a face names through bake proxies (IMG_USE_BAKED_*)
*
* Copyright (c) 2026, Rye Mutt <rye@alchemyviewer.org>
*
* The source code in this file is provided to you under the terms of the
* GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
* in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
*
*/

#include "linden_common.h"

#include "albakeproxies.h"

#include "llgltfmaterial.h"
#include "lltextureentry.h"

using namespace LLAvatarAppearanceDefines;

namespace
{
// The bake a texture ID names, where it is a bake proxy
ALBakeProxies::bake_set_t named_by(const LLUUID& id)
{
ALBakeProxies::bake_set_t bakes;
const EBakedTextureIndex index = LLAvatarAppearanceDictionary::assetIdToBakedTextureIndex(id);
if (index != BAKED_NUM_INDICES)
{
bakes.set(index);
}
return bakes;
}
}

namespace ALBakeProxies
{

bool anyNamedBy(const LLGLTFMaterial& material)
{
for (const LLUUID& id : material.mTextureId)
{
if (LLAvatarAppearanceDictionary::isBakedImageId(id))
{
return true;
}
}
return false;
}

bake_set_t drawnBy(const LLGLTFMaterial* material)
{
return material ? named_by(material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) : bake_set_t();
}

bake_set_t drawnBy(const LLTextureEntry& te, bool hud)
{
bake_set_t bakes = named_by(te.getID());
if (!hud)
{
bakes |= drawnBy(te.getGLTFRenderMaterial());
}
return bakes;
}

}
45 changes: 45 additions & 0 deletions indra/newview/albakeproxies.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @file albakeproxies.h
* @brief The bakes a face names through bake proxies (IMG_USE_BAKED_*): those an avatar wearing it hides
* the system body parts of, and whether its PBR material resolves one for its own avatar.
*
* Copyright (c) 2026, Rye Mutt <rye@alchemyviewer.org>
*
* The source code in this file is provided to you under the terms of the
* GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
* in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
*
*/
#ifndef AL_BAKEPROXIES_H
#define AL_BAKEPROXIES_H

#include "llavatarappearancedefines.h"

#include <bitset>

class LLGLTFMaterial;
class LLTextureEntry;

namespace ALBakeProxies
{
// A set of bakes, by EBakedTextureIndex
typedef std::bitset<LLAvatarAppearanceDefines::BAKED_NUM_INDICES> bake_set_t;

// Whether any slot of a PBR material names a bake proxy, which the face drawing it resolves for its own
// object's avatar, and so draws from a copy of its own (LLViewerObject::initRenderMaterial)
bool anyNamedBy(const LLGLTFMaterial& material);

// The bake a PBR material draws a body with: the one its base colour names, the slot that stands for a
// face's image. A bake in its other slots shades the surface without drawing a body
bake_set_t drawnBy(const LLGLTFMaterial* material);

// The bakes a face draws a body with, whose system body parts an avatar wearing it hides
// (LLVOAvatar::updateMeshVisibility): its image's and its PBR material's. A HUD's face resolves no bake
// (LLViewerObject::getBakedTextureForMagicId): its image counts, as Linden's viewer counts it, and its
// material does not
bake_set_t drawnBy(const LLTextureEntry& te, bool hud);
}

#endif // AL_BAKEPROXIES_H
2 changes: 1 addition & 1 deletion indra/newview/alselectionoutline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,7 @@ ALSelectionOutline::FaceBinding ALSelectionOutline::bindFace(LLGLSLShader& progr
// LLFetchedGLTFMaterial::bind in its place.
if (!face->hasMedia() || !texture || texture->getType() != LLViewerTexture::MEDIA_TEXTURE)
{
texture = gltf->mBaseColorTexture.get();
texture = gltf->getRenderTexture(LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR);
}
if (texture && texture_has_alpha(texture))
{
Expand Down
16 changes: 12 additions & 4 deletions indra/newview/alviewermenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llavatarpropertiesprocessor.h"
#include "llfetchedgltfmaterial.h"
#include "llhudobject.h"
#include "llnotifications.h"
#include "llnotificationsutil.h"
Expand All @@ -48,6 +49,7 @@
#include "llviewerobject.h"
#include "llviewerobjectlist.h"
#include "llviewerregion.h"
#include "llviewertexturelist.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"

Expand Down Expand Up @@ -381,7 +383,9 @@ namespace
if (id.isNull() || id == IMG_DEFAULT
|| id == IMG_TRANSPARENT|| id == "8dcd4a48-2d37-4909-9f78-f7a9eb4ef903")
return;
LLViewerFetchedTexture* texture = LLViewerTextureManager::getFetchedTexture(id);
// Looked up, not created: an ID no fetched texture has, such as a dynamic texture's, has
// nothing to clear, and creating one would start a download nothing draws
LLViewerFetchedTexture* texture = gTextureList.findImage(id, TEX_LIST_STANDARD);
if (texture)
texture->clearFetchedResults();
LLAppViewer::getTextureCache()->removeFromCache(id);
Expand Down Expand Up @@ -412,12 +416,16 @@ namespace
faces_per_tex[spec_img->getID()].push_back(i);
}

LLPointer<LLGLTFMaterial> mat = node->getObject()->getTE(i)->getGLTFRenderMaterial();
if (mat.notNull())
// What each slot draws, as getTEImage is for the image: a bake proxy's bake, not the
// proxy's own ID. A copy of the material not yet built since it was made
// (initRenderMaterial) draws nothing yet, and its slots' own IDs are what it will draw
const auto* mat = static_cast<const LLFetchedGLTFMaterial*>(node->getObject()->getTE(i)->getGLTFRenderMaterial());
if (mat)
{
for (U32 j = 0; j < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++j)
{
faces_per_tex[mat->mTextureId[j]].push_back(i);
const LLViewerTexture* tex = mat->getRenderTexture((LLGLTFMaterial::TextureInfo)j);
faces_per_tex[tex ? tex->getID() : mat->mTextureId[j]].push_back(i);
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions indra/newview/lldrawpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1220,7 +1220,8 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps map
// vs data split LLFetchedGLTFMaterial::bind makes, and it has to match: a batched and
// an unbatched copy of the same material must sample identically, or they shade
// differently for no reason the content author can see.
LLViewerTexture* base = mat->mBaseColorTexture.notNull() ? mat->mBaseColorTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get();
LLViewerTexture* base = mat->getRenderTexture(LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR);
base = base ? base : LLViewerFetchedTexture::sWhiteImagep.get();
gGL.getTextureSlot(s)->bindFast(base, ALSamplers::AnisoWrapSRGB);

min_alpha[s] = (mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) ? mat->mAlphaCutoff : -1.f;
Expand All @@ -1236,7 +1237,8 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps map
}

// emissive map/color/transform -- needed by both the glow and GBuffer passes
LLViewerTexture* em = mat->mEmissiveTexture.notNull() ? mat->mEmissiveTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get();
LLViewerTexture* em = mat->getRenderTexture(LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE);
em = em ? em : LLViewerFetchedTexture::sWhiteImagep.get();
gGL.getTextureSlot(3 * N + s)->bindFast(em, ALSamplers::AnisoWrapSRGB);

emissive[3 * s + 0] = mat->mEmissiveColor.mV[0];
Expand All @@ -1254,8 +1256,10 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps map
continue;
}

LLViewerTexture* norm = (mat->mNormalTexture.notNull() && mat->mNormalTexture->getDiscardLevel() <= 4) ? mat->mNormalTexture.get() : LLViewerFetchedTexture::sFlatNormalImagep.get();
LLViewerTexture* orm = mat->mMetallicRoughnessTexture.notNull() ? mat->mMetallicRoughnessTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get();
LLViewerTexture* norm = mat->getRenderTexture(LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL);
norm = (norm && norm->getDiscardLevel() <= 4) ? norm : LLViewerFetchedTexture::sFlatNormalImagep.get();
LLViewerTexture* orm = mat->getRenderTexture(LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS);
orm = orm ? orm : LLViewerFetchedTexture::sWhiteImagep.get();

gGL.getTextureSlot(N + s)->bindFast(norm, ALSamplers::AnisoWrap);
gGL.getTextureSlot(2 * N + s)->bindFast(orm, ALSamplers::AnisoWrap);
Expand Down
16 changes: 10 additions & 6 deletions indra/newview/llfetchedgltfmaterial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ LLFetchedGLTFMaterial& LLFetchedGLTFMaterial::operator=(const LLFetchedGLTFMater
mNormalTexture = rhs.mNormalTexture;
mMetallicRoughnessTexture = rhs.mMetallicRoughnessTexture;
mEmissiveTexture = rhs.mEmissiveTexture;
std::copy(std::begin(rhs.mBakedTextures), std::end(rhs.mBakedTextures), std::begin(mBakedTextures));

return *this;
}
Expand Down Expand Up @@ -95,8 +96,8 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex)
LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;

// override emissive and base color textures with media tex if present
LLViewerTexture* baseColorTex = media_tex ? media_tex : mBaseColorTexture;
LLViewerTexture* emissiveTex = media_tex ? media_tex : mEmissiveTexture;
LLViewerTexture* baseColorTex = media_tex ? media_tex : getRenderTexture(GLTF_TEXTURE_INFO_BASE_COLOR);
LLViewerTexture* emissiveTex = media_tex ? media_tex : getRenderTexture(GLTF_TEXTURE_INFO_EMISSIVE);

// sImpostorRender is checked alongside sShadowRender throughout this function because
// generateImpostor sets BOTH, and only one of them means "depth-only". An impostor bake
Expand Down Expand Up @@ -136,18 +137,20 @@ void LLFetchedGLTFMaterial::bind(LLViewerTexture* media_tex)

if (!depth_only_pass)
{
if (mNormalTexture.notNull() && mNormalTexture->getDiscardLevel() <= 4)
LLViewerTexture* normalTex = getRenderTexture(GLTF_TEXTURE_INFO_NORMAL);
if (normalTex != nullptr && normalTex->getDiscardLevel() <= 4)
{
shader->bindTexture(LLShaderMgr::BUMP_MAP, mNormalTexture, GLTF_DATA_SAMPLER);
shader->bindTexture(LLShaderMgr::BUMP_MAP, normalTex, GLTF_DATA_SAMPLER);
}
else
{
shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep, GLTF_DATA_SAMPLER);
}

if (mMetallicRoughnessTexture.notNull())
LLViewerTexture* metallicRoughnessTex = getRenderTexture(GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS);
if (metallicRoughnessTex != nullptr)
{
shader->bindTexture(LLShaderMgr::SPECULAR_MAP, mMetallicRoughnessTexture, GLTF_DATA_SAMPLER); // PBR linear packed Occlusion, Roughness, Metal.
shader->bindTexture(LLShaderMgr::SPECULAR_MAP, metallicRoughnessTex, GLTF_DATA_SAMPLER); // PBR linear packed Occlusion, Roughness, Metal.
}
else
{
Expand Down Expand Up @@ -269,6 +272,7 @@ void LLFetchedGLTFMaterial::clearFetchedTextures()
mNormalTexture = nullptr;
mMetallicRoughnessTexture = nullptr;
mEmissiveTexture = nullptr;
std::fill(std::begin(mBakedTextures), std::end(mBakedTextures), nullptr);
}

void LLFetchedGLTFMaterial::materialBegin()
Expand Down
24 changes: 24 additions & 0 deletions indra/newview/llfetchedgltfmaterial.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ class LLFetchedGLTFMaterial: public LLGLTFMaterial
LLPointer<LLViewerFetchedTexture> mNormalTexture;
LLPointer<LLViewerFetchedTexture> mMetallicRoughnessTexture;
LLPointer<LLViewerFetchedTexture> mEmissiveTexture;

// What a slot naming a bake proxy (IMG_USE_BAKED_*) resolved to on the face this render
// material is drawn on (LLViewerObject::updateTEMaterialTextures, which fetches nothing for
// it): the server's bake, or the local composite while the avatar edits its appearance, which
// is no fetched texture and so has no place in the slots above. Null for any other slot.
LLPointer<LLViewerTexture> mBakedTextures[GLTF_TEXTURE_INFO_COUNT];

// The texture a pass draws a slot with: its bake, else its fetched texture
LLViewerTexture* getRenderTexture(TextureInfo slot) const
{
if (mBakedTextures[slot].notNull())
{
return mBakedTextures[slot];
}
switch (slot)
{
case GLTF_TEXTURE_INFO_BASE_COLOR: return mBaseColorTexture;
case GLTF_TEXTURE_INFO_NORMAL: return mNormalTexture;
case GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS: return mMetallicRoughnessTexture;
case GLTF_TEXTURE_INFO_EMISSIVE: return mEmissiveTexture;
default: return nullptr;
}
}

void clearFetchedTextures();

std::set<LLTextureEntry*> mTextureEntires;
Expand Down
Loading
Loading