Skip to content
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ repos:
- id: mixed-line-ending
- id: trailing-whitespace
files: \.(cpp|c|m|mm|h|inl|py|glsl|cmake|yaml|sh)$
- repo: local
hooks:
- id: raw-gl-newview
name: Raw GL calls in newview
entry: python scripts/code_tools/check_raw_gl.py
language: python
files: ^indra/newview/.*\.(c|cc|cpp|cxx|h|hh|hpp|inl|m|mm)$
exclude: ^indra/newview/tests/
- id: raw-gl-baseline
name: Raw GL baseline
entry: python scripts/code_tools/check_raw_gl.py
language: python
files: ^scripts/code_tools/(check_raw_gl\.py|raw_gl_baseline\.txt)$
pass_filenames: false
2 changes: 1 addition & 1 deletion doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
- **alscript** - Script Studio's language services, free of UI (depends on llcommon, llmath, and SLua's Luau with the Tailslide it carries). One folder a part, each on the include path: `core/` (problems, symbols, the lexicon, messages, weights, the formatter), `lint/` (the studio's lints and fixes), `lsl/` (the LSL analyzer over Tailslide, the SLua converter, and `ALLSLDepth`, the bound on how deep a parsed tree may go for any of them to walk it), `lsl/optimizer/` (the LSL optimizer, a pass a file, sharing `allsloptimizerpass.h`), `luau/` (the SLua analyzer), `masters/` (files on disk as the masters of scripts in the world: the links and their index, a master found from a script's `@file` hint or its name under the blessed folders, and what a send does) and `preprocessor/` (the preprocessor and its includes)
- **lscript** - LL's LSL compiler and LSO VM, restored as a test-only reference for the optimizer and the weigher; built with tests, never linked into the viewer
- **rust** - The Rust the viewer builds: one Cargo workspace and lock file, imported by Corrosion, a vcpkg port. It holds velopack_libc, Velopack's C API over the `velopack` crate from crates.io, its sources and headers vendored from the release with one buffer fix marked Alchemy (`cmake/Velopack.cmake`): a DLL on Windows, a static library on macOS and Linux, the release build for every configuration
- **newview** - The main viewer application. Contains all viewer-specific logic: the rendering pipeline, scene graph, UI floaters/panels, settings, object/avatar systems, and the application entry point. Links against all libraries above plus third-party: Tracy, OpenXR, Discord SDK, NVAPI, TinyEXR, Velopack
- **newview** - The main viewer application. Contains all viewer-specific logic: the rendering pipeline, scene graph, UI floaters/panels, settings, object/avatar systems, and the application entry point. Links against all libraries above plus third-party: Tracy, OpenXR, Discord SDK, NVAPI, TinyEXR, Velopack. It reaches GL only through llrender (gGL, LLRenderTarget, ALGPUReadback, ...): a pre-commit hook (`scripts/code_tools/check_raw_gl.py`) holds each of its files to the raw gl* calls `scripts/code_tools/raw_gl_baseline.txt` allows it

### Application Lifecycle

Expand Down
7 changes: 3 additions & 4 deletions indra/alrender/altransienttargetpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,14 @@ namespace
LLGLDisable no_scissor(GL_SCISSOR_TEST);
LLGLSColorMask colour_mask(true, true);
LLGLDepthTest depth_mask(GL_TRUE, GL_TRUE, GL_ALWAYS);
GLint stencil_mask = 0xff;
GLint clear_stencil = 0;
GLfloat clear_depth = 1.f;
glGetIntegerv(GL_STENCIL_WRITEMASK, &stencil_mask);
glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &clear_stencil);
glGetFloatv(GL_DEPTH_CLEAR_VALUE, &clear_depth);
const LLColor4 clear_colour = gGL.getClearColor();
const U32 stencil_mask = gGL.getStencilMask();

glStencilMask(0xff);
gGL.setStencilMask(0xff);
glClearDepth(Pool::POISON_DEPTH);
glClearStencil(Pool::POISON_STENCIL);
gGL.setClearColor(POISON_COLOUR);
Expand All @@ -114,7 +113,7 @@ namespace
gGL.setClearColor(clear_colour);
glClearStencil(clear_stencil);
glClearDepth(clear_depth);
glStencilMask((GLuint)stencil_mask);
gGL.setStencilMask(stencil_mask);
}

