From 17f7b439d617bfdf4c865f1478ade662e86b61d2 Mon Sep 17 00:00:00 2001
From: Alexander Gavriliuk <alexandrgproductengine@lindenlab.com>
Date: Wed, 7 Aug 2024 20:56:10 +0200
Subject: #1922 Make PBR scale and offset crosshair work like Texture crosshair
 do

---
 indra/llrender/llrender.h         |  19 +++--
 indra/llrender/llvertexbuffer.cpp |  12 +++
 indra/llrender/llvertexbuffer.h   |   9 ++-
 indra/newview/llface.cpp          | 116 ++++++++++++++++++++++-----
 indra/newview/llface.h            |   4 +-
 indra/newview/pipeline.cpp        | 159 ++++++++++++++++++++------------------
 indra/newview/pipeline.h          |   1 +
 7 files changed, 210 insertions(+), 110 deletions(-)

diff --git a/indra/llrender/llrender.h b/indra/llrender/llrender.h
index e6b4118beb..010ab122b6 100644
--- a/indra/llrender/llrender.h
+++ b/indra/llrender/llrender.h
@@ -293,15 +293,18 @@ public:
 
     enum eTexIndex : U8
     {
-        DIFFUSE_MAP           = 0,
-        ALTERNATE_DIFFUSE_MAP = 1,
-        NORMAL_MAP            = 1,
-        SPECULAR_MAP          = 2,
-        BASECOLOR_MAP         = 3,
+        // Channels for material textures
+        DIFFUSE_MAP            = 0,
+        ALTERNATE_DIFFUSE_MAP  = 1,
+        NORMAL_MAP             = 1,
+        SPECULAR_MAP           = 2,
+        // Channels for PBR textures
+        BASECOLOR_MAP          = 3,
         METALLIC_ROUGHNESS_MAP = 4,
-        GLTF_NORMAL_MAP           = 5,
-        EMISSIVE_MAP          = 6,
-        NUM_TEXTURE_CHANNELS  = 7,
+        GLTF_NORMAL_MAP        = 5,
+        EMISSIVE_MAP           = 6,
+        // Total number of channels
+        NUM_TEXTURE_CHANNELS   = 7,
     };
 
     enum eVolumeTexIndex : U8
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index 2eb7c21f77..02afcf12c6 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -794,6 +794,18 @@ void LLVertexBuffer::setLabel(const char* label) {
 }
 #endif
 
