diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 6045dfbffd..ad7f80d6cd 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -121,7 +121,7 @@ Depth of field runs **before** bloom and before the tonemapper, on linear HDR. G - **Auto-exposure:** Progressive histogram (`gLuminanceProgram`, `gExposureProgram`) with history fade - **Lens flare sun state:** a 2x1 temporal pass (`gLensFlareStateProgram`, `lensFlareStateF.glsl`) run just before colour correction measures how much of the sun disc is unoccluded and its colour, and filters both under a rate limit; `computeLensFlare` in `postEffectUtilsF.glsl` reads one texel of it. The shader is the source of truth for the filter; `scripts/content_tools/check_lens_flare_state.py` mirrors it and is its regression test - **Bloom:** Bright-area extraction → downsample/upsample pyramid over `bloomMip[]` with warmth correction and optional halation carried in alpha. The additive composite is **not** a pass of its own: it is folded into `colorCorrect`'s `BLOOM_COMPOSITE` permutation. `compositeBloomHDR` is a standalone equivalent that nothing currently calls -- **Cross-screen (star) filter:** `gCrossFilterProgram` streaks every thresholded highlight, seeded from `bloomMip[0]` and accumulated through three quadrants of `mWaterDis`, which the two shaders address through `uCrossRegion`/`uCrossClamp` so a tap at the quadrant's edge clamps exactly as it did at a dedicated target's edge. One strictly one-sided three-pass chain per arm, at strides that tile the reachable offsets exactly once — the tiling is load-bearing and the shader explains why. Composited in `colorCorrect` alongside the pyramid, so it inherits bloom strength +- **Cross-screen (star) filter:** `gCrossFilterProgram` streaks every thresholded highlight, seeded from `bloomMip[0]` and accumulated through three quadrants of `mWaterDis`, which the two shaders address through `uCrossRegion`/`uCrossClamp` so a tap at the quadrant's edge clamps exactly as it did at a dedicated target's edge. One strictly one-sided three-pass chain per arm, at strides that tile the reachable offsets exactly once — the tiling is load-bearing and the shader explains why. Composited in `colorCorrect` alongside the pyramid, so it inherits bloom strength. Each pass reads a quadrant the pass before it wrote while `mWaterDis` is still attached, which GL defines only across `glTextureBarrier`, so the filter runs only where that resolved (GL 4.5, or `GL_ARB_texture_barrier`/`GL_NV_texture_barrier`) and the Lightbox hides its sections elsewhere - **Lens dirt:** `gLensDirtGenProgram` draws the grime plate into `mLensDirtMap` — dust motes, wipe smudges, stray fibres, fine grit and optional scratches, built from hashes and distance fields rather than loaded from an image. Not a per-frame pass: it runs when a generation parameter moves or the window resizes, guarded by the cached parameter set in `mLensDirtParams`, which is also what makes a failed allocation stop retrying. Generated at the frame's own resolution, so nothing has to fit a square plate to a wide window. A rebuild is held off while `mLensDirtSliderHeld` is raised — the Lightbox raises it on a generation slider's mouse-down and lowers it on mouse-up, so a drag costs one plate on release rather than one per frame. Everything that is not a drag (typed values, resets, applying a Look, undo, window resizes) rebuilds immediately, which is what a settle timer would have delayed for no reason. `colorCorrect` multiplies the accumulated bloom and flare terms by it - **Depth of Field:** three passes — circle of confusion (`gDeferredCoFProgram`, writing sharp linear colour plus signed CoF in alpha), a gather blur at `CameraDoFResScale`, and a combine (`gDeferredDoFCombineProgram`) back over `mRT->screen` under `setColorMask(true, false)` so the legacy prim-glow alpha tag survives. It owns no targets: the sharp copy borrows `mWaterDis`, which the water passes refill each frame and have finished with, and the blur borrows `bloomMip[0]` between the luminance read and the extract pass (`postPongMap` without HDR). The blur viewport is clamped to what mip 0 can hold when `RenderBloomResolutionScale` shrinks it, and the combine is told the fraction it actually covers via `res_scale` and where it sits via `dof_uv_scale`. The gather has four compile-time variants over two axes, `FRONT_BLUR` × `DOF_SHAPED`: `gDeferredPostProgram`, `gDeferredPostProgramNoNear`, `gDeferredPostProgramShaped`, `gDeferredPostProgramNoNearShaped`. Optics: `CameraFNumber`, `CameraFocalLength`, `CameraFieldOfView`, `CameraMaxCoF`, `CameraDoFResScale` - **Bokeh shaping** (inside the `DOF_SHAPED` gather): polygonal aperture (`RenderBokehApertureBlades`, `Rotation`, `Curvature`), anamorphic squeeze, cat's-eye optical vignetting, defocus fringing, and the lens aberrations — spherical (`RenderBokehSphericalAberration`), field stretch for swirl and coma (`RenderBokehFieldStretch`, `FieldFalloff`) and comatic asymmetry. The CPU picks a shaped variant only when one of them is actually doing something diff --git a/doc/LIGHTBOX.md b/doc/LIGHTBOX.md index 0b18e6a94c..be7880fe6a 100644 --- a/doc/LIGHTBOX.md +++ b/doc/LIGHTBOX.md @@ -1223,7 +1223,10 @@ only; apply per row, not on the parent panel. Reference patterns: Advanced section apply while `RenderHDREnabled` is on (on GL above 4.05, the pipeline's own test), and so do Cross Filter and its Advanced section, which streak what the bloom pyramid holds; Glow (Legacy) and its Advanced section - apply while it is off. An effect that only runs on one side of the fork + apply while it is off. Cross Filter also needs a texture barrier, so on a + driver with neither GL 4.5 nor a texture barrier extension its sections stay + hidden under HDR too, as the renderer leaves it off there. An effect that only + runs on one side of the fork, or only with some capability, belongs in the table in `refreshBloomSections()`, not just behind an `enabled_control`. `refreshBloomSections()` hides the other side's accordion tabs and re-arranges the accordion, live on the setting's signal. A tab's diff --git a/indra/llrender/altextureslot.cpp b/indra/llrender/altextureslot.cpp index 78d7cfa814..fda1447229 100644 --- a/indra/llrender/altextureslot.cpp +++ b/indra/llrender/altextureslot.cpp @@ -390,12 +390,6 @@ bool ALTextureSlot::bind(LLCubeMap* cubeMap, ALSampler key) mCurrTexture = cubeMap->mImages[0]->getTexName(); glBindTexture(GL_TEXTURE_CUBE_MAP, mCurrTexture); cubeMap->mImages[0]->updateBindStats(); - // Named by the caller, not read off face 0. A cube map is as much a shared - // resource as any other texture -- the environment map is sampled by every - // shiny surface in the scene -- so which sampler a pass wants is the pass's - // business. - bindSampler(gGL.getSampler(key)); - return true; } else { @@ -403,6 +397,13 @@ bool ALTextureSlot::bind(LLCubeMap* cubeMap, ALSampler key) return false; } } + + // Named by the caller, not read off face 0. A cube map is as much a shared + // resource as any other texture -- the environment map is sampled by every + // shiny surface in the scene -- so which sampler a pass wants is the pass's + // business. Outside the redundancy check, as in bindImpl: the same cube can be + // bound again after a pass that left a different sampler on this slot. + bindSampler(gGL.getSampler(key)); return true; } diff --git a/indra/llrender/llfonttextcache.cpp b/indra/llrender/llfonttextcache.cpp index b6636749fd..48786dfb23 100644 --- a/indra/llrender/llfonttextcache.cpp +++ b/indra/llrender/llfonttextcache.cpp @@ -76,13 +76,16 @@ namespace bool ALFontCacheKey::environmentMoved(const LLFontGL* fontp) { const U64 font_cache_gen = fontp ? fontp->getCacheGeneration() : 0; + // The capture keeps the sampler's GL name, which a sampler rebuild deletes. + const U32 sampler_gen = gGL.getSamplerGeneration(); if (mFont == fontp && mScaleX == LLFontGL::sScaleX && mScaleY == LLFontGL::sScaleY && mVertDPI == LLFontGL::sVertDPI && mHorizDPI == LLFontGL::sHorizDPI && mResGeneration == LLFontGL::sResolutionGeneration - && mFontCacheGen == font_cache_gen) + && mFontCacheGen == font_cache_gen + && mSamplerGen == sampler_gen) { return false; } @@ -94,6 +97,7 @@ bool ALFontCacheKey::environmentMoved(const LLFontGL* fontp) mHorizDPI = LLFontGL::sHorizDPI; mResGeneration = LLFontGL::sResolutionGeneration; mFontCacheGen = font_cache_gen; + mSamplerGen = sampler_gen; return true; } diff --git a/indra/llrender/llfonttextcache.h b/indra/llrender/llfonttextcache.h index 3556b9f781..ee62a5cc5b 100644 --- a/indra/llrender/llfonttextcache.h +++ b/indra/llrender/llfonttextcache.h @@ -146,6 +146,7 @@ class ALFontCacheKey F32 mHorizDPI = 0.f; S32 mResGeneration = 0; U64 mFontCacheGen = 0; + U32 mSamplerGen = 0; // 0 is never a generation, so the first ask records }; // Everything one piece of text costs to draw, kept so it can be reused: the diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 14279aa2f0..e47d40f538 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -2420,6 +2420,18 @@ void LLGLManager::initExtensions() glGetnUniformuiv = (PFNGLGETNUNIFORMUIVPROC)LL_GET_PROC_ADDRESS("glGetnUniformuiv"); } + // Texture barrier. Core in 4.5, and GL_ARB_texture_barrier names it without a suffix on a + // lower context, so it is resolved here for the GL_VERSION_4_5 ladder's early-out as above. + // GL_NV_texture_barrier, which ARB's took over unchanged, names it glTextureBarrierNV. + if (mGLVersion >= 4.49f || mGLExtensions.contains("GL_ARB_texture_barrier")) + { + glTextureBarrier = (PFNGLTEXTUREBARRIERPROC)LL_GET_PROC_ADDRESS("glTextureBarrier"); + } + if (!glTextureBarrier && mGLExtensions.contains("GL_NV_texture_barrier")) + { + glTextureBarrier = (PFNGLTEXTUREBARRIERPROC)LL_GET_PROC_ADDRESS("glTextureBarrierNV"); + } + // GL_EXT_memory_object if (mHasEXTMemoryObject) { @@ -3292,6 +3304,11 @@ GLboolean LLGLDepthTest::sWriteEnabled = GL_TRUE; // OpenGL default //static void LLGLState::initClass() { + // Runs per context, from initGL: the map and the depth state describe this context, and + // GL starts every context at its defaults. + sStateMap.clear(); + LLGLDepthTest::initClass(); + sStateMap[GL_DITHER] = GL_TRUE; // sStateMap[GL_TEXTURE_2D] = GL_TRUE; @@ -3677,6 +3694,15 @@ GLenum LLGLDepthTest::remap(GLenum func) } } +//static +void LLGLDepthTest::initClass() +{ + sDepthEnabled = GL_FALSE; + sDepthFunc = GL_LESS; + sWriteEnabled = GL_TRUE; + glDepthFunc(remap(sDepthFunc)); +} + void LLGLDepthTest::rebase() { // Force the physical depth func to agree with the current translation of the tracked diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index f403991b99..e2588dbff0 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -319,6 +319,10 @@ class LLGLState static void initClass(); static void restoreGL(); + // Record a cap set with a raw glEnable/glDisable, so a scoped state does not take it for + // GL's default. The map describes the main context; call on the main thread only. + static void seedState(LLGLenum state, LLGLboolean enabled) { sStateMap[state] = enabled; } + static void resetTextureStates(); static void dumpStates(); @@ -334,6 +338,9 @@ class LLGLState enum { CURRENT_STATE = -2, DISABLED_STATE = 0, ENABLED_STATE = 1 }; LLGLState(LLGLenum state, S32 enabled = CURRENT_STATE); ~LLGLState(); + // A copy would restore the cap a second time. + LLGLState(const LLGLState&) = delete; + LLGLState& operator=(const LLGLState&) = delete; void setEnabled(S32 enabled); void enable() { setEnabled(ENABLED_STATE); } void disable() { setEnabled(DISABLED_STATE); } diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index f1bf366e05..2d31da1961 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -484,6 +484,14 @@ LLGLSLShader::~LLGLSLShader() // than a leak. Recursion covers the subtree: each child erases itself on the way out. sInstances.erase(this); + // Nor may the bound-program statics name this object. They are cleared without a GL call, + // for the reason below. + if (sCurBoundShaderPtr == this) + { + sCurBoundShaderPtr = nullptr; + sCurBoundShader = 0; + } + // Free the owned subtree's OBJECTS, so a program destroyed without unload() does not leak // them. Deliberately not unload(): a global program's destructor runs at static destruction // with no GL context, where deleting program objects is undefined -- and the driver reclaims @@ -644,20 +652,29 @@ bool LLGLSLShader::createVariant(EVariant axis) void LLGLSLShader::unload() { + freeOwnedVariants(); + + unloadInternal(); + + // After unloadInternal: unbinding a bound program ends its profile queries by the + // features they were begun with. mShaderFiles.clear(); mDefines.clear(); mPermutationsAdded = false; mFeatures = LLShaderFeatures(); - - freeOwnedVariants(); - - unloadInternal(); } void LLGLSLShader::unloadInternal() { sInstances.erase(this); + // A bound program is unbound before it is deleted, so the bound-program statics never + // name a dead program, or an owned variant freed right after this. + if (sCurBoundShaderPtr == this || (mProgramObject != 0 && sCurBoundShader == mProgramObject)) + { + unbind(); + } + stop_glerror(); mAttribute.clear(); mTexture.clear(); @@ -714,6 +731,12 @@ void LLGLSLShader::unloadInternal() mSamplesQuery = 0; } + if (mPrimitivesQuery) + { + glDeleteQueries(1, &mPrimitivesQuery); + mPrimitivesQuery = 0; + } + stop_glerror(); } diff --git a/indra/llrender/llglstates.h b/indra/llrender/llglstates.h index c3b28548c0..3bf4411d26 100644 --- a/indra/llrender/llglstates.h +++ b/indra/llrender/llglstates.h @@ -40,8 +40,17 @@ class LLGLDepthTest ~LLGLDepthTest(); + // A copy would restore the depth state a second time. + LLGLDepthTest(const LLGLDepthTest&) = delete; + LLGLDepthTest& operator=(const LLGLDepthTest&) = delete; + void checkState(); + // Take the tracked state back to a fresh context's: test off, writes on, GL_LESS, with the + // func issued through remap so GL agrees under whichever convention is latched. Run per + // context by LLGLState::initClass. + static void initClass(); + // Reverse-Z depth-func translation. The tracked state (sDepthFunc) and every call // site stay in the forward/semantic convention; the physical glDepthFunc is fed the // translated func so a call site asking for "nearer wins" keeps that meaning under diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 39a713e163..6b0cc42ec7 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -42,6 +42,7 @@ #include "llgl.h" #include "llglslshader.h" #include "llrender.h" +#include "llrendertarget.h" #include "llwindow.h" #include "llframetimer.h" #include @@ -3221,6 +3222,24 @@ bool LLImageGL::scaleDown(S32 desired_discard) S32 desired_width = getWidth(desired_discard); S32 desired_height = getHeight(desired_discard); + // A deprecated source format is held under a swizzle, and the FBO path cannot carry + // one: it samples the old texture THROUGH the swizzle and copies the result back by + // position, so luminance-alpha's alpha comes back as luminance and alpha-only loses + // its alpha entirely. The PBO path copies the stored channels as they are. + // + // The FBO path also draws into the target the caller bound -- the texture list's + // 1024x1024 scratch -- and copies back out of it, so a result larger than that target + // copies undefined pixels from outside the framebuffer. Such a texture takes the PBO + // path, which reads the level out of the texture's own mip chain; without one there + // is nothing it can read, however it came to that path, so the downscale is refused. + const bool fits_target = desired_width <= (S32)LLRenderTarget::sCurResX + && desired_height <= (S32)LLRenderTarget::sCurResY; + const bool use_fbo = gGLManager.mDownScaleMethod == 0 && mDeprecatedSourceFormat == 0 && fits_target; + if (!use_fbo && !mHasMipMaps) + { + return false; + } + // Downscale into a NEW texture object rather than reallocating this one in place. // glTexImage2D on the live name is illegal once the texture has immutable storage // (GL_INVALID_OPERATION, silently leaving it at the old size), and it has no D3D11 @@ -3235,12 +3254,6 @@ bool LLImageGL::scaleDown(S32 desired_discard) return false; } - // A deprecated source format is held under a swizzle, and the FBO path cannot carry - // one: it samples the old texture THROUGH the swizzle and copies the result back by - // position, so luminance-alpha's alpha comes back as luminance and alpha-only loses - // its alpha entirely. The PBO path copies the stored channels as they are. - const bool use_fbo = gGLManager.mDownScaleMethod == 0 && mDeprecatedSourceFormat == 0; - if (use_fbo) { // use an FBO to downscale the texture glViewport(0, 0, desired_width, desired_height); @@ -3283,7 +3296,10 @@ bool LLImageGL::scaleDown(S32 desired_discard) else { // use a PBO to downscale the texture U64 size = getBytes(desired_discard); - llassert(size <= 2048 * 2048 * 4); // we shouldn't be using this method to downscale huge textures, but it'll work + // At most a level below the largest texture the viewer keeps (a larger one is set + // missing on load), at four bytes a texel. The scratch buffer grows to whatever it is + // asked for; this bounds how far. + llassert(size <= (U64)(MAX_IMAGE_SIZE / 2) * (MAX_IMAGE_SIZE / 2) * 4); gGL.getTextureSlot(0)->bind(this, false, true); if (sScratchPBO == 0) diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 8b65316277..5ae5ac6436 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -39,6 +39,7 @@ #include "hbxxh.h" #include "alprojection.h" #include "llcamera.h" +#include "llthread.h" #include @@ -352,6 +353,10 @@ LLRender::~LLRender() bool LLRender::init(bool needs_vertex_buffer) { + // LLGLState's map describes the main context. A worker thread's shared context keeps its + // own caps, and the map is not the worker's to write. + const bool main_context = on_main_thread(); + #if GL_ARB_debug_output && !LL_DARWIN if (gGLManager.mHasDebugOutput && gDebugGL) { //setup debug output callback @@ -359,6 +364,13 @@ bool LLRender::init(bool needs_vertex_buffer) glDebugMessageCallback((GLDEBUGPROC) gl_debug_callback, NULL); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } + if (main_context && gGLManager.mHasDebugOutput) + { + // Both caps are set raw: above, by a debug context's default, or by whoever made the + // context before this ran. They are read back rather than assumed. + LLGLState::seedState(GL_DEBUG_OUTPUT, glIsEnabled(GL_DEBUG_OUTPUT)); + LLGLState::seedState(GL_DEBUG_OUTPUT_SYNCHRONOUS, glIsEnabled(GL_DEBUG_OUTPUT_SYNCHRONOUS)); + } #endif glPixelStorei(GL_PACK_ALIGNMENT, 1); @@ -422,6 +434,10 @@ bool LLRender::init(bool needs_vertex_buffer) // necessary for reflection maps glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + if (main_context) + { + LLGLState::seedState(GL_TEXTURE_CUBE_MAP_SEAMLESS, GL_TRUE); + } #if LL_WINDOWS if (glGenVertexArrays == nullptr) @@ -526,7 +542,7 @@ void LLRender::refreshState(void) mDirty = true; // Called when GL state may have been changed behind our back, so re-assert the shared - // blocks' bindings along with the texture units and colour mask. + // blocks' bindings along with the texture units and the state the setters cache. mLightsUBOBound = false; mMatricesUBOBound = false; @@ -539,10 +555,29 @@ void LLRender::refreshState(void) mTextureSlots[active_unit].activate(); - setColorMask(mCurrColorMask[0], mCurrColorMask[1], mCurrColorMask[2], mCurrColorMask[3]); + // The cached state below is issued straight to GL: each setter would see its cache + // agreeing with the value asked for and skip the call. + glColorMask(mCurrColorMask[0] ? GL_TRUE : GL_FALSE, + mCurrColorMask[1] ? GL_TRUE : GL_FALSE, + mCurrColorMask[2] ? GL_TRUE : GL_FALSE, + mCurrColorMask[3] ? GL_TRUE : GL_FALSE); + + // Blend factors still unknown since init have nothing to put back. + if (mCurrBlendColorSFactor != BF_UNDEF && mCurrBlendColorDFactor != BF_UNDEF && + mCurrBlendAlphaSFactor != BF_UNDEF && mCurrBlendAlphaDFactor != BF_UNDEF) + { + glBlendFuncSeparate(sGLBlendFactor[mCurrBlendColorSFactor], sGLBlendFactor[mCurrBlendColorDFactor], + sGLBlendFactor[mCurrBlendAlphaSFactor], sGLBlendFactor[mCurrBlendAlphaDFactor]); + } + + glLineWidth(mLineWidth); + + // Loaded only on a context with tessellation, the one that has a patch size. + if (glPatchParameteri) + { + glPatchParameteri(GL_PATCH_VERTICES, mPatchVertices); + } - // Unconditional re-issue: setPolygonOffset would see the cache already agreeing with the - // requested value and skip the GL call, leaving the fresh context at its 0,0 default. rebasePolygonOffset(); // The same for the clear colour and a scissor box that was ever set. @@ -1004,13 +1039,13 @@ void LLRender::setColorMask(bool writeColor, bool writeAlpha) void LLRender::setColorMask(bool writeColorR, bool writeColorG, bool writeColorB, bool writeAlpha) { - flush(); - if (mCurrColorMask[0] != writeColorR || mCurrColorMask[1] != writeColorG || mCurrColorMask[2] != writeColorB || mCurrColorMask[3] != writeAlpha) { + flush(); + mCurrColorMask[0] = writeColorR; mCurrColorMask[1] = writeColorG; mCurrColorMask[2] = writeColorB; @@ -1670,11 +1705,10 @@ void LLRender::diffuseColor4ub(U8 r, U8 g, U8 b, U8 a) void LLRender::setLineWidth(F32 width) { - gGL.flush(); - width = llclamp(width, gGLManager.mAliasedLineRange[0], gGLManager.mAliasedLineRange[1]); if(mLineWidth != width) { + flush(); mLineWidth = width; glLineWidth(width); } diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp index 0b175e2265..dac14f36d7 100644 --- a/indra/llrender/llrendertarget.cpp +++ b/indra/llrender/llrendertarget.cpp @@ -536,6 +536,8 @@ void LLRenderTarget::release() void LLRenderTarget::bindTarget() { LL_PROFILE_GPU_ZONE("bindTarget"); + // What gGL has batched belongs to the framebuffer bound before this one. + gGL.flush(); llassert(mFBO); llassert(!isBoundInStack()); @@ -571,6 +573,8 @@ void LLRenderTarget::bindTarget() void LLRenderTarget::clear(U32 mask_in) { LL_PROFILE_GPU_ZONE("clear"); + // What gGL has batched is drawn before the clear, not after it. + gGL.flush(); llassert(mFBO); U32 mask = GL_COLOR_BUFFER_BIT; if (mUseDepth) @@ -750,6 +754,9 @@ void LLRenderTarget::copyContents(LLRenderTarget& source, S32 srcX0, S32 srcY0, void LLRenderTarget::copyContentsToFramebuffer(LLRenderTarget& source, S32 srcX0, S32 srcY0, S32 srcX1, S32 srcY1, S32 dstX0, S32 dstY0, S32 dstX1, S32 dstY1, U32 mask, U32 filter) { + // What gGL has batched is drawn before the blit reads or overwrites it. + gGL.flush(); + if (!source.mFBO) { LL_WARNS() << "Cannot copy framebuffer contents for non FBO render targets." << LL_ENDL; @@ -808,6 +815,8 @@ void LLRenderTarget::setDrawBuffers(U32 count) const U32 wanted = (count == 0) ? attachments : llmin(count, attachments); + // What gGL has batched goes to the attachments selected when it was drawn. + gGL.flush(); glDrawBuffers(static_cast(wanted), drawbuffers); } @@ -822,6 +831,7 @@ void LLRenderTarget::setDrawOutput(U32 output) output = llmin(output, (U32)LL_ARRAY_SIZE(drawbuffers) - 1); drawbuffers[output] = GL_COLOR_ATTACHMENT0; + gGL.flush(); glDrawBuffers(static_cast(output + 1), drawbuffers); } diff --git a/indra/llrender/lluiimage.cpp b/indra/llrender/lluiimage.cpp index 892d7f4ee4..3d515f5e18 100644 --- a/indra/llrender/lluiimage.cpp +++ b/indra/llrender/lluiimage.cpp @@ -100,9 +100,11 @@ void LLUIImage::drawCached(S32 x, S32 y, S32 width, S32 height, const LLColor4& } // The GL name stands for the texture's current state: a discard change or a - // recreation renames it, and recordings made against the old name lapse. + // recreation renames it, and recordings made against the old name lapse. The sampler + // generation does the same for the sampler name the recording keeps. const LLVector3 ui_scale = gGL.getUIScale(); - const PackedKey key = PackedKey::create(width, height, color, solid_color, ui_scale, gl_image->getTexName()); + const PackedKey key = PackedKey::create(width, height, color, solid_color, ui_scale, gl_image->getTexName(), + gGL.getSamplerGeneration()); // Where the image sits on screen, in the units the recording's vertices are // in. It goes through the modelview rather than into the vertices, which is diff --git a/indra/llrender/lluiimage.h b/indra/llrender/lluiimage.h index e45aa7cc6d..e48ff18dba 100644 --- a/indra/llrender/lluiimage.h +++ b/indra/llrender/lluiimage.h @@ -135,7 +135,7 @@ class LLUIImage : public LLRefCount uint64_t color_flags; // RGBA color (8 bits each) + solid_color flag (1 bit) uint64_t dimensions; // width and height (32 bits each) uint64_t scale; // UI scale (32 bits each for X and Y) - uint64_t tex_name; // OpenGL texture name (32 bits) + padding + uint64_t tex_name; // OpenGL texture name (low 32 bits) + sampler generation (high 32 bits) constexpr bool operator==(const PackedKey& other) const { @@ -164,7 +164,8 @@ class LLUIImage : public LLRefCount static constexpr PackedKey create(S32 width, S32 height, const LLColor4& color, bool solid_color, const LLVector3& scale, - LLGLuint texture_name) + LLGLuint texture_name, + U32 sampler_generation) { auto float_to_u8 = [](F32 f) -> uint8_t { return static_cast(llclamp(f * 255.0f, 0.0f, 255.0f)); @@ -191,8 +192,11 @@ class LLUIImage : public LLRefCount uint64_t scl = (static_cast(float_to_bits(scale.mV[VX])) << 32) | static_cast(float_to_bits(scale.mV[VY])); - // Store full 32-bit texture name in lower 32 bits (upper 32 bits unused/zero) - uint64_t tex = static_cast(texture_name); + // A recording keeps the sampler's GL name, and rebuilding the samplers (an + // anisotropy change, a depth convention flip) deletes every name, so a recording + // made under an older generation would bind a dead or reused one. + uint64_t tex = (static_cast(sampler_generation) << 32) | + static_cast(texture_name); return PackedKey{ col, dim, scl, tex }; } diff --git a/indra/llrender/tests/alrenderstate_gl_test.cpp b/indra/llrender/tests/alrenderstate_gl_test.cpp index a11edb4584..d17807e860 100644 --- a/indra/llrender/tests/alrenderstate_gl_test.cpp +++ b/indra/llrender/tests/alrenderstate_gl_test.cpp @@ -1,7 +1,8 @@ /** * @file alrenderstate_gl_test.cpp - * @brief LLRender's cached clear colour and scissor box on the hidden window: what a clear fills - * with and cuts to, what GL holds after refreshState, and what LLGLSScissor hands back. + * @brief LLRender's cached clear colour, scissor box and patch size on the hidden window: what a + * clear fills with and cuts to, what GL holds after refreshState, and what LLGLSScissor + * hands back. * * $LicenseInfo:firstyear=2026&license=viewerlgpl$ * Alchemy Viewer Source Code @@ -134,6 +135,40 @@ namespace tut ensure_equals("no GL error", (U32)glGetError(), (U32)GL_NO_ERROR); } + // And the patch size, on a context with tessellation to have one. + template<> template<> + void alrenderstate_object_t::test<4>() + { + if (!glPatchParameteri) + { + skip("this context has no tessellation"); + } + + // Handed back however the test ends: the shared context outlives it, and its scope does not keep this. + struct PatchSizeRestore + { + GLint mFound = 0; + PatchSizeRestore() { glGetIntegerv(GL_PATCH_VERTICES, &mFound); } + ~PatchSizeRestore() + { + gGL.setPatchVertices((U32)mFound); + glPatchParameteri(GL_PATCH_VERTICES, mFound); + } + } restore; + + gGL.setPatchVertices(4); + glPatchParameteri(GL_PATCH_VERTICES, 3); + gGL.setPatchVertices(4); + GLint patch = 0; + glGetIntegerv(GL_PATCH_VERTICES, &patch); + ensure_equals("an agreeing cache issues nothing", (S32)patch, 3); + + gGL.refreshState(); + glGetIntegerv(GL_PATCH_VERTICES, &patch); + ensure_equals("refreshState restores the patch size", (S32)patch, 4); + ensure_equals("no GL error", (U32)glGetError(), (U32)GL_NO_ERROR); + } + // LLGLSScissor cuts a clear to its box with the test on, and hands back the box and the test it found, nested // scopes included. template<> template<> diff --git a/indra/newview/alfloaterlightbox.cpp b/indra/newview/alfloaterlightbox.cpp index fe1402975b..192a642b3e 100644 --- a/indra/newview/alfloaterlightbox.cpp +++ b/indra/newview/alfloaterlightbox.cpp @@ -2377,26 +2377,28 @@ void ALFloaterLightBox::refreshBloomSections() // is on the HDR side with the bloom it is seeded from: it streaks what // the bloom pyramid's top holds, and without the pyramid it draws nothing. // So is the auto exposure, which renderFinalize meters only under HDR. + // The cross filter also needs a texture barrier (generateBloomHDR), which a + // driver with neither GL 4.5 nor a texture barrier extension does not have. const bool hdr = gGLManager.mGLVersion > 4.05f && gSavedSettings.getBOOL("RenderHDREnabled"); - static const std::pair bloom_sections[] = { - { "sec_autoexp", true }, - { "sec_autoexp_adv", true }, - { "sec_bloom", true }, - { "sec_bloom_adv", true }, - { "sec_crossfilter", true }, - { "sec_crossfilter_adv", true }, - { "sec_glow", false }, - { "sec_glow_adv", false }, + const bool cross_filter = hdr && glTextureBarrier; + const std::pair bloom_sections[] = { + { "sec_autoexp", hdr }, + { "sec_autoexp_adv", hdr }, + { "sec_bloom", hdr }, + { "sec_bloom_adv", hdr }, + { "sec_crossfilter", cross_filter }, + { "sec_crossfilter_adv", cross_filter }, + { "sec_glow", !hdr }, + { "sec_glow_adv", !hdr }, }; std::set changed; - for (const auto& [name, for_hdr] : bloom_sections) + for (const auto& [name, shown] : bloom_sections) { const ALLightboxDirectory::Section* section = mDirectory.section(name); if (!section || !section->mTab) { continue; } - const bool shown = (for_hdr == hdr); if (section->mTab->getVisible() == shown) { continue; diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 1888bc877c..71a1156048 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8595,28 +8595,6 @@ Value 4.0 - RenderDeferredSpotShadowBias - - Comment - Bias value for spot shadows (prevent shadow acne). - Persist - 1 - Type - F32 - Value - -64.0 - - RenderDeferredSpotShadowOffset - - Comment - Offset value for spot shadows (prevent shadow acne). - Persist - 1 - Type - F32 - Value - 0.8 - RenderShadowBias @@ -8757,28 +8735,6 @@ Value 1.0 - RenderDeferredTreeShadowBias - - Comment - Bias value for tree shadows (prevent shadow acne). - Persist - 1 - Type - F32 - Value - 1.0 - - RenderDeferredTreeShadowOffset - - Comment - Offset value for tree shadows (prevent shadow acne). - Persist - 1 - Type - F32 - Value - 1.0 - RenderHoverGlowEnable @@ -10234,17 +10190,6 @@ Value 0 - RenderUIBuffer - - Comment - Cache ui render in a screen aligned buffer. - Persist - 1 - Type - Boolean - Value - 0 - RenderUnloadedAvatar Comment diff --git a/indra/newview/fsmaniprotatejoint.cpp b/indra/newview/fsmaniprotatejoint.cpp index 8694bb7386..deb10b1443 100644 --- a/indra/newview/fsmaniprotatejoint.cpp +++ b/indra/newview/fsmaniprotatejoint.cpp @@ -115,13 +115,6 @@ static void renderPulsingSphere(const LLVector3& joint_world_position, const LLC gGL.popMatrix(); } gGL.popMatrix(); - - // Check for OpenGL errors - GLenum err; - while ((err = glGetError()) != GL_NO_ERROR) - { - LL_INFOS() << "OpenGL Error: " << err << LL_ENDL; - } } static void renderStaticSphere(const LLVector3& joint_world_position, const LLColor4& color = LLColor4(1.f, 1.f, 0.f, .6f), float radius=0.01f) @@ -160,13 +153,6 @@ static void renderStaticSphere(const LLVector3& joint_world_position, const LLCo gGL.popMatrix(); } gGL.popMatrix(); - - // Check for OpenGL errors - GLenum err; - while ((err = glGetError()) != GL_NO_ERROR) - { - LL_INFOS() << "OpenGL Error: " << err << LL_ENDL; - } } bool FSManipRotateJoint::isMouseOverJoint(S32 mouseX, S32 mouseY, const LLVector3& jointWorldPos, F32 jointRadius, F32& outDistanceFromCamera, F32& outRayDistanceFromCenter) const diff --git a/indra/newview/llcefaccelinterop.cpp b/indra/newview/llcefaccelinterop.cpp index 25a674ec0c..4554f00a7a 100644 --- a/indra/newview/llcefaccelinterop.cpp +++ b/indra/newview/llcefaccelinterop.cpp @@ -30,6 +30,7 @@ #include "llgl.h" // viewer GL entry points + the global wglDX* pointers #include "llrender.h" +#include "llrendertarget.h" #include "lldxhardware.h" // shared D3D11 device + GL interop device (gDXHardware) #include @@ -231,10 +232,6 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) if (!w->read_fbo) glGenFramebuffers(1, &w->read_fbo); if (!w->draw_fbo) glGenFramebuffers(1, &w->draw_fbo); - GLint prev_read = 0, prev_draw = 0; - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw); - glBindFramebuffer(GL_READ_FRAMEBUFFER, w->read_fbo); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, w->local_gl, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, w->draw_fbo); @@ -246,8 +243,7 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw); + glBindFramebuffer(GL_FRAMEBUFFER, LLRenderTarget::sCurFBO); wglDXUnlockObjectsNV(gl_dx, 1, &w->local_obj); return true; @@ -256,6 +252,8 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) #elif LL_DARWIN // macOS: bind the shared IOSurface to a GL texture, blit to media #include "llgl.h" +#include "llrender.h" +#include "llrendertarget.h" #include #include #include @@ -341,11 +339,13 @@ bool LLCEFAccelInterop::setStableTexture(unsigned long long handle, int width, i const int new_height = (int)IOSurfaceGetHeight(surf); CGLContextObj cgl = CGLGetCurrentContext(); - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m->tex); + // Through the slot, so its record of what is bound stays true (see the Linux import). + ALTextureSlot* slot = gGL.getTextureSlot(0); + slot->bindManual(ALTextureSlot::TT_RECT_TEXTURE, m->tex); CGLError err = CGLTexImageIOSurface2D(cgl, GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, new_width, new_height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surf, 0); - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); + slot->unbind(); if (err != kCGLNoError) { CFRelease(surf); @@ -380,10 +380,6 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) if (!m->read_fbo) glGenFramebuffers(1, &m->read_fbo); if (!m->draw_fbo) glGenFramebuffers(1, &m->draw_fbo); - GLint prev_read = 0, prev_draw = 0; - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw); - glBindFramebuffer(GL_READ_FRAMEBUFFER, m->read_fbo); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, m->tex, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m->draw_fbo); @@ -395,14 +391,15 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, 0, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw); + glBindFramebuffer(GL_FRAMEBUFFER, LLRenderTarget::sCurFBO); return true; } #elif LL_LINUX // import the plugin's dma-buf via EGL, blit to the media texture #include "llgl.h" +#include "llrender.h" +#include "llrendertarget.h" #include #include #include @@ -592,9 +589,12 @@ bool LLCEFAccelInterop::setStableTexture(unsigned long long handle, int width, i l->image = new_image; l->width = width; l->height = height; - glBindTexture(GL_TEXTURE_2D, l->tex); + // Through the slot, so its record of what is bound stays true: a raw bind left it naming a + // texture this replaced, and its next bind of that texture was skipped. + ALTextureSlot* slot = gGL.getTextureSlot(0); + slot->bindManual(ALTextureSlot::TT_TEXTURE, l->tex); glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, l->image); - glBindTexture(GL_TEXTURE_2D, 0); + slot->unbind(); return true; } @@ -619,10 +619,6 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) if (!l->read_fbo) glGenFramebuffers(1, &l->read_fbo); if (!l->draw_fbo) glGenFramebuffers(1, &l->draw_fbo); - GLint prev_read = 0, prev_draw = 0; - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prev_read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &prev_draw); - glBindFramebuffer(GL_READ_FRAMEBUFFER, l->read_fbo); glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, l->tex, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, l->draw_fbo); @@ -634,8 +630,7 @@ bool LLCEFAccelInterop::blitTo(unsigned int dst_tex, int width, int height) glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, prev_read); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, prev_draw); + glBindFramebuffer(GL_FRAMEBUFFER, LLRenderTarget::sCurFBO); return true; } diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 8477faff59..7bd5309bad 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -752,7 +752,9 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) { // debug code to draw a sphere in place of avatar gGL.getTextureSlot(0)->bindSampled(LLViewerFetchedTexture::sWhiteImagep, ALSamplers::AnisoWrap); - gGL.setColorMask(true, true); + // Scoped: pass 0 runs for every avatar pool before pass 1 does, and the G-buffer + // passes behind this one write their flags to alpha. + LLGLSColorMask mask(true, true); LLVector3 pos = avatarp->getPositionAgent(); gGL.color4f(1.0f, 1.0f, 1.0f, 0.7f); @@ -765,7 +767,6 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) gSphere.renderGGL(); gGL.popMatrix(); - gGL.setColorMask(true, false); } // don't render please return; diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index d2ab4cd9d7..17c99f8aac 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -941,7 +941,9 @@ void LLBumpImageList::onSourceUpdated(LLViewerTexture* src, EBumpEffect bump_cod LLGLDepthTest depth(GL_FALSE); LLGLDisable cull(GL_CULL_FACE); LLGLDisable blend(GL_BLEND); - gGL.setColorMask(true, true); + // Scoped: this is reached from the post-deferred bump pass too, where the passes + // after it keep alpha writes off. + LLGLSColorMask mask(true, true); LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; gNormalMapGenProgram.bind(); diff --git a/indra/newview/lldrawpooltree.cpp b/indra/newview/lldrawpooltree.cpp index 9b02cb90ac..11451acda9 100644 --- a/indra/newview/lldrawpooltree.cpp +++ b/indra/newview/lldrawpooltree.cpp @@ -36,7 +36,6 @@ #include "llviewercamera.h" #include "llviewershadermgr.h" #include "llrender.h" -#include "llviewercontrol.h" #include "llviewerregion.h" #include "llenvironment.h" @@ -117,10 +116,6 @@ void LLDrawPoolTree::beginShadowPass(S32 pass) { LL_PROFILE_ZONE_SCOPED; - static LLCachedControl shadow_offset(gSavedSettings, "RenderDeferredTreeShadowOffset"); - static LLCachedControl shadow_bias(gSavedSettings, "RenderDeferredTreeShadowBias"); - gGL.setPolygonOffset(shadow_offset(), shadow_bias()); - LLEnvironment& environment = LLEnvironment::instance(); gDeferredTreeShadowProgram.bind(); @@ -137,8 +132,6 @@ void LLDrawPoolTree::endShadowPass(S32 pass) { LL_PROFILE_ZONE_SCOPED; - gGL.setPolygonOffset(gSavedSettings.getF32("RenderDeferredSpotShadowOffset"), - gSavedSettings.getF32("RenderDeferredSpotShadowBias")); gDeferredTreeShadowProgram.unbind(); } diff --git a/indra/newview/llgltfmaterialpreviewmgr.cpp b/indra/newview/llgltfmaterialpreviewmgr.cpp index 3df66b433f..3245614ccf 100644 --- a/indra/newview/llgltfmaterialpreviewmgr.cpp +++ b/indra/newview/llgltfmaterialpreviewmgr.cpp @@ -428,6 +428,11 @@ bool LLGLTFPreviewTexture::render() if (!mShouldRender) { return false; } + // The swatch's alpha is its coverage, drawn over a checkerboard. Without this the + // preview takes whatever mask the last frame ended on, and after a HUD pass that leaves + // alpha unwritten from the clears through the final blit. + LLGLSColorMask color_mask(true, true); + gGL.setClearColor(LLColor4::transparent); gGL.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -507,7 +512,9 @@ bool LLGLTFPreviewTexture::render() // Alpha blend rendering screen.bindTarget(); - gGL.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Colour only: the sphere draws with the depth test off, and depth writes are off + // with it, so a depth clear here would do nothing. + gGL.clear(GL_COLOR_BUFFER_BIT); LLGLSLShader& shader = *gDeferredPBRAlphaProgram.selectVariant(); @@ -558,7 +565,10 @@ bool LLGLTFPreviewTexture::render() gDeferredPostNoDoFProgram.bind(); // From LLPipeline::renderFinalize: "Whatever is last in the above post processing chain should _always_ be rendered directly here. If not, expect problems." - gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &screen); + // Under HDR that is colorCorrect's output in postPingMap; otherwise the glow combine has + // written the result back to the screen. + LLRenderTarget* last_in_chain = hdr ? &gPipeline.mRT->postPingMap : &screen; + gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, last_in_chain); gDeferredPostNoDoFProgram.bindDepthTexture(LLShaderMgr::DEFERRED_DEPTH, &gPipeline.mAuxillaryRT.deferredScreen); { diff --git a/indra/newview/llmanip.cpp b/indra/newview/llmanip.cpp index 09b8c0bdf0..4733488057 100644 --- a/indra/newview/llmanip.cpp +++ b/indra/newview/llmanip.cpp @@ -594,6 +594,9 @@ LLColor4 LLManip::setupSnapGuideRenderPass(S32 pass) LLColor4 line_color; F32 line_alpha = gSavedSettings.getF32("GridOpacity"); + // The last pass's lines go out under its viewport; setup3DViewport sets the next one raw. + gGL.flush(); + switch(pass) { case 0: diff --git a/indra/newview/llscenemonitor.cpp b/indra/newview/llscenemonitor.cpp index 82ef7fb967..893e097ee5 100644 --- a/indra/newview/llscenemonitor.cpp +++ b/indra/newview/llscenemonitor.cpp @@ -42,6 +42,8 @@ #include "llviewerparcelmgr.h" #include "llviewerpartsim.h" +#include + LLSceneMonitorView* gSceneMonitorView = NULL; // @@ -409,9 +411,10 @@ void LLSceneMonitor::calcDiffAggregate() } LLGLDepthTest depth(true, false, GL_ALWAYS); + std::optional no_color; if(!mDebugViewerVisible) { - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + no_color.emplace(false, false); } LLGLSLShader* cur_shader = NULL; @@ -429,6 +432,9 @@ void LLSceneMonitor::calcDiffAggregate() if(mDiffState == EXECUTE_DIFF) { + // The quad is still batched in gGL; the query only counts what reaches the GPU before + // it ends. + gGL.flush(); glEndQuery(GL_SAMPLES_PASSED); mDiffState = WAIT_ON_RESULT; } @@ -439,11 +445,6 @@ void LLSceneMonitor::calcDiffAggregate() { cur_shader->bind(); } - - if(!mDebugViewerVisible) - { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } #endif } diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 93690b2d5a..cd936aac24 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -1706,6 +1706,7 @@ void renderOctree(LLSpatialGroup* group) { LLGLDepthTest gl_depth(false, false); + gGL.flush(); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); gGL.diffuseColor4f(1,0,0,group->mBuilt); @@ -1749,6 +1750,12 @@ void renderOctree(LLSpatialGroup* group) gGL.loadMatrix(LLViewerCamera::getCurrent().getModelview()); if (!LLRenderPass::uploadMatrixPalette(face->mAvatar, face->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) { + // Both pushes above are popped at the end of the iteration this skips. + gGL.popMatrix(); + if (!group->getSpatialPartition()->isBridge()) + { + gGL.popMatrix(); + } continue; } } @@ -1922,6 +1929,10 @@ void renderUpdateType(LLDrawable* drawablep) void renderBoundingBox(LLDrawable* drawable, bool set_color = true) { + // The colour is a uniform, so the last drawable's outlines go out under their own colour + // before it changes. + gGL.flush(); + if (set_color) { if (drawable->isSpatialBridge()) @@ -2949,6 +2960,8 @@ class LLRenderOctreeRaycast : public LLOctreeTriangleRayIntersect gGL.vertex3fv(tri->mV[2]->getF32ptr()); } gGL.end(); + // Drawn under this pass's depth func and colour, which the next pass changes. + gGL.flush(); if (i == 1) { diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 71f2838628..37e223d676 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -331,12 +331,6 @@ static bool handleShadowsResized(const LLSD& newvalue) return true; } -static bool handleWindowResized(const LLSD& newvalue) -{ - gPipeline.requestResizeScreenTexture(); - return true; -} - static bool handleReleaseGLBufferChanged(const LLSD& newvalue) { if (gPipeline.isInit()) @@ -1064,7 +1058,6 @@ void settings_setup_listeners() setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition); setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition); setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized); setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged); setting_setup_signal_listener(gSavedSettings, "RenderFSAAType", handleReleaseGLBufferChanged); setting_setup_signal_listener(gSavedSettings, "RenderSMAAUseStencil", handleReleaseGLBufferChanged); diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 0ddfd97b78..0274cb38c6 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -200,8 +200,6 @@ void display_startup() if (gViewerWindow && gViewerWindow->getWindow()) gViewerWindow->getWindow()->swapBuffers(); - - glClear(GL_DEPTH_BUFFER_BIT); } void display_update_camera() @@ -841,7 +839,6 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LLGLState::checkStates(); const LLCamera saved_camera = LLViewerCamera::getCurrent(); - glViewport(0,0,512,512); LLVOAvatar::updateImpostors(); @@ -1821,69 +1818,7 @@ void render_ui_2d() stop_glerror(); } - - if (LLPipeline::RenderUIBuffer) - { - if (LLView::sIsRectDirty) - { - LLView::sIsRectDirty = false; - LLRect t_rect; - - gPipeline.mUIScreen.bindTarget(); - gGL.setColorMask(true, true); - { - constexpr S32 pad = 8; - - LLView::sDirtyRect.mLeft -= pad; - LLView::sDirtyRect.mRight += pad; - LLView::sDirtyRect.mBottom -= pad; - LLView::sDirtyRect.mTop += pad; - - LLGLEnable scissor(GL_SCISSOR_TEST); - static LLRect last_rect = LLView::sDirtyRect; - - //union with last rect to avoid mouse poop - last_rect.unionWith(LLView::sDirtyRect); - - t_rect = LLView::sDirtyRect; - LLView::sDirtyRect = last_rect; - last_rect = t_rect; - - last_rect.mLeft = LLRect::tCoordType(last_rect.mLeft / LLUI::getScaleFactor().mV[0]); - last_rect.mRight = LLRect::tCoordType(last_rect.mRight / LLUI::getScaleFactor().mV[0]); - last_rect.mTop = LLRect::tCoordType(last_rect.mTop / LLUI::getScaleFactor().mV[1]); - last_rect.mBottom = LLRect::tCoordType(last_rect.mBottom / LLUI::getScaleFactor().mV[1]); - - LLRect clip_rect(last_rect); - - glClear(GL_COLOR_BUFFER_BIT); - - gViewerWindow->draw(); - } - - gPipeline.mUIScreen.flush(); - gGL.setColorMask(true, false); - - LLView::sDirtyRect = t_rect; - } - - LLGLDisable cull(GL_CULL_FACE); - LLGLDisable blend(GL_BLEND); - S32 width = gViewerWindow->getWindowWidthScaled(); - S32 height = gViewerWindow->getWindowHeightScaled(); - gGL.getTextureSlot(0)->bind(&gPipeline.mUIScreen); - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.color4f(1.f,1.f,1.f,1.f); - gGL.texCoord2f(0.f, 0.f); gGL.vertex2i(0, 0); - gGL.texCoord2f((F32)width, 0.f); gGL.vertex2i(width, 0); - gGL.texCoord2f(0.f, (F32)height); gGL.vertex2i(0, height); - gGL.texCoord2f((F32)width, (F32)height); gGL.vertex2i(width, height); - gGL.end(); - } - else - { - gViewerWindow->draw(); - } + gViewerWindow->draw(); // reset current origin for font rendering, in case of tiling render LLFontGL::sCurOrigin.set(0, 0); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 30ee15e6cf..f5e1717c8b 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -3030,10 +3030,7 @@ void LLViewerWindow::draw() //S32 screen_x, screen_y; - if (!LLPipeline::RenderUIBuffer) - { - LLView::sDirtyRect = getWindowRectScaled(); - } + LLView::sDirtyRect = getWindowRectScaled(); // HACK for timecode debugging if (gSavedSettings.getBOOL("DisplayTimecode")) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 77e2a970fe..4e5f1eed63 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -177,7 +177,6 @@ U32 LLPipeline::RenderResolutionDivisor; // [SL:KB] - Patch: Settings-RenderResolutionMultiplier | Checked: Catznip-5.4 F32 LLPipeline::RenderResolutionMultiplier; // [/SL:KB] -bool LLPipeline::RenderUIBuffer; S32 LLPipeline::RenderShadowDetail; S32 LLPipeline::RenderShadowSplits; bool LLPipeline::RenderDeferredSSAO; @@ -590,7 +589,6 @@ void LLPipeline::init() // [SL:KB] - Patch: Settings-RenderResolutionMultiplier | Checked: Catznip-5.4 connectRefreshCachedSettingsSafe("RenderResolutionMultiplier"); // [/SL:KB] - connectRefreshCachedSettingsSafe("RenderUIBuffer"); connectRefreshCachedSettingsSafe("RenderShadowDetail"); connectRefreshCachedSettingsSafe("RenderShadowSplits"); connectRefreshCachedSettingsSafe("RenderDeferredSSAO"); @@ -1038,14 +1036,6 @@ bool LLPipeline::allocateScreenBufferInternal(U32 resX, U32 resY) if (!gCubeSnapshot) // hack to not re-allocate various targets for cube snapshots { - if (RenderUIBuffer) - { - if (!mUIScreen.allocate(resX, resY, GL_RGBA8)) - { - return false; - } - } - if (RenderFSAAType > 0) { // SMAA benefits from a stencil buffer shared across its passes so the @@ -1234,7 +1224,6 @@ void LLPipeline::refreshCachedSettings() // [SL:KB] - Patch: Settings-RenderResolutionMultiplier | Checked: Catznip-5.4 RenderResolutionMultiplier = gSavedSettings.getF32("RenderResolutionMultiplier"); // [/SL:KB] - RenderUIBuffer = gSavedSettings.getBOOL("RenderUIBuffer"); RenderShadowDetail = gSavedSettings.getS32("RenderShadowDetail"); RenderShadowSplits = gSavedSettings.getS32("RenderShadowSplits"); RenderDeferredSSAO = gSavedSettings.getBOOL("RenderDeferredSSAO"); @@ -1378,8 +1367,6 @@ void LLPipeline::releaseGLBuffers() mFXAAMap.release(); mSMAABlendBuffer.release(); - mUIScreen.release(); - mDownResMap.release(); mPreviewMap.release(); @@ -3263,7 +3250,7 @@ void LLPipeline::doOcclusion(LLCamera& camera) if (sReflectionProbesEnabled && sUseOcclusion > 1 && !LLPipeline::sShadowRender && !gCubeSnapshot) { - gGL.setColorMask(false, false); + LLGLSColorMask color_mask(false, false); LLGLDepthTest depth(GL_TRUE, GL_FALSE); LLGLDisable cull(GL_CULL_FACE); @@ -3278,8 +3265,6 @@ void LLPipeline::doOcclusion(LLCamera& camera) mReflectionMapManager.doOcclusion(); mHeroProbeManager.doOcclusion(); gOcclusionCubeProgram.unbind(); - - gGL.setColorMask(true, true); } if (LLPipeline::sUseOcclusion > 1 && @@ -3287,7 +3272,7 @@ void LLPipeline::doOcclusion(LLCamera& camera) { LLVertexBuffer::unbind(); - gGL.setColorMask(false, false); + LLGLSColorMask color_mask(false, false); LLGLDisable blend(GL_BLEND); gGL.getTextureSlot(0)->unbind(); @@ -3323,8 +3308,6 @@ void LLPipeline::doOcclusion(LLCamera& camera) vo_part->processOccluders(&camera); } } - - gGL.setColorMask(true, true); } } @@ -4756,18 +4739,15 @@ void LLPipeline::renderGeomPostDeferred(LLCamera& camera) LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; LL_PROFILE_GPU_ZONE("renderGeomPostDeferred"); - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - U32 cur_type = 0; LLGLEnable cull(GL_CULL_FACE); bool done_atmospherics = LLPipeline::sRenderingHUDs; //skip atmospherics on huds bool done_water_haze = done_atmospherics; - bool done_water_exclusion = false; + // and the water exclusion mask: the water pool and water haze, which read it, do not run + // on huds, and every pass that runs them builds the mask first + bool done_water_exclusion = LLPipeline::sRenderingHUDs; // do water exclusion just before water pass. U32 water_exclusion_pass = LLDrawPool::POOL_WATEREXCLUSION; @@ -4832,6 +4812,15 @@ void LLPipeline::renderGeomPostDeferred(LLCamera& camera) { LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred poolrender"); + // Wireframe is for the pools' geometry alone. The passes run between pools above + // are full-screen triangles, which in line mode shade only their edges, and the + // water exclusion would stencil only the outlines of its prims. + if (gUseWireframe) + { + gGL.flush(); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + gGLLastMatrix = NULL; gGL.loadMatrix(LLViewerCamera::getCurrent().getModelview()); @@ -4857,6 +4846,12 @@ void LLPipeline::renderGeomPostDeferred(LLCamera& camera) LLGLState::checkStates(GL_FALSE); } } + + if (gUseWireframe) + { + gGL.flush(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } } else { @@ -4894,11 +4889,6 @@ void LLPipeline::renderGeomPostDeferred(LLCamera& camera) ALFarPlaneOverlay::render(); } } - - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } } void LLPipeline::renderGeomShadow(LLCamera& camera) @@ -4991,8 +4981,14 @@ void LLPipeline::renderPhysicsDisplay() gGL.flush(); gDebugProgram.bind(); + // The passes below set their own masks; this hands the caller's back. + LLGLSColorMask color_mask(true, false); + + // Toward the camera: pass 0 lays the shapes' own depth down unoffset, and an outline + // pushed behind it fails the depth test everywhere the fill covers. setPolygonOffset + // flips the sign under reverse-Z itself. LLGLEnable polygon_offset_line(GL_POLYGON_OFFSET_LINE); - gGL.setPolygonOffset(3.f, 3.f); + gGL.setPolygonOffset(-3.f, -3.f); gGL.setLineWidth(3.f); LLGLEnable blend(GL_BLEND); gGL.setSceneBlendType(LLRender::BT_ALPHA); @@ -9092,7 +9088,11 @@ void LLPipeline::generateBloomHDR(LLRenderTarget* src) const F32 streak_strength = (no_post || bloom_strength_gate() <= 0.f) ? 0.f : llclamp(streak_strength_setting(), 0.f, CROSS_FILTER_MAX_STRENGTH); - const bool streaks_on = (streak_strength > 0.f) && gCrossFilterProgram.isComplete(); + // And only with a texture barrier: the chain reads quadrants of mWaterDis that the draw + // before it wrote while the texture is still attached, which GL defines only across one. + // glTextureBarrier is null where the driver has neither GL 4.5 nor a texture barrier + // extension, and there the streaks are off rather than undefined. + const bool streaks_on = (streak_strength > 0.f) && gCrossFilterProgram.isComplete() && glTextureBarrier; bool streaks_ready = false; if (streaks_on) @@ -9230,6 +9230,16 @@ void LLPipeline::generateBloomHDR(LLRenderTarget* src) 1.f / (F32)streak_h); gCrossFilterProgram.uniform1f(LLShaderMgr::CROSS_PASS_SCALE, scales[pass]); + // Passes 1 and 2 sample a quadrant the previous draw wrote while + // mWaterDis is still attached to the bound framebuffer, a read GL + // defines only across a texture barrier. The same barriers put each + // arm's reads of a scratch quadrant before the next arm's write to it. + // Never null here: streaks_on requires it. + if (src == &mWaterDis) + { + glTextureBarrier(); + } + mScreenTriangleVB->setBuffer(); mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); }; @@ -9508,6 +9518,11 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src) LLGLSLShader& edge_shader = gSMAAEdgeDetectProgram[fsaa_quality]; dest.bindTarget(); + if (use_stencil) + { + // The stencil clear writes only the bits the stencil write mask allows. + glStencilMask(0xFF); + } dest.clear(use_stencil ? (GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) : GL_COLOR_BUFFER_BIT); edge_shader.bind(); @@ -11566,7 +11581,8 @@ void LLPipeline::doAtmospherics() LLGLEnable blend(GL_BLEND); gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); - gGL.setColorMask(true, true); + // The caller's mask comes back for the pools drawn after the haze. + LLGLSColorMask color_mask(true, true); // apply haze LLGLSLShader& haze_shader = *gHazeProgram.selectVariant(); @@ -11631,7 +11647,8 @@ void LLPipeline::doWaterHaze() LLGLEnable blend(GL_BLEND); gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); - gGL.setColorMask(true, true); + // The caller's mask comes back for the pools drawn after the haze. + LLGLSColorMask color_mask(true, true); // apply haze LLGLSLShader& haze_shader = gHazeWaterProgram; @@ -11689,7 +11706,11 @@ void LLPipeline::doWaterExclusionMask() mWaterExclusionMask.bindTarget(); gGL.setClearColor(LLColor4::white); mWaterExclusionMask.clear(); - mWaterExclusionPool->render(); + // Made in init and kept to cleanup. Without it the mask stays cleared, excluding nothing. + if (mWaterExclusionPool) + { + mWaterExclusionPool->render(); + } mWaterExclusionMask.flush(); gGL.setClearColor(LLColor4::transparent); @@ -12621,7 +12642,9 @@ void LLPipeline::generateSunShadow(LLCamera& camera) LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED, END_RENDER_TYPES); - gGL.setColorMask(false, false); + // The caller's mask comes back on every exit, the early one for an empty point cloud + // included. + LLGLSColorMask color_mask(false, false); LLEnvironment& environment = LLEnvironment::instance(); @@ -13407,7 +13430,6 @@ void LLPipeline::generateSunShadow(LLCamera& camera) gGL.loadMatrix(proj[1]); gGL.matrixMode(LLRender::MM_MODELVIEW); } - gGL.setColorMask(true, true); gGLViewport[0] = saved_gl_viewport[0]; gGLViewport[1] = saved_gl_viewport[1]; @@ -13473,6 +13495,14 @@ void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments) // on a partially clipped draw, which is the one thing it must not be. LLGLDisable no_scissor(GL_SCISSOR_TEST); + // The depth and colour state display() gives a world render. No caller has it: the UI's + // LLGLSUIDefault turns depth testing and depth writes off, and the autotune caller runs + // from the main loop's work queue, outside display(), where neither is on. Without it + // the clear below leaves depth alone and the opaque pools draw untested, so the time + // measured is not the avatar's. + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_LEQUAL); + LLGLSColorMask color_mask(true, true); + // The globals generateImpostor overwrites are saved here rather than there, because the // display() path repairs them itself immediately afterwards and this path does not: it // returns into the middle of a UI draw. sCull in particular is left pointing at diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index 8e53f9bf15..0731c96719 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -907,9 +907,6 @@ class LLPipeline LLRenderTarget mFXAAMap; LLRenderTarget mSMAABlendBuffer; - // render ui to buffer target - LLRenderTarget mUIScreen; - // downres scratch space for GPU downscaling of textures LLRenderTarget mDownResMap; @@ -1325,7 +1322,6 @@ class LLPipeline // [SL:KB] - Patch: Settings-RenderResolutionMultiplier | Checked: Catznip-5.4 static F32 RenderResolutionMultiplier; // [/SL:KB] - static bool RenderUIBuffer; static S32 RenderShadowDetail; static S32 RenderShadowSplits; static bool RenderDeferredSSAO; diff --git a/indra/newview/skins/default/xui/en/panel_lightbox_lens.xml b/indra/newview/skins/default/xui/en/panel_lightbox_lens.xml index 4e0eb1fb71..cce1f4cf49 100644 --- a/indra/newview/skins/default/xui/en/panel_lightbox_lens.xml +++ b/indra/newview/skins/default/xui/en/panel_lightbox_lens.xml @@ -7,6 +7,8 @@ renderer is running: the two Bloom (HDR) sections, and the two Cross Filter sections that are seeded from the same bloom, while RenderHDREnabled is on, the two Glow (Legacy) sections while it is off (ALFloaterLightBox::refreshBloomSections, live as the setting changes). The +Cross Filter sections also need a texture barrier, and stay hidden on a driver +without one, where the renderer leaves the filter off. The rows also grey out on the same setting (enabled_control on Bloom and Cross Filter, disabled_control on Glow), which is all a build of this panel without the floater gets. (RenderBloomHDR is currently unwired renderer-side, so the fork diff --git a/indra/newview/skins/default/xui/pt/settings_comments.xml b/indra/newview/skins/default/xui/pt/settings_comments.xml index e2dec64506..1f6c1ca6f9 100644 --- a/indra/newview/skins/default/xui/pt/settings_comments.xml +++ b/indra/newview/skins/default/xui/pt/settings_comments.xml @@ -1839,18 +1839,10 @@ Escalar de ruído para esconder banding na renderização diferida. RenderDeferredSSAO Executar o shader de oclusão de ambiente em espaço de tela no renderizador diferido. - RenderDeferredSpotShadowBias - Valor de viés para sombras de spot (previne shadow acne). - RenderDeferredSpotShadowOffset - Valor de deslocamento para sombras de spot (previne shadow acne). RenderDeferredSun Executar o shader de luz solar no renderizador diferido. RenderDeferredSunWash Quanto as luzes locais são apagadas pelo sol. - RenderDeferredTreeShadowBias - Valor de viés para sombras de árvores (previne shadow acne). - RenderDeferredTreeShadowOffset - Valor de deslocamento para sombras de árvores (previne shadow acne). RenderDelayCreation Limitar a criação de drawables. RenderDelayVBUpdate @@ -2197,8 +2189,6 @@ Renderizar a água como transparente. Definir como falso renderiza a água como opaca com uma textura simples aplicada. RenderTreeLODFactor Controla o nível de detalhe da vegetação (multiplicador para a área de tela atual ao calcular o nível de detalhe). - RenderUIBuffer - Armazenar em cache a renderização da interface em um buffer alinhado à tela. RenderUIInSnapshot Exibir a interface do usuário na captura. RenderUnloadedAvatar