diff options
| author | Andrey Lihatskiy <alihatskiy@productengine.com> | 2026-08-13 04:19:15 +0300 |
|---|---|---|
| committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2026-08-13 04:19:15 +0300 |
| commit | f376c52ee2a22b3b58263d86566394e80626fc1a (patch) | |
| tree | 012d0585b39ef5cd1eac25f4f783d930ff50dff6 /indra/llrender | |
| parent | 21bb60fc65e7fa6c3cf472a29cda220f653af1d8 (diff) | |
| parent | e3a92a3f8e7d6df3772adcecb037149e6fcf0f3e (diff) | |
Merge branch 'geenz/linux-to-develop' into marchcat/lua-linux-develop
# Conflicts:
# .github/workflows/build.yaml
# indra/llinventory/llinventory.cpp
# indra/newview/CMakeLists.txt
# indra/newview/llappviewer.cpp
# indra/newview/llappviewerwin32.cpp
# indra/newview/llviewermenu.cpp
# indra/newview/llvoiceclient.cpp
# indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml
Diffstat (limited to 'indra/llrender')
| -rw-r--r-- | indra/llrender/llfontfreetype.cpp | 93 | ||||
| -rw-r--r-- | indra/llrender/llfontfreetype.h | 6 | ||||
| -rw-r--r-- | indra/llrender/llfontgl.cpp | 14 | ||||
| -rw-r--r-- | indra/llrender/llgl.cpp | 11 | ||||
| -rw-r--r-- | indra/llrender/llimagegl.cpp | 150 | ||||
| -rw-r--r-- | indra/llrender/llimagegl.h | 2 | ||||
| -rw-r--r-- | indra/llrender/llrender.cpp | 6 | ||||
| -rw-r--r-- | indra/llrender/llshadermgr.cpp | 90 | ||||
| -rw-r--r-- | indra/llrender/lluiimage.cpp | 211 | ||||
| -rw-r--r-- | indra/llrender/lluiimage.h | 126 | ||||
| -rw-r--r-- | indra/llrender/lluiimage.inl | 80 | ||||
| -rw-r--r-- | indra/llrender/llvertexbuffer.cpp | 13 |
12 files changed, 717 insertions, 85 deletions
diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index cc4d6ad5ef..5af6b61513 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -110,6 +110,7 @@ LLFontManager::~LLFontManager() LLFontGlyphInfo::LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type) : mGlyphIndex(index), mGlyphType(glyph_type), + mChar(0), mWidth(0), // In pixels mHeight(0), // In pixels mXAdvance(0.f), // In pixels @@ -127,6 +128,7 @@ LLFontGlyphInfo::LLFontGlyphInfo(U32 index, EFontGlyphType glyph_type) LLFontGlyphInfo::LLFontGlyphInfo(const LLFontGlyphInfo& fgi) : mGlyphIndex(fgi.mGlyphIndex) , mGlyphType(fgi.mGlyphType) + , mChar(fgi.mChar) , mWidth(fgi.mWidth) , mHeight(fgi.mHeight) , mXAdvance(fgi.mXAdvance) @@ -151,7 +153,8 @@ LLFontFreetype::LLFontFreetype() mFTFace(nullptr), mRenderGlyphCount(0), mStyle(0), - mPointSize(0) + mPointSize(0), + mMaxDigitWidth(0.0f) { } @@ -347,6 +350,10 @@ F32 LLFontFreetype::getXAdvance(llwchar wch) const LLFontGlyphInfo* gi = getGlyphInfo(wch, EFontGlyphType::Unspecified); if (gi) { + if (wch >= '0' && wch <= '9' && mMaxDigitWidth > 0.0f) + { + return mMaxDigitWidth; + } return gi->mXAdvance; } else @@ -367,6 +374,12 @@ F32 LLFontFreetype::getXAdvance(const LLFontGlyphInfo* glyph) const if (mFTFace == nullptr) return 0.0; + // Use max digit width for tabular numbers + if (mWeight > 0 && glyph->mChar >= '0' && glyph->mChar <= '9' && mMaxDigitWidth > 0.0f) + { + return mMaxDigitWidth; + } + return glyph->mXAdvance; } @@ -388,8 +401,27 @@ F32 LLFontFreetype::getXKerning(const LLFontGlyphInfo* left_glyph_info, const LL if (mFTFace == nullptr) return 0.0; - U32 left_glyph = left_glyph_info ? left_glyph_info->mGlyphIndex : 0; - U32 right_glyph = right_glyph_info ? right_glyph_info->mGlyphIndex : 0; + U32 left_glyph = 0; + U32 right_glyph = 0; + + if (left_glyph_info) + { + if (mWeight > 0 && left_glyph_info->mChar >= '0' && left_glyph_info->mChar <= '9') + { + // Disable kerning for digits when using tabular numbers + return 0.0; + } + left_glyph = left_glyph_info->mGlyphIndex; + } + if (right_glyph_info) + { + if (mWeight > 0 && right_glyph_info->mChar >= '0' && right_glyph_info->mChar <= '9') + { + // Disable kerning for digits when using tabular numbers + return 0.0; + } + right_glyph = right_glyph_info->mGlyphIndex; + } FT_Vector delta; @@ -589,6 +621,7 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l mFontBitmapCachep->nextOpenPos(width, pos_x, pos_y, bitmap_glyph_type, bitmap_num); LLFontGlyphInfo* gi = new LLFontGlyphInfo(glyph_index, requested_glyph_type); + gi->mChar = wch; gi->mXBitmapOffset = pos_x; gi->mYBitmapOffset = pos_y; gi->mBitmapEntry = std::make_pair(bitmap_glyph_type, bitmap_num); @@ -605,6 +638,14 @@ LLFontGlyphInfo* LLFontFreetype::addGlyphFromFont(const LLFontFreetype *fontp, l gi->mXAdvance = fontp->mFTFace->glyph->advance.x / 64.f; gi->mYAdvance = fontp->mFTFace->glyph->advance.y / 64.f; + if (mWeight > 0 && wch >= '0' && wch <= '9') + { + // Digits are supposed to be preloaded, and buffers + // refresh when new chars get added, so this lazy load + // should not cause any issues. + mMaxDigitWidth = llmax(mMaxDigitWidth, gi->mXAdvance); + } + insertGlyphInfo(wch, gi); if (requested_glyph_type != bitmap_glyph_type) @@ -761,7 +802,50 @@ void LLFontFreetype::renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index, ll llassert_always_msg(FT_Err_Ok == error, message.c_str()); } - llassert_always(! FT_Render_Glyph(mFTFace->glyph, gFontRenderMode) ); + // TODO: Make this more sturdy, make asserts/ll_errs conditional + // to non-critical characters. + // Temporarily leaving them for data gathering, but unicode chars + // like emojis should not cause the app to crash and should either + // fallback to some predetermined bitmap or simply return. + + // Verify glyph slot is valid + if (!mFTFace->glyph) + { + LL_ERRS() << "FT_Load_Glyph succeeded but glyph slot is null for wchar " << llformat("U+%xu", U32(wch)) << LL_ENDL; + return; + } + + // Check if bitmap buffer is already allocated + // It can potentially be preallocated for: + // 1. SVG/color glyphs rendered by FreeType's SVG_RendererHooks + // 2. Embedded bitmap fonts + // 3. Some Color emoji that use FT_LOAD_COLOR + if (!mFTFace->glyph->bitmap.buffer) + { + error = FT_Render_Glyph(mFTFace->glyph, gFontRenderMode); + if (error != FT_Err_Ok) + { + std::string render_message = llformat( + "Error %d (%s) rendering wchar %u glyph %u: format=%lu, pixel_mode=%d, render_mode=%d", + error, FT_Error_String(error), wch, glyph_index, + (unsigned long)mFTFace->glyph->format, mFTFace->glyph->bitmap.pixel_mode, gFontRenderMode); + + // Try with FT_RENDER_MODE_NORMAL as fallback + if (gFontRenderMode != FT_RENDER_MODE_NORMAL) + { + LL_WARNS_ONCE() << render_message << LL_ENDL; + error = FT_Render_Glyph(mFTFace->glyph, FT_RENDER_MODE_NORMAL); + if (error != FT_Err_Ok) + { + LL_ERRS() << "Fallback to FT_RENDER_MODE_NORMAL failed. " << render_message << LL_ENDL; + } + } + else + { + LL_ERRS() << render_message << LL_ENDL; + } + } + } mRenderGlyphCount++; } @@ -797,6 +881,7 @@ void LLFontFreetype::resetBitmapCache() } mCharGlyphInfoMap.clear(); mFontBitmapCachep->reset(); + mMaxDigitWidth = 0.0f; // Adding default glyph is skipped for fallback fonts here as well as in loadFace(). // This if was added as fix for EXT-4971. diff --git a/indra/llrender/llfontfreetype.h b/indra/llrender/llfontfreetype.h index 12a4052cae..3191ca8df7 100644 --- a/indra/llrender/llfontfreetype.h +++ b/indra/llrender/llfontfreetype.h @@ -77,6 +77,8 @@ struct LLFontGlyphInfo U32 mGlyphIndex; EFontGlyphType mGlyphType; + llwchar mChar; + // Metrics S32 mWidth; // In pixels @@ -148,6 +150,9 @@ public: F32 getXKerning(llwchar char_left, llwchar char_right) const; // Get the kerning between the two characters F32 getXKerning(const LLFontGlyphInfo* left_glyph_info, const LLFontGlyphInfo* right_glyph_info) const; // Get the kerning between the two characters + F32 getMaxDigitWidth() const { return mMaxDigitWidth; } + S32 getFontWeight() const { return mWeight; } + LLFontGlyphInfo* getGlyphInfo(llwchar wch, EFontGlyphType glyph_type) const; void reset(F32 vert_dpi, F32 horz_dpi); @@ -186,6 +191,7 @@ private: F32 mAscender; F32 mDescender; F32 mLineHeight; + mutable F32 mMaxDigitWidth; LLFT_Face mFTFace; diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 5d99c35047..bb5a12375e 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -345,6 +345,14 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons break; } + // Calculate horizontal offset for tabular numbers (center narrow digits) + F32 x_offset = 0.0f; + if (mFontFreetype->getFontWeight() > 0 && fgi->mChar >= '0' && fgi->mChar <= '9' && mFontFreetype->getMaxDigitWidth() > 0.0f) + { + // use mXAdvance directly here, since we don't want to get max width instead. + x_offset = (mFontFreetype->getMaxDigitWidth() - fgi->mXAdvance) * 0.5f; + } + // Draw the text at the appropriate location //Specify vertices and texture coordinates LLRectf uv_rect((fgi->mXBitmapOffset) * inv_width, @@ -352,9 +360,9 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons (fgi->mXBitmapOffset + fgi->mWidth) * inv_width, (fgi->mYBitmapOffset - PAD_UVY) * inv_height); // snap glyph origin to whole screen pixel - LLRectf screen_rect((F32)ll_round(cur_render_x + (F32)fgi->mXBearing), + LLRectf screen_rect((F32)ll_round(cur_render_x + (F32)fgi->mXBearing + x_offset), (F32)ll_round(cur_render_y + (F32)fgi->mYBearing), - (F32)ll_round(cur_render_x + (F32)fgi->mXBearing) + (F32)fgi->mWidth, + (F32)ll_round(cur_render_x + (F32)fgi->mXBearing + x_offset) + (F32)fgi->mWidth, (F32)ll_round(cur_render_y + (F32)fgi->mYBearing) - (F32)fgi->mHeight); if (glyph_count >= GLYPH_BATCH_SIZE) @@ -375,7 +383,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons col, style_to_add, shadow, drop_shadow_strength); chars_drawn++; - cur_x += fgi->mXAdvance; + cur_x += mFontFreetype->getXAdvance(fgi); cur_y += fgi->mYAdvance; llwchar next_char = wstr[i+1]; diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 5357a79bae..8545f634a3 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -1042,11 +1042,6 @@ void LLGLManager::initWGL() { LL_WARNS("RenderInit") << "No ARB WGL PBuffer extensions" << LL_ENDL; } - - if(!mGLExtensions.contains("WGL_ARB_render_texture")) - { - LL_WARNS("RenderInit") << "No ARB WGL render texture extensions" << LL_ENDL; - } #endif } @@ -1264,7 +1259,7 @@ bool LLGLManager::initGL() } if (mVRAM != 0) { - LL_WARNS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL; + LL_INFOS("RenderInit") << "VRAM Detected (AMDAssociations):" << mVRAM << LL_ENDL; } } else #endif @@ -1279,7 +1274,7 @@ bool LLGLManager::initGL() if (mVRAM != 0) { - LL_WARNS("RenderInit") << "VRAM Detected (GLXMesaQueryRenderer):" << mVRAM << LL_ENDL; + LL_INFOS("RenderInit") << "VRAM Detected (GLXMesaQueryRenderer):" << mVRAM << LL_ENDL; } } } @@ -1295,7 +1290,7 @@ bool LLGLManager::initGL() if (mVRAM != 0) { - LL_WARNS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL; + LL_INFOS("RenderInit") << "VRAM Detected (NVXGpuMemoryInfo):" << mVRAM << LL_ENDL; } } diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 868284031d..fafc92b5d0 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -540,6 +540,7 @@ void LLImageGL::init(bool usemipmaps, bool allow_compression) mIsMask = false; mNeedsAlphaAndPickMask = true ; + mAlphaAnalysisSerial = 0; mAlphaStride = 0 ; mAlphaOffset = 0 ; @@ -1945,6 +1946,9 @@ void LLImageGL::destroyGLTexture() mTexName = 0; mGLTextureCreated = false ; } + + // Invalidate pending jobs + ++mAlphaAnalysisSerial; } //force to invalidate the gl texture, most likely a sculpty texture @@ -2104,6 +2108,9 @@ void LLImageGL::setNeedsAlphaAndPickMask(bool need_mask) { mAlphaOffset = INVALID_OFFSET ; mIsMask = false; + + // Invalidate pending jobs + ++mAlphaAnalysisSerial; } } } @@ -2181,11 +2188,16 @@ void LLImageGL::calcAlphaChannelOffsetAndStride() } } -void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h) +bool LLImageGL::analyzeAlphaData( + const void* data_in, + U32 w, + U32 h, + S8 alpha_offset, + S8 alpha_stride) { - if(!data_in || sSkipAnalyzeAlpha || !mNeedsAlphaAndPickMask) + if (!data_in) { - return ; + return false; } LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; @@ -2194,7 +2206,7 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h) U32 alphatotal = 0; U32 sample[16]; - memset(sample, 0, sizeof(U32)*16); + memset(sample, 0, sizeof(U32) * 16); // generate histogram of quantized alpha. // also add-in the histogram of a 2x2 box-sampled version. The idea is @@ -2205,46 +2217,46 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h) { llassert(w % 2 == 0); llassert(h % 2 == 0); - const GLubyte* rowstart = ((const GLubyte*) data_in) + mAlphaOffset; + const GLubyte* rowstart = ((const GLubyte*)data_in) + alpha_offset; for (U32 y = 0; y < h; y += 2) { - const GLubyte* current = rowstart; + const unsigned char* current = rowstart; for (U32 x = 0; x < w; x += 2) { const U32 s1 = current[0]; alphatotal += s1; - const U32 s2 = current[w * mAlphaStride]; + const U32 s2 = current[w * alpha_stride]; alphatotal += s2; - current += mAlphaStride; + current += alpha_stride; const U32 s3 = current[0]; alphatotal += s3; - const U32 s4 = current[w * mAlphaStride]; + const U32 s4 = current[w * alpha_stride]; alphatotal += s4; - current += mAlphaStride; + current += alpha_stride; - ++sample[s1/16]; - ++sample[s2/16]; - ++sample[s3/16]; - ++sample[s4/16]; + ++sample[s1 / 16]; + ++sample[s2 / 16]; + ++sample[s3 / 16]; + ++sample[s4 / 16]; - const U32 asum = (s1+s2+s3+s4); + const U32 asum = (s1 + s2 + s3 + s4); alphatotal += asum; - sample[asum/(16*4)] += 4; + sample[asum / (16 * 4)] += 4; } - rowstart += 2 * w * mAlphaStride; + rowstart += 2 * w * alpha_stride; } length *= 2; // we sampled everything twice, essentially } else { - const GLubyte* current = ((const GLubyte*) data_in) + mAlphaOffset; + const unsigned char* current = ((const unsigned char*)data_in) + alpha_offset; for (U32 i = 0; i < length; i++) { const U32 s1 = *current; alphatotal += s1; - ++sample[s1/16]; - current += mAlphaStride; + ++sample[s1 / 16]; + current += alpha_stride; } } @@ -2271,15 +2283,103 @@ void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h) upperhalftotal += sample[i]; } - if (midrangetotal > length/48 || // lots of midrange, or - (lowerhalftotal == length && alphatotal != 0) || // all close to transparent but not all totally transparent, or - (upperhalftotal == length && alphatotal != 255*length)) // all close to opaque but not all totally opaque + if (midrangetotal > length / 48 || + (lowerhalftotal == length && alphatotal != 0) || + (upperhalftotal == length && alphatotal != 255 * length)) { - mIsMask = false; // not suitable for masking + return false; // not suitable for masking } else { - mIsMask = true; + return true; // is a mask + } +} + +void LLImageGL::analyzeAlpha(const void* data_in, U32 w, U32 h) +{ + // if mNeedsAlphaAndPickMask is true, then offset and stride are supposed to be valid. + if (!data_in || sSkipAnalyzeAlpha || !mNeedsAlphaAndPickMask) + return; + + // Already on a worker thread or a small image - analyze immediately + if (!on_main_thread() || (w < 64 && h < 64)) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Should have been incremented by destroyGLTexture, + // but increment either way, for extra safety. + ++mAlphaAnalysisSerial; + + mIsMask = analyzeAlphaData(data_in, w, h, mAlphaOffset, mAlphaStride); + return; + } + + // On main thread - defer to worker thread + + // Capture context + const S8 alpha_offset = mAlphaOffset; + const S8 alpha_stride = mAlphaStride; + const U32 request_serial = ++mAlphaAnalysisSerial; + + // Copy data for worker thread + const size_t data_size = size_t(w) * size_t(h) * size_t(alpha_stride); + U8* data_copy = new (std::nothrow) U8[data_size]; + + if (!data_copy) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + mIsMask = analyzeAlphaData(data_in, w, h, alpha_offset, alpha_stride); + return; + } + memcpy(data_copy, static_cast<const U8*>(data_in), data_size); + + // Viewer can rapidly switch between lods, which would invalidate + // previous analysis results. + // Use a serial number to filter out obsolete analysis results. + + ref(); // Keep texture alive + + auto mainq = mMainQueue.lock(); + + // Get the GL thread queue + auto workerq = LLImageGLThread::sEnabledTextures ? + LL::WorkQueue::getInstance("LLImageGL") : // Use the image processing queue if available + LL::WorkQueue::getInstance("General"); // Fallback to general + + bool posted_job = false; + if (mainq && workerq) + { + posted_job = mainq->postTo( + workerq, + // Worker thread: analyze alpha + [data_copy, w, h, alpha_offset, alpha_stride]() -> bool + { + LL_PROFILE_ZONE_NAMED("Deffered alpha mask analysis"); + bool is_mask = LLImageGL::analyzeAlphaData(data_copy, w, h, alpha_offset, alpha_stride); + delete[] data_copy; + return is_mask; + }, + // Main thread: apply result + [this, request_serial](bool is_mask) + { + // Only apply if no newer analysis has been requested + if (mAlphaAnalysisSerial == request_serial) + { + mIsMask = is_mask; + } + unref(); + } + ); + + // Conservative default until analysis completes + mIsMask = false; + } + if (!posted_job) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Queues not available - fall back to synchronous analysis + delete[] data_copy; + mIsMask = analyzeAlphaData(data_in, w, h, mAlphaOffset, mAlphaStride); + unref(); } } diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 773b647d69..0e186ca833 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -111,6 +111,7 @@ public: protected: virtual ~LLImageGL(); + static bool analyzeAlphaData(const void* data_in, U32 w, U32 h, S8 alpha_offset, S8 alpha_stride); void analyzeAlpha(const void* data_in, U32 w, U32 h); void calcAlphaChannelOffsetAndStride(); @@ -242,6 +243,7 @@ private: bool mIsMask; bool mNeedsAlphaAndPickMask; + LLAtomicU32 mAlphaAnalysisSerial; // for request tracking. S8 mAlphaStride ; S8 mAlphaOffset ; diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 8bbc9a8176..6a39a34c4e 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -968,7 +968,7 @@ void LLRender::syncMatrices() shader->uniformMatrix3fv(LLShaderMgr::NORMAL_MATRIX, 1, GL_FALSE, norm_mat); } - if (shader->getUniformLocation(LLShaderMgr::INVERSE_MODELVIEW_MATRIX)) + if (shader->getUniformLocation(LLShaderMgr::INVERSE_MODELVIEW_MATRIX) >= 0) { shader->uniformMatrix4fv(LLShaderMgr::INVERSE_MODELVIEW_MATRIX, 1, GL_FALSE, glm::value_ptr(cached_inv_mdv)); } @@ -1000,14 +1000,14 @@ void LLRender::syncMatrices() // GZ: This was previously disabled seemingly due to a bug involving the deferred renderer's regular pushing and popping of mats. // We're reenabling this and cleaning up the code around that - that would've been the appropriate course initially. // Anything beyond the standard proj and inv proj mats are special cases. Please setup special uniforms accordingly in the future. - if (shader->getUniformLocation(LLShaderMgr::INVERSE_PROJECTION_MATRIX)) + if (shader->getUniformLocation(LLShaderMgr::INVERSE_PROJECTION_MATRIX) >= 0) { glm::mat4 inv_proj = glm::inverse(mat); shader->uniformMatrix4fv(LLShaderMgr::INVERSE_PROJECTION_MATRIX, 1, false, glm::value_ptr(inv_proj)); } // Used by some full screen effects - such as full screen lights, glow, etc. - if (shader->getUniformLocation(LLShaderMgr::IDENTITY_MATRIX)) + if (shader->getUniformLocation(LLShaderMgr::IDENTITY_MATRIX) >= 0) { shader->uniformMatrix4fv(LLShaderMgr::IDENTITY_MATRIX, 1, GL_FALSE, glm::value_ptr(glm::identity<glm::mat4>())); } diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index d8a620cf5b..9f7c244fe9 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1085,7 +1085,14 @@ void LLShaderMgr::persistShaderCacheMetadata() return; } - LL_INFOS("ShaderMgr") << "Persisting shader cache metadata to disk" << LL_ENDL; + if (mShaderCacheDir.empty() || !LLFile::isdir(mShaderCacheDir)) + { + LL_WARNS("ShaderMgr") << "Invalid shader cache directory: " << mShaderCacheDir << LL_ENDL; + return; + } + + size_t total_entries = mShaderBinaryCache.size(); + LL_INFOS("ShaderMgr") << "Persisting shader " << (S32)total_entries << " cache metadata entries to disk" << LL_ENDL; LLSD out; // Settings and shader cache get saved at different time, thus making @@ -1096,6 +1103,8 @@ void LLShaderMgr::persistShaderCacheMetadata() out["shaders"] = LLSD::emptyMap(); LLSD &shaders = out["shaders"]; + size_t removed = 0; + static const F32 LRU_TIME = (60.f * 60.f) * 24.f * 7.f; // 14 days const F32 current_time = (F32)LLTimer::getTotalSeconds(); for (auto it = mShaderBinaryCache.begin(); it != mShaderBinaryCache.end();) @@ -1104,8 +1113,9 @@ void LLShaderMgr::persistShaderCacheMetadata() if ((shader_metadata.mLastUsedTime + LRU_TIME) < current_time) { std::string shader_path = gDirUtilp->add(mShaderCacheDir, it->first.asString() + ".shaderbin"); - LLFile::remove(shader_path); + LLFile::remove(shader_path, ENOENT); it = mShaderBinaryCache.erase(it); + removed++; } else { @@ -1119,14 +1129,32 @@ void LLShaderMgr::persistShaderCacheMetadata() } std::string meta_out_path = gDirUtilp->add(mShaderCacheDir, "shaderdata.llsd"); + if (shaders.size() == 0) + { + LL_WARNS("ShaderMgr") << "No shader cache entries to persist, removing cache metadata file" << LL_ENDL; + LLFile::remove(meta_out_path); + return; + } + llofstream outstream(meta_out_path, std::ios_base::out | std::ios_base::binary); if (!outstream.is_open()) { LL_WARNS("ShaderMgr") << "Failed to open file. Unable to save shader cache to: " << mShaderCacheDir << LL_ENDL; return; } + LLSDSerialize::toBinary(out, outstream); + if (outstream.fail()) + { + LL_WARNS("ShaderMgr") << "Failed to serialize shader cache metadata" << LL_ENDL; + outstream.close(); + LLFile::remove(meta_out_path); // Clean up partial write + return; + } outstream.close(); + + LL_INFOS("ShaderMgr") << "Persisted " << (S32)shaders.size() + << " entries. Removed " << (S32)removed << " entries." << LL_ENDL; } bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader) @@ -1140,34 +1168,56 @@ bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader) { std::string in_path = gDirUtilp->add(mShaderCacheDir, shader->mShaderHash.asString() + ".shaderbin"); auto& shader_info = binary_iter->second; - if (shader_info.mBinaryLength > 0) + + try { - std::vector<U8> in_data; - in_data.resize(shader_info.mBinaryLength); - std::error_code ec; - LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec); - if (!ec && (bool)filep) + constexpr GLsizei MAX_SHADER_BINARY_SIZE = 1024 * 1024; // 1 MB, normally around 10KB + if (shader_info.mBinaryLength > 0 && shader_info.mBinaryLength <= MAX_SHADER_BINARY_SIZE) { - size_t result = filep.read(in_data.data(), in_data.size(), ec); - filep.close(); + std::vector<U8> in_data; + in_data.resize(shader_info.mBinaryLength); - if (result == in_data.size()) + std::error_code ec; + LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec); + if (!ec && (bool)filep) { - GLenum error = glGetError(); // Clear current error - glProgramBinary(shader->mProgramObject, shader_info.mBinaryFormat, in_data.data(), shader_info.mBinaryLength); + size_t result = filep.read(in_data.data(), in_data.size(), ec); + filep.close(); + + if (result == in_data.size()) + { + GLenum error = glGetError(); // Clear current error + glProgramBinary(shader->mProgramObject, shader_info.mBinaryFormat, in_data.data(), shader_info.mBinaryLength); - error = glGetError(); - GLint success = GL_TRUE; - glGetProgramiv(shader->mProgramObject, GL_LINK_STATUS, &success); - if (error == GL_NO_ERROR && success == GL_TRUE) + error = glGetError(); + GLint success = GL_TRUE; + glGetProgramiv(shader->mProgramObject, GL_LINK_STATUS, &success); + if (error == GL_NO_ERROR && success == GL_TRUE) + { + binary_iter->second.mLastUsedTime = (F32)LLTimer::getTotalSeconds(); + LL_INFOS() << "Loaded cached binary for shader: " << shader->mName << LL_ENDL; + return true; + } + } + else { - binary_iter->second.mLastUsedTime = (F32)LLTimer::getTotalSeconds(); - LL_INFOS() << "Loaded cached binary for shader: " << shader->mName << LL_ENDL; - return true; + LL_WARNS("ShaderMgr") << "Incomplete read of shader binary. Expected: " + << in_data.size() << ", read: " << result << LL_ENDL; } } } } + catch (const std::bad_alloc&) + { + LL_WARNS("ShaderMgr") << "Failed to allocate memory for shader binary (" + << shader_info.mBinaryLength << " bytes) for: " + << shader->mName << LL_ENDL; + } + catch (const std::exception& err) + { + LL_WARNS("ShaderMgr") << "Caught exception " << err.what() << " while loading shader binary for: " << shader->mName << LL_ENDL; + } + //an error occured, normally we would print log but in this case it means the shader needs recompiling. LL_INFOS() << "Failed to load cached binary for shader: " << shader->mName << " falling back to compilation" << LL_ENDL; LLFile::remove(in_path); diff --git a/indra/llrender/lluiimage.cpp b/indra/llrender/lluiimage.cpp index dc18bf16bf..d0b6bcbf66 100644 --- a/indra/llrender/lluiimage.cpp +++ b/indra/llrender/lluiimage.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -31,6 +31,12 @@ // Project includes #include "lluiimage.h" +#include <algorithm> + +// Static member initialization +std::vector<LLPointer<LLUIImage> > LLUIImage::sImageList; +size_t LLUIImage::sCleanupIndex = 0; +bool LLUIImage::sEnableDisplayListsCollection = true; LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image) : mName(name), @@ -49,6 +55,12 @@ LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image) LLUIImage::~LLUIImage() { delete mImageLoaded; + + // Unregister from global cleanup list (sanity check) + // But it's supposed to be cleared already, else we wouldn't + // be destructing this object. + unregisterFromGlobalCleanup(); + mDisplayLists.clear(); } S32 LLUIImage::getWidth() const @@ -63,6 +75,199 @@ S32 LLUIImage::getHeight() const return ll_round((F32)mImage->getHeight(0) * mClipRegion.getHeight()); } +buffer_data_list_t* LLUIImage::findDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const +{ + LLImageGL* gl_image = mImage->getGLTexture(); + if (!gl_image) + { + return nullptr; + } + + LLVector3 ui_translation = gGL.getUITranslation(); + LLVector3 ui_scale = gGL.getUIScale(); + LLGLuint tex_name = gl_image->getTexName(); + + auto key = PackedKey::create(x, y, width, height, color, solid_color, ui_translation, ui_scale, tex_name); + + auto it = mDisplayLists.find(key); + if (it != mDisplayLists.end()) + { + it->second.last_used = std::chrono::steady_clock::now(); + return &it->second.list; + } + return nullptr; +} + +buffer_data_list_t* LLUIImage::genDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const +{ + LL_PROFILE_ZONE_SCOPED; + + LLImageGL* gl_image = mImage->getGLTexture(); + if (!gl_image) + { + // Don't cache when texture hasn't been created yet + // draw just aborts in this case, so don't draw either. + return nullptr; + } + + LLVector3 ui_translation = gGL.getUITranslation(); + LLVector3 ui_scale = gGL.getUIScale(); + + // Get the GL texture name - this uniquely identifies the current texture state + // including discard level changes, texture recreation, etc. + LLGLuint tex_name = gl_image->getTexName(); + + auto key = PackedKey::create(x, y, width, height, color, solid_color, ui_translation, ui_scale, tex_name); + + CachedDisplayList cached; + cached.last_used = std::chrono::steady_clock::now(); + + // Generate the display list by capturing the draw commands + gGL.beginList(&cached.list); + + gl_draw_scaled_image_with_border( + x, y, + width, height, + mImage, + color, + solid_color, + mClipRegion, + mScaleRegion, + mScaleStyle == SCALE_INNER); + + gGL.endList(); + + // Insert into cache + // emplace, since we only call genDisplayList if key was not found. + auto result = mDisplayLists.emplace(key, std::move(cached)); + + // Register for cleanup on first buffer creation + if (mDisplayLists.size() == 1) + { + sImageList.push_back(const_cast<LLUIImage*>(this)); + } + + return &result.first->second.list; +} + +void LLUIImage::invalidateDisplayLists() +{ + mDisplayLists.clear(); + + unregisterFromGlobalCleanup(); +} + +void LLUIImage::cleanupDisplayLists() +{ + if (mDisplayLists.empty()) + { + llassert(false); //it shouldn't be in this list + unregisterFromGlobalCleanup(); + // marks current position for a recheck. Increments after cleanupDisplayLists. + if (sCleanupIndex > 0) + { + sCleanupIndex--; + } + else if (sImageList.empty()) + { + sCleanupIndex = 0; + } + else + { + sCleanupIndex = sImageList.size() - 1; + } + return; + } + + // Time threshold for cleaning up unused display lists (global cleanup) + constexpr std::chrono::seconds DISPLAY_LIST_TIMEOUT{ 2 }; + auto now = std::chrono::steady_clock::now(); + + // Remove display lists that haven't been used recently + for (auto it = mDisplayLists.begin(); it != mDisplayLists.end(); ) + { + if (now - it->second.last_used > DISPLAY_LIST_TIMEOUT) + { + it = mDisplayLists.erase(it); + } + else + { + ++it; + } + } + + // Unregister from cleanup list if all display lists were removed + if (mDisplayLists.empty()) + { + unregisterFromGlobalCleanup(); + // marks current position for a recheck. Increments after cleanupDisplayLists. + if (sCleanupIndex > 0) + { + sCleanupIndex--; + } + else if (sImageList.empty()) + { + sCleanupIndex = 0; + } + else + { + sCleanupIndex = sImageList.size() - 1; + } + } +} + +void LLUIImage::unregisterFromGlobalCleanup() +{ + auto list_it = std::find(sImageList.begin(), sImageList.end(), this); + if (list_it != sImageList.end()) + { + // Swap with last element and pop (O(1) removal) + *list_it = sImageList.back(); + sImageList.pop_back(); + } +} + +// static +void LLUIImage::updateClass() +{ + if (sImageList.empty()) + { + return; + } + + // Clean up a batch of images each frame to amortize the cost + // ensuring all images are checked regularly + // Note: buffers often get obsolete in batches, perhaps + // increase rate of cleanup after a buffer was removed? + // and decrease rate if no buffer were removed and creates + // for a while? + constexpr size_t BATCH_SIZE = 8; + size_t images_to_process = std::min(BATCH_SIZE, sImageList.size()); + + for (size_t i = 0; i < images_to_process; ++i) + { + if (sCleanupIndex >= sImageList.size()) + { + sCleanupIndex = 0; + } + + sImageList[sCleanupIndex]->cleanupDisplayLists(); + ++sCleanupIndex; + } +} + +void LLUIImage::cleanupClass() +{ + std::vector<LLPointer<LLUIImage> > list_copy(sImageList); + sImageList.clear(); + for (LLUIImage* image : list_copy) + { + // invalidateDisplayLists will attempt to clear sImageList + image->invalidateDisplayLists(); + } + sCleanupIndex = 0; +} + void LLUIImage::draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, const LLVector3& y_axis, const LLRect& rect, const LLColor4& color) { @@ -77,7 +282,7 @@ void LLUIImage::draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, c } else { - border_scale = (F32)rect.getWidth() / border_width; + border_scale = (F32)rect.getWidth() / border_width; } } @@ -124,6 +329,8 @@ void LLUIImage::onImageLoaded() { (*mImageLoaded)(); } + + invalidateDisplayLists(); } namespace LLInitParam diff --git a/indra/llrender/lluiimage.h b/indra/llrender/lluiimage.h index 7dde84f295..0998ac17b4 100644 --- a/indra/llrender/lluiimage.h +++ b/indra/llrender/lluiimage.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -34,10 +34,15 @@ #include "llinitparam.h" #include "lltexture.h" #include "llrender2dutils.h" +#include "llvertexbuffer.h" #include <boost/signals2.hpp> +#include <bit> +#include <chrono> #include <type_traits> +#include <unordered_map> +#include <vector> extern const LLColor4 UI_VERTEX_COLOR; @@ -58,21 +63,28 @@ public: LL_FORCE_INLINE void setClipRegion(const LLRectf& region) { mClipRegion = region; + // This happens when image becomes loaded + invalidateDisplayLists(); } LL_FORCE_INLINE void setScaleRegion(const LLRectf& region) { mScaleRegion = region; + // This happens when image becomes loaded + invalidateDisplayLists(); } LL_FORCE_INLINE void setScaleStyle(EScaleStyle style) { mScaleStyle = style; + // This happens when image becomes loaded + invalidateDisplayLists(); } LL_FORCE_INLINE LLPointer<LLTexture> getImage() { return mImage; } LL_FORCE_INLINE const LLPointer<LLTexture>& getImage() const { return mImage; } + LL_FORCE_INLINE void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const; LL_FORCE_INLINE void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color = UI_VERTEX_COLOR) const; LL_FORCE_INLINE void draw(S32 x, S32 y, const LLColor4& color = UI_VERTEX_COLOR) const; LL_FORCE_INLINE void draw(const LLRect& rect, const LLColor4& color = UI_VERTEX_COLOR) const { draw(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); } @@ -85,6 +97,10 @@ public: LL_FORCE_INLINE void drawBorder(const LLRect& rect, const LLColor4& color, S32 border_width) const { drawBorder(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color, border_width); } LL_FORCE_INLINE void drawBorder(S32 x, S32 y, const LLColor4& color, S32 border_width) const { drawBorder(x, y, getWidth(), getHeight(), color, border_width); } + // Note: draw3D is not cached with display lists because it uses world-space rendering + // with dynamic transforms (gl_segmented_rect_3d_tex). These calls are infrequent and + // highly dynamic, making caching ineffective. The 2D UI methods benefit from caching + // because they're called many times per frame with the same dimensions. void draw3D(const LLVector3& origin_agent, const LLVector3& x_axis, const LLVector3& y_axis, const LLRect& rect, const LLColor4& color); LL_FORCE_INLINE const std::string& getName() const { return mName; } @@ -100,7 +116,107 @@ public: void onImageLoaded(); + // Global cleanup of unused display lists across all LLUIImage instances + // Should be called periodically (e.g., once per frame or when memory pressure is detected) + static void updateClass(); + static void cleanupClass(); + + static void enableDisplayListsCollection(bool enable) { sEnableDisplayListsCollection = enable; } + protected: + // Packed key for identifying unique display list configurations + struct PackedKey + { + uint64_t position; // x and y coordinates (32 bits each) + 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 translate; // UI translation (32 bits each for X and Y) + uint64_t scale; // UI scale (32 bits each for X and Y) + uint64_t tex_name; // OpenGL texture name (32 bits) + padding + + constexpr bool operator==(const PackedKey& other) const + { + return position == other.position && + color_flags == other.color_flags && + dimensions == other.dimensions && + translate == other.translate && + scale == other.scale && + tex_name == other.tex_name; + } + + struct Hash + { + std::size_t operator()(const PackedKey& key) const + { + return static_cast<std::size_t>(key.position ^ key.color_flags ^ + key.dimensions ^ key.translate ^ + key.scale ^ key.tex_name); + } + }; + + // Static factory function to create PackedKey from parameters + static constexpr PackedKey create(S32 x, S32 y, S32 width, S32 height, + const LLColor4& color, bool solid_color, + const LLVector3& translate, const LLVector3& scale, + LLGLuint texture_name) + { + auto float_to_u8 = [](F32 f) -> uint8_t { + return static_cast<uint8_t>(llclamp(f * 255.0f, 0.0f, 255.0f)); + }; + + auto float_to_bits = [](F32 f) -> uint32_t { + return std::bit_cast<uint32_t>(f); + }; + + uint8_t r = float_to_u8(color.mV[VRED]); + uint8_t g = float_to_u8(color.mV[VGREEN]); + uint8_t b = float_to_u8(color.mV[VBLUE]); + uint8_t a = float_to_u8(color.mV[VALPHA]); + + uint64_t pos = (static_cast<uint64_t>(static_cast<uint32_t>(x)) << 32) | + static_cast<uint64_t>(static_cast<uint32_t>(y)); + + uint64_t col = (static_cast<uint64_t>(r) << 56) | + (static_cast<uint64_t>(g) << 48) | + (static_cast<uint64_t>(b) << 40) | + (static_cast<uint64_t>(a) << 32) | + (solid_color ? 1ULL : 0ULL); + + uint64_t dim = (static_cast<uint64_t>(static_cast<uint32_t>(width)) << 32) | + static_cast<uint64_t>(static_cast<uint32_t>(height)); + + uint64_t trns = (static_cast<uint64_t>(float_to_bits(translate.mV[VX])) << 32) | + static_cast<uint64_t>(float_to_bits(translate.mV[VY])); + + uint64_t scl = (static_cast<uint64_t>(float_to_bits(scale.mV[VX])) << 32) | + static_cast<uint64_t>(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<uint64_t>(texture_name); + + return PackedKey{ pos, col, dim, trns, scl, tex }; + } + }; + + // Cached display list for a specific configuration + struct CachedDisplayList + { + buffer_data_list_t list; + std::chrono::steady_clock::time_point last_used; + }; + + // Get a display list for the given configuration + buffer_data_list_t* findDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const; + // Generate a new display list for the given configuration, draws immediately. + buffer_data_list_t* genDisplayList(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const; + + // Invalidate all cached display lists (called when image properties change) + void invalidateDisplayLists(); + + // Clean up old display lists for this image (called by updateClass) + void cleanupDisplayLists(); + void unregisterFromGlobalCleanup(); + image_loaded_signal_t* mImageLoaded; std::string mName; @@ -110,6 +226,14 @@ protected: EScaleStyle mScaleStyle; mutable S32 mCachedW; mutable S32 mCachedH; + + // Display list cache - now using PackedKey directly + mutable std::unordered_map<PackedKey, CachedDisplayList, PackedKey::Hash> mDisplayLists; + + // Track all LLUIImage cache instances for global cleanup + static std::vector<LLPointer<LLUIImage> > sImageList; + static size_t sCleanupIndex; // Round-robin cleanup position + static bool sEnableDisplayListsCollection; }; #include "lluiimage.inl" diff --git a/indra/llrender/lluiimage.inl b/indra/llrender/lluiimage.inl index dff1fcdfcc..d40835a0f4 100644 --- a/indra/llrender/lluiimage.inl +++ b/indra/llrender/lluiimage.inl @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,30 +29,72 @@ void LLUIImage::draw(S32 x, S32 y, const LLColor4& color) const draw(x, y, getWidth(), getHeight(), color); } +void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, bool solid_color) const +{ + if (sEnableDisplayListsCollection) + { + // Get display list for this configuration + buffer_data_list_t* display_list = findDisplayList(x, y, width, height, color, solid_color); + + if (display_list && !display_list->empty()) + { + // Deliberately empty pending verts. + // They aren't related to the image, so don't register them under draw zone + gGL.flush(); + LL_PROFILE_ZONE_SCOPED; + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + + //gGL.pushUIMatrix(); + + if (solid_color) + { + gSolidColorProgram.bind(); + } + + gGL.color4fv(color.mV); // for the shader + + // Replay the cached display list + for (LLVertexBufferData& buffer : *display_list) + { + buffer.draw(); + } + + if (solid_color) + { + gUIProgram.bind(); + } + //gGL.popUIMatrix(); + } + else + { + // Create, draw and capture display list. + // Basically a wrapper around gl_draw_scaled_image_with_border + // that records the output into a list. + genDisplayList(x, y, width, height, color, solid_color); + } + } + else + { + gl_draw_scaled_image_with_border( + x, y, + width, height, + mImage, + color, + solid_color, + mClipRegion, + mScaleRegion, + mScaleStyle == SCALE_INNER); + } +} + void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const { - gl_draw_scaled_image_with_border( - x, y, - width, height, - mImage, - color, - false, - mClipRegion, - mScaleRegion, - mScaleStyle == SCALE_INNER); + draw(x, y, width, height, color, false); } void LLUIImage::drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const { - gl_draw_scaled_image_with_border( - x, y, - width, height, - mImage, - color, - true, - mClipRegion, - mScaleRegion, - mScaleStyle == SCALE_INNER); + draw(x, y, width, height, color, true); } void LLUIImage::drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index d59ddd0fec..9b839350ae 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -939,12 +939,14 @@ void LLVertexBuffer::initClass(LLWindow* window) { llassert(sVBOPool == nullptr); +#if LL_DARWIN || LL_ARM64 if (gGLManager.mIsApple) { LL_INFOS() << "VBO Pooling Disabled" << LL_ENDL; sVBOPool = new LLAppleVBOPool(); } else +#endif { LL_INFOS() << "VBO Pooling Enabled" << LL_ENDL; sVBOPool = new LLDefaultVBOPool(); @@ -1282,7 +1284,12 @@ U8* LLVertexBuffer::mapVertexBuffer(LLVertexBuffer::AttributeType type, U32 inde count = mNumVerts - index; } +#if LL_DARWIN || LL_ARM64 + // Region tracking not needed on apple silicon - it recreates entire buffer + // While mIsApple can be encountered under windows, this is a + // macOS OpenGL behavior workaround. LL_ARM64 check might be not needed if (!gGLManager.mIsApple) +#endif { U32 start = mOffsets[type] + sTypeSize[type] * index; U32 end = start + sTypeSize[type] * count-1; @@ -1319,7 +1326,9 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count) count = mNumIndices-index; } +#if LL_DARWIN || LL_ARM64 if (!gGLManager.mIsApple) +#endif { U32 start = sizeof(U16) * index; U32 end = start + sizeof(U16) * count-1; @@ -1354,6 +1363,7 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count) // dst -- mMappedData or mMappedIndexData void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst) { +#if LL_DARWIN || LL_ARM64 if (gGLManager.mIsApple) { // on OS X, flush_vbo doesn't actually write to the GL buffer, so be sure to call @@ -1365,6 +1375,7 @@ void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8 memcpy(dst+start, data, end-start+1); } else +#endif { llassert(target == GL_ARRAY_BUFFER ? sGLRenderBuffer == mGLBuffer : sGLRenderIndices == mGLIndices); @@ -1420,6 +1431,7 @@ void LLVertexBuffer::_unmapBuffer() } }; +#if LL_DARWIN || LL_ARM64 if (gGLManager.mIsApple) { STOP_GLERROR; @@ -1462,6 +1474,7 @@ void LLVertexBuffer::_unmapBuffer() STOP_GLERROR; } else +#endif // LL_DARWIN || LL_ARM64 { if (!mMappedVertexRegions.empty()) { |