+void LLVertexBuffer::clone(LLVertexBuffer& target) const
+{
+    target.mTypeMask = mTypeMask;
+    target.mIndicesType = mIndicesType;
+    target.mIndicesStride = mIndicesStride;
+    if (target.getNumVerts() != getNumVerts() ||
+        target.getNumIndices() != getNumIndices())
+    {
+        target.allocateBuffer(getNumVerts(), getNumIndices());
+    }
+}
+
 void LLVertexBuffer::drawRange(U32 mode, U32 start, U32 end, U32 count, U32 indices_offset) const
 {
     llassert(validateRange(start, end, count, indices_offset));
diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h
index 49500e28ce..4ada0c335b 100644
--- a/indra/llrender/llvertexbuffer.h
+++ b/indra/llrender/llvertexbuffer.h
@@ -163,13 +163,13 @@ public:
 
     // set for rendering
     // assumes (and will assert on) the following:
-    //      - this buffer has no pending unampBuffer call
+    //      - this buffer has no pending unmapBuffer call
     //      - a shader is currently bound
     //      - This buffer has sufficient attributes within it to satisfy the needs of the currently bound shader
     void    setBuffer();
 
     // Only call each getVertexPointer, etc, once before calling unmapBuffer()
-    // call unmapBuffer() after calls to getXXXStrider() before any cals to setBuffer()
+    // call unmapBuffer() after calls to getXXXStrider() before any calls to setBuffer()
     // example:
     //   vb->getVertexBuffer(verts);
     //   vb->getNormalStrider(norms);
@@ -218,12 +218,12 @@ public:
     U32 getNumIndices() const               { return mNumIndices; }
 
     U32 getTypeMask() const                 { return mTypeMask; }
-    bool hasDataType(AttributeType type) const      { return ((1 << type) & getTypeMask()); }
+    bool hasDataType(AttributeType type) const { return ((1 << type) & getTypeMask()); }
     U32 getSize() const                     { return mSize; }
     U32 getIndicesSize() const              { return mIndicesSize; }
     U8* getMappedData() const               { return mMappedData; }
     U8* getMappedIndices() const            { return mMappedIndexData; }
-    U32 getOffset(AttributeType type) const         { return mOffsets[type]; }
+    U32 getOffset(AttributeType type) const { return mOffsets[type]; }
 
     // these functions assume (and assert on) the current VBO being bound
     // Detailed error checking can be enabled by setting gDebugGL to true
@@ -242,6 +242,7 @@ public:
     void setLabel(const char* label);
     #endif
 
+    void clone(LLVertexBuffer& target) const;
 
 protected:
     U32     mGLBuffer = 0;      // GL VBO handle
diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp
index f73c530ff9..d14a7ffddc 100644
--- a/indra/newview/llface.cpp
+++ b/indra/newview/llface.cpp
@@ -514,7 +514,7 @@ void LLFace::renderSelected(LLViewerTexture *imagep, const LLColor4& color)
     mDrawablep->getSpatialGroup()->rebuildGeom();
     mDrawablep->getSpatialGroup()->rebuildMesh();
 
-    if(mVertexBuffer.isNull())
+    if (mVertexBuffer.isNull())
     {
         return;
     }
@@ -567,8 +567,20 @@ void LLFace::renderSelected(LLViewerTexture *imagep, const LLColor4& color)
         {
             // cheaters sometimes prosper...
             //
-            mVertexBuffer->setBuffer();
-            mVertexBuffer->draw(LLRender::TRIANGLES, mIndicesCount, mIndicesIndex);
+            LLVertexBuffer* vertex_buffer = mVertexBuffer.get();
+            // To display selection markers (white squares with the rounded cross at the center)
+            // on faces with GLTF textures we use a spectal vertex buffer with other transforms
+            if (const LLTextureEntry* te = getTextureEntry())
+            {
+                if (LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial())
+                {
+                    vertex_buffer = mVertexBufferGLTF.get();
+                    vertex_buffer->unmapBuffer();
+                }
+            }
+            // Draw the selection marker using the correctly chosen vertex buffer
+            vertex_buffer->setBuffer();
+            vertex_buffer->draw(LLRender::TRIANGLES, mIndicesCount, mIndicesIndex);
         }
 
         gGL.popMatrix();
@@ -1144,7 +1156,8 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
                                 const LLMatrix3& mat_norm_in,
                                 U16 index_offset,
                                 bool force_rebuild,
-                                bool no_debug_assert)
+                                bool no_debug_assert,
+                                bool rebuild_for_gltf)
 {
     LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE;
     llassert(verify());
@@ -1202,6 +1215,58 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
         }
     }
 
+    const LLTextureEntry* tep = mVObjp->getTE(face_index);
+    llassert(tep);
+    if (!tep)
+        return false;
+
+    LLGLTFMaterial* gltf_mat = tep->getGLTFRenderMaterial();
+    // To display selection markers (white squares with the rounded cross at the center)
+    // on faces with GLTF textures we use a special vertex buffer with other transforms
+    if (gltf_mat && !rebuild_for_gltf && tep->isSelected() && mVertexBuffer.notNull())
+    {
+        // Create a temporary vertex buffer to provide transforms for GLTF textures
+        if (mVertexBufferGLTF.isNull())
+        {
+            mVertexBufferGLTF = new LLVertexBuffer(mVertexBuffer->getTypeMask());
+        }
+
+        // Clone the existing vertex buffer into the temporary one
+        mVertexBuffer->clone(*mVertexBufferGLTF);
+
+        // Recursive call the same function with the argument rebuild_for_gltf set to true
+        // This call will make geometry in mVertexBuffer but in fact for mVertexBufferGLTF
+        mVertexBufferGLTF.swap(mVertexBufferGLTF, mVertexBuffer);
+        getGeometryVolume(volume, face_index, mat_vert_in, mat_norm_in, index_offset, force_rebuild, no_debug_assert, true);
+        mVertexBufferGLTF.swap(mVertexBufferGLTF, mVertexBuffer);
+    }
+    else if (!tep->isSelected() && mVertexBufferGLTF.notNull())
+    {
+        // Free the temporary vertex buffer when it is not needed anymore
+        mVertexBufferGLTF = nullptr;
+    }
+
+    LLGLTFMaterial::TextureInfo gltf_info_index = (LLGLTFMaterial::TextureInfo)0;
+    if (gltf_mat && rebuild_for_gltf)
+    {
+        switch (LLPipeline::sRenderHighlightTextureChannel)
+        {
+        case LLRender::BASECOLOR_MAP:
+            gltf_info_index = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR;
+            break;
+        case LLRender::METALLIC_ROUGHNESS_MAP:
+            gltf_info_index = LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS;
+            break;
+        case LLRender::GLTF_NORMAL_MAP:
+            gltf_info_index = LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL;
+            break;
+        case LLRender::EMISSIVE_MAP:
+            gltf_info_index = LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE;
+            break;
+        default:; // just to make clang happy
+        }
+    }
+
     LLStrider<LLVector3> vert;
     LLStrider<LLVector2> tex_coords0;
     LLStrider<LLVector2> tex_coords1;