void free(LLRenderTarget& target) override { target.release(); }
Expand Down
4 changes: 2 additions & 2 deletions indra/alrender/tests/alondemandtarget_gl_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ namespace
{
++item.mDrawn;
CHECK(LLRenderTarget::getCurrentBoundTarget() == &target);
glViewport(0, 0, item.mWidth, item.mHeight);
glClear(GL_DEPTH_BUFFER_BIT);
gGL.setViewport(0, 0, item.mWidth, item.mHeight);
gGL.clear(GL_DEPTH_BUFFER_BIT);
draw(target);
REQUIRE(item.mImage->setSubImageFromFrameBuffer(0, 0, 0, 0, item.mWidth, item.mHeight));
}
Expand Down
7 changes: 6 additions & 1 deletion indra/llrender/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(
alquerypool.cpp
alsamplerstate.cpp
altexture3d.cpp
altextureblit.cpp
altextureslot.cpp
alfontcolrv1.cpp
alfontface.cpp
Expand Down Expand Up @@ -54,6 +55,7 @@ set(
alrendertargetdesc.h
alsamplerstate.h
altexture3d.h
altextureblit.h
altextureslot.h
alfontcolrv1.h
alfontface.h
Expand Down Expand Up @@ -210,7 +212,9 @@ if(AL_BUILD_TESTS)
# - which fragment output reaches a render target's colour attachment,
# integer attachments cleared and drawn, targets kept, built again and
# re-pointed at a shared depth by their descriptions, and their labels;
# - LLRender's cached clear colour and scissor box, and LLGLSScissor;
# - LLRender's cached clear colour, scissor box, viewport and raster state,
# LLGLSScissor and ALGLStencil, and a copy from one texture into another
# (ALTextureBlit);
# - LLGLSLShader's cache key, its uniform value cache, the compute path
# (programs built through the shader manager, their images, storage
# blocks and dispatches), integer textures through reserved samplers,
Expand Down Expand Up @@ -251,6 +255,7 @@ if(AL_BUILD_TESTS)
tests/alquerypool_gl_test.cpp
tests/alrenderstate_gl_test.cpp
tests/altessellation_gl_test.cpp
tests/altextureblit_gl_test.cpp
tests/aluniformbuffer_gl_test.cpp
tests/alwindowsdlheadless_test.cpp
tests/llfontgl_test.cpp
Expand Down
87 changes: 87 additions & 0 deletions indra/llrender/altextureblit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @file altextureblit.cpp
* @brief A copy from one texture into another through framebuffers it keeps, for textures no render target holds.
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Alchemy Viewer Source Code
* Copyright (C) 2026, Rye <rye@alchemyviewer.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/

#include "linden_common.h"

#include "altextureblit.h"

#include "llgl.h"
#include "llrender.h"
#include "llrendertarget.h"

ALTextureBlit::~ALTextureBlit()
{
release();
}

void ALTextureBlit::blit(U32 src_target, U32 src, U32 dst, S32 width, S32 height, bool flip_y)
{
llassert(src_target == GL_TEXTURE_2D || src_target == GL_TEXTURE_RECTANGLE);
gGL.flush();

if (!mReadFBO)
{
glGenFramebuffers(1, &mReadFBO);
}
if (!mDrawFBO)
{
glGenFramebuffers(1, &mDrawFBO);
}

glBindFramebuffer(GL_READ_FRAMEBUFFER, mReadFBO);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, src_target, src, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mDrawFBO);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst, 0);

{
// glBlitFramebuffer writes only where the scissor test passes.
LLGLDisable scissor(GL_SCISSOR_TEST);
glBlitFramebuffer(0, 0, width, height,
0, flip_y ? height : 0, width, flip_y ? 0 : height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}

// Detached, so neither framebuffer keeps a texture its owner may delete or hand to another context.
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, src_target, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glBindFramebuffer(GL_FRAMEBUFFER, LLRenderTarget::sCurFBO);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void ALTextureBlit::release()
{
// The names die with the context; deleting them after it would fault in the driver.
if (gGLManager.mInited)
{
if (mReadFBO)
{
glDeleteFramebuffers(1, &mReadFBO);
}
if (mDrawFBO)
{
glDeleteFramebuffers(1, &mDrawFBO);
}
}
mReadFBO = 0;
mDrawFBO = 0;
}
59 changes: 59 additions & 0 deletions indra/llrender/altextureblit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @file altextureblit.h
* @brief A copy from one texture into another through framebuffers it keeps, for textures no render target holds.
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Alchemy Viewer Source Code
* Copyright (C) 2026, Rye <rye@alchemyviewer.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* $/LicenseInfo$
*/

#pragma once

#include "stdtypes.h"

// Copies level 0 of one texture into another by glBlitFramebuffer, through a pair of framebuffers it makes on its first
// copy and keeps: the copy from a texture no LLRenderTarget holds, such as one whose storage a platform's interop
// supplies (a D3D11 texture, an IOSurface, a dma-buf), into another that need not be a target's either, upside down if
// asked.
//
// All operations require a current GL context, the one the framebuffers were made in. Non-copyable.
class ALTextureBlit
{
public:
ALTextureBlit() = default;
~ALTextureBlit();

ALTextureBlit(const ALTextureBlit&) = delete;
ALTextureBlit& operator=(const ALTextureBlit&) = delete;

// Copy the `width` x `height` rectangle at the origin of `src`, a texture of `src_target` (GL_TEXTURE_2D or
// GL_TEXTURE_RECTANGLE), into the same rectangle of `dst`, a GL_TEXTURE_2D, texel for texel, its rows in the
// opposite order where `flip_y`. What gGL has batched is drawn first, and the scissor does not clip the copy.
// Neither texture stays attached, and the framebuffer bound before (LLRenderTarget::sCurFBO) is bound again.
void blit(U32 src_target, U32 src, U32 dst, S32 width, S32 height, bool flip_y);

// Delete the framebuffers, if any. Call from a teardown that still has the context; the destructor calls it as a
// backstop, and issues no GL once the context is gone.
void release();

bool hasFramebuffers() const { return mReadFBO != 0 || mDrawFBO != 0; }

private:
U32 mReadFBO = 0;
U32 mDrawFBO = 0;
};
6 changes: 4 additions & 2 deletions indra/llrender/llgl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2029,10 +2029,12 @@ bool LLGLManager::initGL()

// Extract video card strings and convert to upper case to
// work around driver-to-driver variation in capitalization.
mGLVendor = ll_safe_string((const char *)glGetString(GL_VENDOR));
mGLVendorString = ll_safe_string((const char *)glGetString(GL_VENDOR));
mGLVendor = mGLVendorString;
LLStringUtil::toUpper(mGLVendor);

mGLRenderer = ll_safe_string((const char *)glGetString(GL_RENDERER));
mGLRendererString = ll_safe_string((const char *)glGetString(GL_RENDERER));
mGLRenderer = mGLRendererString;
LLStringUtil::toUpper(mGLRenderer);

parse_gl_version( &mDriverVersionMajor,
Expand Down
4 changes: 4 additions & 0 deletions indra/llrender/llgl.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ class LLGLManager
// In ALL CAPS
std::string mGLRenderer;

// GL_VENDOR and GL_RENDERER in the driver's own case, as mGLVersionString is GL_VERSION.
std::string mGLVendorString;
std::string mGLRendererString;

// GL_VENDOR, GL_RENDERER and GL_VERSION as reported, less the kernel's DRM version and
// release radeonsi and r600 put in the renderer on Linux. Keys what a driver update must
// invalidate and a kernel update must not: the benchmark result and program binaries.
Expand Down
14 changes: 6 additions & 8 deletions indra/llrender/llglstates.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,18 +221,16 @@ class ALGLStencil
{
llassert(sOpen == 0);
++sOpen;
gGL.flush();
glStencilFunc(func, ref, read_mask);
glStencilOp(fail, depth_fail, pass);
glStencilMask(write_mask);
gGL.setStencilFunc(func, ref, read_mask);
gGL.setStencilOp(fail, depth_fail, pass);
gGL.setStencilMask(write_mask);
}

~ALGLStencil()
{
gGL.flush();
glStencilFunc(GL_ALWAYS, 0, ~0u);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilMask(~0u);
gGL.setStencilFunc(GL_ALWAYS, 0, ~0u);
gGL.setStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
gGL.setStencilMask(~0u);
--sOpen;
}

Expand Down
2 changes: 1 addition & 1 deletion indra/llrender/llimagegl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3543,7 +3543,7 @@ bool LLImageGL::scaleDown(S32 desired_discard)

if (use_fbo)
{ // use an FBO to downscale the texture
glViewport(0, 0, desired_width, desired_height);
gGL.setViewport(0, 0, desired_width, desired_height);

// draw a full screen triangle, sampling the old (larger) texture
if (gGL.getTextureSlot(0)->bind(this, true, true))
Expand Down
Loading
Loading