@@ -1218,7 +1283,7 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
     LLVector3 scale;
     if (global_volume)
     {
-        scale.setVec(1,1,1);
+        scale.setVec(1, 1, 1);
     }
     else
     {
@@ -1233,7 +1298,6 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
     bool rebuild_tangent = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TANGENT);
     bool rebuild_weights = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_WEIGHT4);
 
-    const LLTextureEntry *tep = mVObjp->getTE(face_index);
     const U8 bump_code = tep ? tep->getBumpmap() : 0;
 
     bool is_static = mDrawablep->isStatic();
@@ -1323,7 +1387,6 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
 
 
     LLMaterial* mat = tep->getMaterialParams().get();
-    LLGLTFMaterial* gltf_mat = tep->getGLTFRenderMaterial();
 
     F32 r = 0, os = 0, ot = 0, ms = 0, mt = 0, cos_ang = 0, sin_ang = 0;
 
@@ -1334,13 +1397,27 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
 
     S32 xforms = XFORM_NONE;
     // For GLTF, transforms will be applied later
-    if (rebuild_tcoord && tep && !gltf_mat)
+    if (rebuild_tcoord && tep && (!gltf_mat || rebuild_for_gltf))
     {
-        r  = tep->getRotation();
-        os = tep->mOffsetS;
-        ot = tep->mOffsetT;
-        ms = tep->mScaleS;
-        mt = tep->mScaleT;
+        if (gltf_mat && rebuild_for_gltf)
+        {
+            // Apply special transformations for mVertexBufferGLTF
+            // They are used only to display a face selection marker
+            // (white square with a rounded cross at the center)
+            const auto& tt = gltf_mat->mTextureTransform[gltf_info_index];
+            r = -tt.mRotation * 2;
+            ms = tt.mScale[VX];
+            mt = tt.mScale[VY];
+            os += tt.mOffset[VX] + (ms - 1) / 2;
+            ot -= tt.mOffset[VY] + (mt - 1) / 2;
+        }
+        else
+        {
+            r = tep->getRotation();
+            tep->getOffset(&os, &ot);
+            tep->getScale(&ms, &mt);
+        }
+
         cos_ang = cos(r);
         sin_ang = sin(r);
 
@@ -1481,12 +1558,9 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
                     break;
                 }
 
-                F32 s_scale = 1.f;
-                F32 t_scale = 1.f;
-                if( tep )
-                {
-                    tep->getScale( &s_scale, &t_scale );
-                }
+                F32 s_scale = tep->getScaleS();
+                F32 t_scale = tep->getScaleT();
+
                 // Use the nudged south when coming from above sun angle, such
                 // that emboss mapping always shows up on the upward faces of cubes when
                 // it's noon (since a lot of builders build with the sun forced to noon).
@@ -1508,8 +1582,8 @@ bool LLFace::getGeometryVolume(const LLVolume& volume,
 
             bool tex_anim = false;
 
-                LLVOVolume* vobj = (LLVOVolume*) (LLViewerObject*) mVObjp;
-                tex_mode = vobj->mTexAnimMode;
+            LLVOVolume* vobj = (LLVOVolume*)mVObjp.get();
+            tex_mode = vobj->mTexAnimMode;
 
             if (vobj->mTextureAnimp)
             { //texture animation is in play, override specular and normal map tex coords with diffuse texcoords
diff --git a/indra/newview/llface.h b/indra/newview/llface.h
index 917f3aa0b2..7cf256f731 100644
--- a/indra/newview/llface.h
+++ b/indra/newview/llface.h
@@ -161,7 +161,8 @@ public:
                             const LLMatrix3& mat_normal,
                             U16 index_offset,
                             bool force_rebuild = false,
-                            bool no_debug_assert = false);
+                            bool no_debug_assert = false,
+                            bool rebuild_for_gltf = false);
 
     // For avatar
     U16          getGeometryAvatar(
@@ -266,6 +267,7 @@ public:
 
 private:
     LLPointer<LLVertexBuffer> mVertexBuffer;
+    LLPointer<LLVertexBuffer> mVertexBufferGLTF;
 
     U32         mState;
     LLFacePool* mDrawPoolp;
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 2a461ca84b..1b2cffa6c9 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -3580,9 +3580,12 @@ void LLPipeline::postSort(LLCamera &camera)
     {
         mSelectedFaces.clear();
 
+        bool tex_index_changed = false;
         if (!gNonInteractive)
         {
-            LLPipeline::setRenderHighlightTextureChannel(gFloaterTools->getPanelFace()->getTextureChannelToEdit());
+            LLRender::eTexIndex tex_index = sRenderHighlightTextureChannel;
+            setRenderHighlightTextureChannel(gFloaterTools->getPanelFace()->getTextureChannelToEdit());
+            tex_index_changed = sRenderHighlightTextureChannel != tex_index;
         }
 
         // Draw face highlights for selected faces.
@@ -3604,6 +3607,24 @@ void LLPipeline::postSort(LLCamera &camera)
                 }
             } func;
             LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func);
+
+            if (tex_index_changed)
+            {
+                // Rebuild geometry for all selected faces with PBR textures
+                for (const LLFace* face : gPipeline.mSelectedFaces)
+                {
+                    if (const LLViewerObject* vobj = face->getViewerObject())
+                    {
+                        if (const LLTextureEntry* tep = vobj->getTE(face->getTEOffset()))
+                        {
+                            if (tep->getGLTFRenderMaterial())
+                            {
+                                gPipeline.markRebuild(face->getDrawable(), LLDrawable::REBUILD_VOLUME);
+                            }
+                        }
+                    }
+                }
+            }
         }
     }
 
@@ -3656,28 +3677,31 @@ void render_hud_elements()
     gUIProgram.unbind();
 }
 
-void LLPipeline::renderHighlights()
+static inline void bindHighlightProgram(LLGLSLShader& program)
 {
-    assertInitialized();
-
-    // Draw 3D UI elements here (before we clear the Z buffer in POOL_HUD)
-    // Render highlighted faces.
-    LLGLSPipelineAlpha gls_pipeline_alpha;
-    LLColor4 color(1.f, 1.f, 1.f, 0.5f);
-    disableLights();
-
     if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0))
     {
-        gHighlightProgram.bind();
-        gGL.diffuseColor4f(1,1,1,0.5f);
+        program.bind();
+        gGL.diffuseColor4f(1, 1, 1, 0.5f);
     }
+}
 
-    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && !mFaceSelectImagep)
-        {
-            mFaceSelectImagep = LLViewerTextureManager::getFetchedTexture(IMG_FACE_SELECT);
-        }
+static inline void unbindHighlightProgram(LLGLSLShader& program)
+{
+    if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)
+    {
+        program.unbind();
+    }
+}
 
-    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::DIFFUSE_MAP))
+void LLPipeline::renderSelectedFaces(const LLColor4& color)
+{
+    if (!mFaceSelectImagep)
+    {
+        mFaceSelectImagep = LLViewerTextureManager::getFetchedTexture(IMG_FACE_SELECT);
+    }
+
+    if (mFaceSelectImagep)
     {
         // Make sure the selection image gets downloaded and decoded
         mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA);
@@ -3693,81 +3717,61 @@ void LLPipeline::renderHighlights()
             facep->renderSelected(mFaceSelectImagep, color);
         }
     }
+}
 
-    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED))
-    {
-        // Paint 'em red!
-        color.setVec(1.f, 0.f, 0.f, 0.5f);
-
-        for (auto facep : mHighlightFaces)
-        {
-            facep->renderSelected(LLViewerTexture::sNullImagep, color);
-        }
-    }
+void LLPipeline::renderHighlights()
+{
+    assertInitialized();
 
-    // Contains a list of the faces of objects that are physical or
-    // have touch-handlers.
-    mHighlightFaces.clear();
+    // Draw 3D UI elements here (before we clear the Z buffer in POOL_HUD)
+    // Render highlighted faces.
+    LLGLSPipelineAlpha gls_pipeline_alpha;
+    disableLights();
 
-    if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)
+    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED))
     {
-        gHighlightProgram.unbind();
-    }
-
+        bindHighlightProgram(gHighlightProgram);
 
-    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::NORMAL_MAP))
-    {
-        color.setVec(1.0f, 0.5f, 0.5f, 0.5f);
-        if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0))
+        if (sRenderHighlightTextureChannel == LLRender::DIFFUSE_MAP ||
+            sRenderHighlightTextureChannel == LLRender::BASECOLOR_MAP ||
+            sRenderHighlightTextureChannel == LLRender::METALLIC_ROUGHNESS_MAP ||
+            sRenderHighlightTextureChannel == LLRender::GLTF_NORMAL_MAP ||
+            sRenderHighlightTextureChannel == LLRender::EMISSIVE_MAP ||
+            sRenderHighlightTextureChannel == LLRender::NUM_TEXTURE_CHANNELS)
         {
-            gHighlightNormalProgram.bind();
-            gGL.diffuseColor4f(1,1,1,0.5f);
+            static const LLColor4 highlight_selected_color(1.f, 1.f, 1.f, 0.5f);
+            renderSelectedFaces(highlight_selected_color);
         }
 
-        mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA);
-
-        for (auto facep : mSelectedFaces)
+        // Paint 'em red!
+        static const LLColor4 highlight_face_color(1.f, 0.f, 0.f, 0.5f);
+        for (auto facep : mHighlightFaces)
         {
-            if (!facep || facep->getDrawable()->isDead())
-            {
-                LL_ERRS() << "Bad face on selection" << LL_ENDL;
-                return;
-            }
-
-            facep->renderSelected(mFaceSelectImagep, color);
+            facep->renderSelected(LLViewerTexture::sNullImagep, highlight_face_color);
         }
 
-        if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0))
-        {
-            gHighlightNormalProgram.unbind();
-        }
+        unbindHighlightProgram(gHighlightProgram);
     }
 
-    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::SPECULAR_MAP))
-    {
-        color.setVec(0.0f, 0.3f, 1.0f, 0.8f);
-        if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0))
-        {
-            gHighlightSpecularProgram.bind();
-            gGL.diffuseColor4f(1,1,1,0.5f);
-        }
-
-        mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA);
+    // Contains a list of the faces of objects that are physical or
+    // have touch-handlers.
+    mHighlightFaces.clear();
 
-        for (auto facep : mSelectedFaces)
+    if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED))
+    {
+        if (sRenderHighlightTextureChannel == LLRender::NORMAL_MAP)
         {
-            if (!facep || facep->getDrawable()->isDead())
-            {
-                LL_ERRS() << "Bad face on selection" << LL_ENDL;
-                return;
-            }
-
-            facep->renderSelected(mFaceSelectImagep, color);
+            static const LLColor4 highlight_normal_color(1.0f, 0.5f, 0.5f, 0.5f);
+            bindHighlightProgram(gHighlightNormalProgram);
+            renderSelectedFaces(highlight_normal_color);
+            unbindHighlightProgram(gHighlightNormalProgram);
         }
-
-        if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0))
+        else if (sRenderHighlightTextureChannel == LLRender::SPECULAR_MAP)
         {
-            gHighlightSpecularProgram.unbind();
+            static const LLColor4 highlight_specular_color(0.0f, 0.3f, 1.0f, 0.8f);
+            bindHighlightProgram(gHighlightSpecularProgram);
+            renderSelectedFaces(highlight_specular_color);
+            unbindHighlightProgram(gHighlightSpecularProgram);
         }
     }
 }
@@ -6256,7 +6260,10 @@ bool LLPipeline::getRenderHighlights()
 // static
 void LLPipeline::setRenderHighlightTextureChannel(LLRender::eTexIndex channel)
 {
-    sRenderHighlightTextureChannel = channel;
+    if (channel != sRenderHighlightTextureChannel)
+    {
+        sRenderHighlightTextureChannel = channel;
+    }
 }
 
 LLVOPartGroup* LLPipeline::lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection,
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 6c79d20149..4164474238 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -342,6 +342,7 @@ public:
     void renderHighlight(const LLViewerObject* obj, F32 fade);
 
     void renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& camera, LLCullResult& result, bool depth_clamp);
+    void renderSelectedFaces(const LLColor4& color);
     void renderHighlights();
     void renderDebug();
     void renderPhysicsDisplay();
-- 
cgit v1.2.3