diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llrender |
Print done when done.
Diffstat (limited to 'indra/llrender')
-rw-r--r-- | indra/llrender/llfontgl.cpp | 1434 | ||||
-rw-r--r-- | indra/llrender/llfontgl.h | 233 | ||||
-rw-r--r-- | indra/llrender/llgldbg.cpp | 204 | ||||
-rw-r--r-- | indra/llrender/llgldbg.h | 16 | ||||
-rw-r--r-- | indra/llrender/llimagegl.cpp | 1205 | ||||
-rw-r--r-- | indra/llrender/llimagegl.h | 185 |
6 files changed, 3277 insertions, 0 deletions
diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp new file mode 100644 index 0000000000..547a593447 --- /dev/null +++ b/indra/llrender/llfontgl.cpp @@ -0,0 +1,1434 @@ +/** + * @file llfontgl.cpp + * @brief Wrapper around FreeType + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <boost/tokenizer.hpp> + +#include "llfont.h" +#include "llfontgl.h" +#include "llgl.h" +#include "v4color.h" + +const S32 BOLD_OFFSET = 1; + +// static class members +F32 LLFontGL::sVertDPI = 96.f; +F32 LLFontGL::sHorizDPI = 96.f; +F32 LLFontGL::sScaleX = 1.f; +F32 LLFontGL::sScaleY = 1.f; +LLString LLFontGL::sAppDir; + +LLFontGL* LLFontGL::sMonospace = NULL; +LLFontGL* LLFontGL::sSansSerifSmall = NULL; +LLFontGL* LLFontGL::sSansSerif = NULL; +LLFontGL* LLFontGL::sSansSerifBig = NULL; +LLFontGL* LLFontGL::sSansSerifHuge = NULL; +LLFontGL* LLFontGL::sSansSerifBold = NULL; +LLFontList* LLFontGL::sSSFallback = NULL; +LLFontList* LLFontGL::sSSSmallFallback = NULL; +LLFontList* LLFontGL::sSSBigFallback = NULL; +LLFontList* LLFontGL::sSSHugeFallback = NULL; +LLFontList* LLFontGL::sSSBoldFallback = NULL; +LLColor4 LLFontGL::sShadowColor(0.f, 0.f, 0.f, 1.f); + +LLCoordFont LLFontGL::sCurOrigin; +std::vector<LLCoordFont> LLFontGL::sOriginStack; + +LLFontGL*& gExtCharFont = LLFontGL::sSansSerif; + +const F32 EXT_X_BEARING = 1.f; +const F32 EXT_Y_BEARING = 0.f; +const F32 EXT_KERNING = 1.f; +const F32 PIXEL_BORDER_THRESHOLD = 0.0001f; +const F32 PIXEL_CORRECTION_DISTANCE = 0.01f; + +const F32 PAD_AMT = 0.5f; + +F32 llfont_round_x(F32 x) +{ + //return llfloor((x-LLFontGL::sCurOrigin.mX)/LLFontGL::sScaleX+0.5f)*LLFontGL::sScaleX+LLFontGL::sCurOrigin.mX; + //return llfloor(x/LLFontGL::sScaleX+0.5f)*LLFontGL::sScaleY; + return x; +} + +F32 llfont_round_y(F32 y) +{ + //return llfloor((y-LLFontGL::sCurOrigin.mY)/LLFontGL::sScaleY+0.5f)*LLFontGL::sScaleY+LLFontGL::sCurOrigin.mY; + //return llfloor(y+0.5f); + return y; +} + +// static +U8 LLFontGL::getStyleFromString(const LLString &style) +{ + S32 ret = 0; + if (style.find("NORMAL") != style.npos) + { + ret |= NORMAL; + } + if (style.find("BOLD") != style.npos) + { + ret |= BOLD; + } + if (style.find("ITALIC") != style.npos) + { + ret |= ITALIC; + } + if (style.find("UNDERLINE") != style.npos) + { + ret |= UNDERLINE; + } + return ret; +} + +LLFontGL::LLFontGL() + : LLFont() +{ + init(); + clearEmbeddedChars(); +} + +LLFontGL::LLFontGL(const LLFontGL &source) +{ + llerrs << "Not implemented!" << llendl; +} + +LLFontGL::~LLFontGL() +{ + mImageGLp = NULL; + mRawImageGLp = NULL; + clearEmbeddedChars(); +} + +void LLFontGL::init() +{ + if (mImageGLp.isNull()) + { + mImageGLp = new LLImageGL(FALSE); + //RN: use nearest mipmap filtering to obviate the need to do pixel-accurate positioning + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE,TRUE); + } + if (mRawImageGLp.isNull()) + { + mRawImageGLp = new LLImageRaw; // Note LLFontGL owns the image, not LLFont. + } + setRawImage( mRawImageGLp ); +} + +void LLFontGL::reset() +{ + init(); + resetBitmap(); +} + +// static +LLString LLFontGL::getFontPathSystem() +{ + LLString system_path; + + // Try to figure out where the system's font files are stored. + char *system_root = NULL; +#if LL_WINDOWS + system_root = getenv("SystemRoot"); + if (!system_root) + { + llwarns << "SystemRoot not found, attempting to load fonts from default path." << llendl; + } +#endif + + if (system_root) + { + system_path = llformat("%s/fonts/", system_root); + } + else + { +#if LL_WINDOWS + // HACK for windows 98/Me + system_path = "/WINDOWS/FONTS/"; +#elif LL_DARWIN + // HACK for Mac OS X + system_path = "/System/Library/Fonts/"; +#endif + } + return system_path; +} + + +// static +LLString LLFontGL::getFontPathLocal() +{ + LLString local_path; + + // Backup files if we can't load from system fonts directory. + // We could store this in an end-user writable directory to allow + // end users to switch fonts. + if (LLFontGL::sAppDir.length()) + { + // use specified application dir to look for fonts + local_path = LLFontGL::sAppDir + "/fonts/"; + } + else + { + // assume working directory is executable directory + local_path = "./fonts/"; + } + return local_path; +} + +//static +bool LLFontGL::loadFaceFallback(LLFontList *fontlistp, const LLString& fontname, const F32 point_size) +{ + LLString local_path = getFontPathLocal(); + LLString sys_path = getFontPathSystem(); + + // The fontname string may contain multiple font file names separated by semicolons. + // Break it apart and try loading each one, in order. + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(";"); + tokenizer tokens(fontname, sep); + tokenizer::iterator token_iter; + + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + LLFont *fontp = new LLFont(); + LLString font_path = local_path + *token_iter; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + font_path = sys_path + *token_iter; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI, 2, TRUE)) + { + llwarns << "Couldn't load font " << *token_iter << llendl; + delete fontp; + fontp = NULL; + } + } + + if(fontp) + { + fontlistp->addAtEnd(fontp); + } + } + + // We want to return true if at least one fallback font loaded correctly. + return (fontlistp->size() > 0); +} + +//static +bool LLFontGL::loadFace(LLFontGL *fontp, const LLString& fontname, const F32 point_size, LLFontList *fallback_fontp) +{ + LLString local_path = getFontPathLocal(); + LLString font_path = local_path + fontname; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI)) + { + LLString sys_path = getFontPathSystem(); + font_path = sys_path + fontname; + if (!fontp->loadFace(font_path.c_str(), point_size, sVertDPI, sHorizDPI)) + { + llwarns << "Couldn't load font " << fontname << llendl; + return false; + } + } + + fontp->setFallbackFont(fallback_fontp); + return true; +} + + +// static +BOOL LLFontGL::initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, + const LLString& monospace_file, F32 monospace_size, + const LLString& sansserif_file, + const LLString& sanserif_fallback_file, F32 ss_fallback_scale, + F32 small_size, F32 medium_size, F32 big_size, F32 huge_size, + const LLString& sansserif_bold_file, F32 bold_size, + const LLString& app_dir) +{ + BOOL failed = FALSE; + sVertDPI = (F32)llfloor(screen_dpi * y_scale); + sHorizDPI = (F32)llfloor(screen_dpi * x_scale); + sScaleX = x_scale; + sScaleY = y_scale; + sAppDir = app_dir; + + // + // Monospace font + // + + if (!sMonospace) + { + sMonospace = new LLFontGL(); + } + else + { + sMonospace->reset(); + } + + failed |= !loadFace(sMonospace, monospace_file, monospace_size, NULL); + + // + // Sans-serif fonts + // + if(!sSansSerifHuge) + { + sSansSerifHuge = new LLFontGL(); + } + else + { + sSansSerifHuge->reset(); + } + + if (!sSSHugeFallback) + { + sSSHugeFallback = new LLFontList(); + if (!loadFaceFallback(sSSHugeFallback, sanserif_fallback_file, huge_size*ss_fallback_scale)) + { + delete sSSHugeFallback; + sSSHugeFallback = NULL; + } + } + + failed |= !loadFace(sSansSerifHuge, sansserif_file, huge_size, sSSHugeFallback); + + + if(!sSansSerifBig) + { + sSansSerifBig = new LLFontGL(); + } + else + { + sSansSerifBig->reset(); + } + + if (!sSSBigFallback) + { + sSSBigFallback = new LLFontList(); + if (!loadFaceFallback(sSSBigFallback, sanserif_fallback_file, big_size*ss_fallback_scale)) + { + delete sSSBigFallback; + sSSBigFallback = NULL; + } + } + + failed |= !loadFace(sSansSerifBig, sansserif_file, big_size, sSSBigFallback); + + + if(!sSansSerif) + { + sSansSerif = new LLFontGL(); + } + else + { + sSansSerif->reset(); + } + + if (!sSSFallback) + { + sSSFallback = new LLFontList(); + if (!loadFaceFallback(sSSFallback, sanserif_fallback_file, medium_size*ss_fallback_scale)) + { + delete sSSFallback; + sSSFallback = NULL; + } + } + failed |= !loadFace(sSansSerif, sansserif_file, medium_size, sSSFallback); + + + if(!sSansSerifSmall) + { + sSansSerifSmall = new LLFontGL(); + } + else + { + sSansSerifSmall->reset(); + } + + if (!sSSSmallFallback) + { + sSSSmallFallback = new LLFontList(); + if (!loadFaceFallback(sSSSmallFallback, sanserif_fallback_file, small_size*ss_fallback_scale)) + { + delete sSSSmallFallback; + sSSSmallFallback = NULL; + } + } + failed |= !loadFace(sSansSerifSmall, sansserif_file, small_size, sSSSmallFallback); + + + // + // Sans-serif bold + // + if(!sSansSerifBold) + { + sSansSerifBold = new LLFontGL(); + } + else + { + sSansSerifBold->reset(); + } + + if (!sSSBoldFallback) + { + sSSBoldFallback = new LLFontList(); + if (!loadFaceFallback(sSSBoldFallback, sanserif_fallback_file, medium_size*ss_fallback_scale)) + { + delete sSSBoldFallback; + sSSBoldFallback = NULL; + } + } + failed |= !loadFace(sSansSerifBold, sansserif_bold_file, medium_size, sSSBoldFallback); + + return !failed; +} + + + +// static +void LLFontGL::destroyDefaultFonts() +{ + delete sMonospace; + sMonospace = NULL; + + delete sSansSerifHuge; + sSansSerifHuge = NULL; + + delete sSansSerifBig; + sSansSerifBig = NULL; + + delete sSansSerif; + sSansSerif = NULL; + + delete sSansSerifSmall; + sSansSerifSmall = NULL; + + delete sSansSerifBold; + sSansSerifBold = NULL; + + delete sSSHugeFallback; + sSSHugeFallback = NULL; + + delete sSSBigFallback; + sSSBigFallback = NULL; + + delete sSSFallback; + sSSFallback = NULL; + + delete sSSSmallFallback; + sSSSmallFallback = NULL; + + delete sSSBoldFallback; + sSSBoldFallback = NULL; +} + +//static +void LLFontGL::destroyGL() +{ + if (!sMonospace) + { + // Already all destroyed. + return; + } + sMonospace->mImageGLp->destroyGLTexture(); + sSansSerifHuge->mImageGLp->destroyGLTexture(); + sSansSerifSmall->mImageGLp->destroyGLTexture(); + sSansSerif->mImageGLp->destroyGLTexture(); + sSansSerifBig->mImageGLp->destroyGLTexture(); + sSansSerifBold->mImageGLp->destroyGLTexture(); +} + + + +LLFontGL &LLFontGL::operator=(const LLFontGL &source) +{ + llerrs << "Not implemented" << llendl; + return *this; +} + +BOOL LLFontGL::loadFace(const LLString& filename, const F32 point_size, const F32 vert_dpi, const F32 horz_dpi) +{ + if (!LLFont::loadFace(filename, point_size, vert_dpi, horz_dpi, 2, FALSE)) + { + return FALSE; + } + mImageGLp->createGLTexture(0, mRawImageGLp); + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + return TRUE; +} + +BOOL LLFontGL::addChar(const llwchar wch) +{ + if (!LLFont::addChar(wch)) + { + return FALSE; + } + + stop_glerror(); + mImageGLp->setSubImage(mRawImageGLp, 0, 0, mImageGLp->getWidth(), mImageGLp->getHeight()); + mImageGLp->bind(); + mImageGLp->setMipFilterNearest(TRUE, TRUE); + stop_glerror(); + return TRUE; +} + + +S32 LLFontGL::renderUTF8(const LLString &text, const S32 offset, + const F32 x, const F32 y, + const LLColor4 &color, + const HAlign halign, const VAlign valign, + U8 style, + const S32 max_chars, const S32 max_pixels, + F32* right_x, + BOOL use_ellipses) const +{ + LLWString wstr = utf8str_to_wstring(text); + return render(wstr, offset, x, y, color, halign, valign, style, max_chars, max_pixels, right_x, use_ellipses); +} + +S32 LLFontGL::render(const LLWString &wstr, + const S32 begin_offset, + const F32 x, const F32 y, + const LLColor4 &color, + const HAlign halign, const VAlign valign, + U8 style, + const S32 max_chars, S32 max_pixels, + F32* right_x, + BOOL use_embedded, + BOOL use_ellipses) const +{ + LLGLEnable texture_2d(GL_TEXTURE_2D); + + if (wstr.empty()) + { + return 0; + } + + if (style & DROP_SHADOW) + { + LLColor4 shadow_color = sShadowColor; + shadow_color[3] = color[3]; + render(wstr, begin_offset, + x + 1.f / sScaleX, + y - 1.f / sScaleY, + shadow_color, + halign, + valign, + style & (~DROP_SHADOW), + max_chars, + max_pixels, + right_x, + use_embedded, + use_ellipses); + } + + S32 scaled_max_pixels = max_pixels == S32_MAX ? S32_MAX : llceil((F32)max_pixels * sScaleX); + + BOOL render_bold = FALSE; + + // HACK for better bolding + if (style & BOLD) + { + if (this == LLFontGL::sSansSerif) + { + return LLFontGL::sSansSerifBold->render( + wstr, begin_offset, + x, y, + color, + halign, valign, + (style & ~BOLD), + max_chars, max_pixels, + right_x, use_embedded); + } + else + { + render_bold = TRUE; + } + } + + glPushMatrix(); + glLoadIdentity(); + glTranslatef(floorf(sCurOrigin.mX*sScaleX), floorf(sCurOrigin.mY*sScaleY), sCurOrigin.mZ); + //glScalef(sScaleX, sScaleY, 1.0f); + + // avoid half pixels + // RN: if we're going to this trouble, might as well snap to nearest pixel all the time + // but the plan is to get rid of this so that fonts "just work" + //F32 half_pixel_distance = llabs(fmodf(sCurOrigin.mX * sScaleX, 1.f) - 0.5f); + //if (half_pixel_distance < PIXEL_BORDER_THRESHOLD) + //{ + glTranslatef(PIXEL_CORRECTION_DISTANCE*sScaleX, 0.f, 0.f); + //} + + // this code would just snap to pixel grid, although it seems to introduce more jitter + //F32 pixel_offset_x = llround(sCurOrigin.mX * sScaleX) - (sCurOrigin.mX * sScaleX); + //F32 pixel_offset_y = llround(sCurOrigin.mY * sScaleY) - (sCurOrigin.mY * sScaleY); + //glTranslatef(-pixel_offset_x, -pixel_offset_y, 0.f); + + // scale back to native pixel size + //glScalef(1.f / sScaleX, 1.f / sScaleY, 1.f); + //glScaled(1.0 / (F64) sScaleX, 1.0 / (F64) sScaleY, 1.0f); + LLFastTimer t(LLFastTimer::FTM_RENDER_FONTS); + + glColor4fv( color.mV ); + + S32 chars_drawn = 0; + S32 i; + S32 length; + + if (-1 == max_chars) + { + length = (S32)wstr.length() - begin_offset; + } + else + { + length = llmin((S32)wstr.length() - begin_offset, max_chars ); + } + + F32 cur_x, cur_y, cur_render_x, cur_render_y; + F32 slant_offset; + + slant_offset = ((style & ITALIC) ? ( -mAscender * 0.25f) : 0.f); + + // Bind the font texture + + mImageGLp->bind(0); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Not guaranteed to be set correctly + + cur_x = ((F32)x * sScaleX); + cur_y = ((F32)y * sScaleY); + + // Offset y by vertical alignment. + switch (valign) + { + case TOP: + cur_y -= mAscender; + break; + case BOTTOM: + cur_y += mDescender; + break; + case VCENTER: + cur_y -= ((mAscender - mDescender)/2.f); + break; + case BASELINE: + // Baseline, do nothing. + break; + default: + break; + } + + switch (halign) + { + case LEFT: + break; + case RIGHT: + cur_x -= (F32)getWidth(wstr.c_str(), 0, length) * sScaleX; + break; + case HCENTER: + cur_x -= llmin(scaled_max_pixels, llround(getWidthF32(wstr.c_str(), 0, length) * sScaleX)) / 2; + break; + default: + break; + } + + // Round properly. + //cur_render_y = (F32)llfloor(cur_y/sScaleY + 0.5f)*sScaleY; + //cur_render_x = (F32)llfloor(cur_x/sScaleX + 0.5f)*sScaleX; + + cur_render_y = cur_y; + cur_render_x = cur_x; + + F32 start_x = cur_x; + + F32 inv_width = 1.f / mImageGLp->getWidth(); + F32 inv_height = 1.f / mImageGLp->getHeight(); + + const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; + + + BOOL draw_ellipses = FALSE; + if (use_ellipses) + { + // check for too long of a string + if (getWidthF32(wstr.c_str(), 0, max_chars) > scaled_max_pixels) + { + const LLWString dots(utf8str_to_wstring(LLString("..."))); + scaled_max_pixels = llmax(0, scaled_max_pixels - llround(getWidthF32(dots.c_str()))); + draw_ellipses = TRUE; + } + } + + + glBegin(GL_QUADS); + for (i = begin_offset; i < begin_offset + length; i++) + { + llwchar wch = wstr[i]; + + // Handle embedded characters first, if they're enabled. + // Embedded characters are a hack for notecards + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + LLImageGL* ext_image = ext_data->mImage; + const LLWString& label = ext_data->mLabel; + + F32 ext_height = (F32)ext_image->getHeight() * sScaleY; + + F32 ext_width = (F32)ext_image->getWidth() * sScaleX; + F32 ext_advance = (EXT_X_BEARING * sScaleX) + ext_width; + + if (!label.empty()) + { + ext_advance += (EXT_X_BEARING + gExtCharFont->getWidthF32( label.c_str() )) * sScaleX; + } + + if (start_x + scaled_max_pixels < cur_x + ext_advance) + { + // Not enough room for this character. + break; + } + + glEnd(); + + glColor3f(1.f, 1.f, 1.f); + + ext_image->bind(); + const F32 ext_x = cur_render_x + (EXT_X_BEARING * sScaleX); + const F32 ext_y = cur_render_y + (EXT_Y_BEARING * sScaleY + mAscender - mLineHeight); + + glBegin(GL_QUADS); + { + S32 num_passes = render_bold ? 2 : 1; + for (S32 pass = 0; pass < num_passes; pass++) + { + glTexCoord2f(1.f, 1.f); + glVertex2f(llfont_round_x(ext_x + ext_width + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y + ext_height)); + + glTexCoord2f(0.f, 1.f); + glVertex2f(llfont_round_x(ext_x + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y + ext_height)); + + glTexCoord2f(0.f, 0.f); + glVertex2f(llfont_round_x(ext_x + (F32)(pass * BOLD_OFFSET)), llfont_round_y(ext_y)); + + glTexCoord2f(1.f, 0.f); + glVertex2f(llfont_round_x(ext_x + ext_width + (F32)(pass * BOLD_OFFSET)), + llfont_round_y(ext_y)); + } + } + glEnd(); + + if (!label.empty()) + { + glPushMatrix(); + //glLoadIdentity(); + //glTranslatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + gExtCharFont->render(label, 0, + /*llfloor*/((ext_x + (F32)ext_image->getWidth() + EXT_X_BEARING) / sScaleX), + /*llfloor*/(cur_y / sScaleY), + color, + halign, BASELINE, NORMAL, S32_MAX, S32_MAX, NULL, + TRUE ); + glPopMatrix(); + } + + glColor4fv(color.mV); + + chars_drawn++; + cur_x += ext_advance; + if (((i + 1) < length) && wstr[i+1]) + { + cur_x += EXT_KERNING * sScaleX; + } + cur_render_x = cur_x; + + // Bind the font texture + mImageGLp->bind(); + glBegin(GL_QUADS); + } + else + { + if (!hasGlyph(wch)) + { + glEnd(); + (const_cast<LLFontGL*>(this))->addChar(wch); + glBegin(GL_QUADS); + } + + const LLFontGlyphInfo* fgi= getGlyphInfo(wch); + if (!fgi) + { + llerrs << "Missing Glyph Info" << llendl; + } + if ((start_x + scaled_max_pixels) < (cur_x + fgi->mXBearing + fgi->mWidth)) + { + // Not enough room for this character. + break; + } + + // Draw the text at the appropriate location + //Specify vertices and texture coordinates + S32 num_passes = render_bold ? 2 : 1; + for (S32 pass = 0; pass < num_passes; pass++) + { + glTexCoord2f((fgi->mXBitmapOffset - PAD_AMT) * inv_width, + (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + (F32)(pass * BOLD_OFFSET) - PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing + PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset - PAD_AMT) * inv_width, + (fgi->mYBitmapOffset - PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + slant_offset + (F32)(pass * BOLD_OFFSET) - PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, + (fgi->mYBitmapOffset - PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + slant_offset + (F32)fgi->mWidth + (F32)(pass * BOLD_OFFSET) + PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing - (F32)fgi->mHeight - PAD_AMT)); + glTexCoord2f((fgi->mXBitmapOffset + fgi->mWidth + PAD_AMT) * inv_width, + (fgi->mYBitmapOffset + fgi->mHeight + PAD_AMT) * inv_height); + glVertex2f(llfont_round_x(cur_render_x + (F32)fgi->mXBearing + (F32)fgi->mWidth + (F32)(pass * BOLD_OFFSET) + PAD_AMT), + llfont_round_y(cur_render_y + (F32)fgi->mYBearing + PAD_AMT)); + } + + chars_drawn++; + + cur_x += fgi->mXAdvance; + cur_y += fgi->mYAdvance; + llwchar next_char = wstr[i+1]; + if (next_char && (next_char < LAST_CHARACTER)) + { + // Kern this puppy. + if (!hasGlyph(next_char)) + { + glEnd(); + (const_cast<LLFontGL*>(this))->addChar(next_char); + glBegin(GL_QUADS); + } + cur_x += getXKerning(wch, next_char); + } + + // Round after kerning. + // Must do this to cur_x, not just to cur_render_x, otherwise you + // will squish sub-pixel kerned characters too close together. + // For example, "CCCCC" looks bad. + cur_x = (F32)llfloor(cur_x + 0.5f); + //cur_y = (F32)llfloor(cur_y + 0.5f); + + cur_render_x = cur_x; + cur_render_y = cur_y; + } + } + + glEnd(); + + if (right_x) + { + *right_x = cur_x / sScaleX; + } + + if (style & UNDERLINE) + { + LLGLSNoTexture no_texture; + glBegin(GL_LINES); + glVertex2f(start_x, cur_y - (mDescender)); + glVertex2f(cur_x, cur_y - (mDescender)); + glEnd(); + } + + //FIXME: get this working in all alignment cases, etc. + if (draw_ellipses) + { + // recursively render ellipses at end of string + // we've already reserved enough room + glPushMatrix(); + //glLoadIdentity(); + //glTranslatef(sCurOrigin.mX, sCurOrigin.mY, 0.0f); + //glScalef(sScaleX, sScaleY, 1.f); + renderUTF8("...", + 0, + cur_x / sScaleX, (F32)y, + color, + LEFT, valign, + style, + S32_MAX, max_pixels, + right_x, + FALSE); + glPopMatrix(); + } + + glPopMatrix(); + + return chars_drawn; +} + + +LLImageGL *LLFontGL::getImageGL() const +{ + return mImageGLp; +} + +S32 LLFontGL::getWidth(const LLString& utf8text) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidth(wtext.c_str(), 0, S32_MAX); +} + +S32 LLFontGL::getWidth(const llwchar* wchars) const +{ + return getWidth(wchars, 0, S32_MAX); +} + +S32 LLFontGL::getWidth(const LLString& utf8text, const S32 begin_offset, const S32 max_chars) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidth(wtext.c_str(), begin_offset, max_chars); +} + +S32 LLFontGL::getWidth(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + F32 width = getWidthF32(wchars, begin_offset, max_chars, use_embedded); + return llround(width); +} + +F32 LLFontGL::getWidthF32(const LLString& utf8text) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidthF32(wtext.c_str(), 0, S32_MAX); +} + +F32 LLFontGL::getWidthF32(const llwchar* wchars) const +{ + return getWidthF32(wchars, 0, S32_MAX); +} + +F32 LLFontGL::getWidthF32(const LLString& utf8text, const S32 begin_offset, const S32 max_chars ) const +{ + LLWString wtext = utf8str_to_wstring(utf8text); + return getWidthF32(wtext.c_str(), begin_offset, max_chars); +} + +F32 LLFontGL::getWidthF32(const llwchar* wchars, const S32 begin_offset, const S32 max_chars, BOOL use_embedded) const +{ + const S32 LAST_CHARACTER = LLFont::LAST_CHAR_FULL; + + F32 cur_x = 0; + const S32 max_index = begin_offset + max_chars; + for (S32 i = begin_offset; i < max_index; i++) + { + const llwchar wch = wchars[i]; + if (wch == 0) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + // Handle crappy embedded hack + cur_x += getEmbeddedCharAdvance(ext_data); + + if( ((i+1) < max_chars) && (i+1 < max_index)) + { + cur_x += EXT_KERNING * sScaleX; + } + } + else + { + cur_x += getXAdvance(wch); + llwchar next_char = wchars[i+1]; + + if (((i + 1) < max_chars) + && next_char + && (next_char < LAST_CHARACTER)) + { + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + + return cur_x / sScaleX; +} + + + +// Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels +S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, + BOOL end_on_word_boundary, const BOOL use_embedded, + F32* drawn_pixels) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + llassert(max_pixels >= 0.f); + llassert(max_chars >= 0); + + BOOL clip = FALSE; + F32 cur_x = 0; + F32 drawn_x = 0; + + S32 start_of_last_word = 0; + BOOL in_word = FALSE; + + F32 scaled_max_pixels = (F32)llceil(max_pixels * sScaleX); + + S32 i; + for (i=0; (i < max_chars); i++) + { + llwchar wch = wchars[i]; + + if(wch == 0) + { + // Null terminator. We're done. + break; + } + + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + if (in_word) + { + in_word = FALSE; + } + else + { + start_of_last_word = i; + } + cur_x += getEmbeddedCharAdvance(ext_data); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + cur_x += EXT_KERNING * sScaleX; + } + + if( scaled_max_pixels < cur_x ) + { + clip = TRUE; + break; + } + } + else + { + if (in_word) + { + if (iswspace(wch)) + { + in_word = FALSE; + } + } + else + { + start_of_last_word = i; + if (!iswspace(wch)) + { + in_word = TRUE; + } + } + + cur_x += getXAdvance(wch); + + if (scaled_max_pixels < cur_x) + { + clip = TRUE; + break; + } + + if (((i+1) < max_chars) && wchars[i+1]) + { + // Kern this puppy. + cur_x += getXKerning(wch, wchars[i+1]); + } + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + drawn_x = cur_x; + } + + if( clip && end_on_word_boundary && (start_of_last_word != 0) ) + { + i = start_of_last_word; + } + if (drawn_pixels) + { + *drawn_pixels = drawn_x; + } + return i; +} + + +S32 LLFontGL::firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos, S32 max_chars) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 total_width = 0.0; + S32 drawable_chars = 0; + + F32 scaled_max_pixels = max_pixels * sScaleX; + + S32 start = llmin(start_pos, text_len - 1); + for (S32 i = start; i >= 0; i--) + { + llwchar wch = wchars[i]; + + const embedded_data_t* ext_data = getEmbeddedCharData(wch); + if (ext_data) + { + F32 char_width = getEmbeddedCharAdvance(ext_data); + + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + total_width += EXT_KERNING * sScaleX; + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + if( scaled_max_pixels < (total_width + char_width) ) + { + break; + } + + total_width += char_width; + + drawable_chars++; + if( max_chars >= 0 && drawable_chars >= max_chars ) + { + break; + } + + if ( i > 0 ) + { + // Kerning + total_width += getXKerning(wchars[i-1], wch); + } + + // Round after kerning. + total_width = (F32)llfloor(total_width + 0.5f); + } + } + + return text_len - drawable_chars; +} + + +S32 LLFontGL::charFromPixelOffset(const llwchar* wchars, const S32 begin_offset, F32 target_x, F32 max_pixels, S32 max_chars, BOOL round, BOOL use_embedded) const +{ + if (!wchars || !wchars[0] || max_chars == 0) + { + return 0; + } + + F32 cur_x = 0; + S32 pos = 0; + + target_x *= sScaleX; + + // max_chars is S32_MAX by default, so make sure we don't get overflow + const S32 max_index = begin_offset + llmin(S32_MAX - begin_offset, max_chars); + + F32 scaled_max_pixels = max_pixels * sScaleX; + + for (S32 i = begin_offset; (i < max_index); i++) + { + llwchar wch = wchars[i]; + if (!wch) + { + break; // done + } + const embedded_data_t* ext_data = use_embedded ? getEmbeddedCharData(wch) : NULL; + if (ext_data) + { + F32 ext_advance = getEmbeddedCharAdvance(ext_data); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + ext_advance/2) + { + break; + } + } + else + { + if (target_x < cur_x + ext_advance) + { + break; + } + } + + if (scaled_max_pixels < cur_x + ext_advance) + { + break; + } + + pos++; + cur_x += ext_advance; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + cur_x += EXT_KERNING * sScaleX; + } + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + else + { + F32 char_width = getXAdvance(wch); + + if (round) + { + // Note: if the mouse is on the left half of the character, the pick is to the character's left + // If it's on the right half, the pick is to the right. + if (target_x < cur_x + char_width*0.5f) + { + break; + } + } + else if (target_x < cur_x + char_width) + { + break; + } + + if (scaled_max_pixels < cur_x + char_width) + { + break; + } + + pos++; + cur_x += char_width; + + if (((i + 1) < max_index) + && (wchars[(i + 1)])) + { + llwchar next_char = wchars[i + 1]; + // Kern this puppy. + cur_x += getXKerning(wch, next_char); + } + + // Round after kerning. + cur_x = (F32)llfloor(cur_x + 0.5f); + } + } + + return pos; +} + + +const LLFontGL::embedded_data_t* LLFontGL::getEmbeddedCharData(const llwchar wch) const +{ + // Handle crappy embedded hack + embedded_map_t::const_iterator iter = mEmbeddedChars.find(wch); + if (iter != mEmbeddedChars.end()) + { + return iter->second; + } + return NULL; +} + + +F32 LLFontGL::getEmbeddedCharAdvance(const embedded_data_t* ext_data) const +{ + const LLWString& label = ext_data->mLabel; + LLImageGL* ext_image = ext_data->mImage; + + F32 ext_width = (F32)ext_image->getWidth(); + if( !label.empty() ) + { + ext_width += (EXT_X_BEARING + gExtCharFont->getWidthF32(label.c_str())) * sScaleX; + } + + return (EXT_X_BEARING * sScaleX) + ext_width; +} + + +void LLFontGL::clearEmbeddedChars() +{ + for_each(mEmbeddedChars.begin(), mEmbeddedChars.end(), DeletePairedPointer()); + mEmbeddedChars.clear(); +} + +void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const LLString& label ) +{ + LLWString wlabel = utf8str_to_wstring(label); + addEmbeddedChar(wc, image, wlabel); +} + +void LLFontGL::addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& wlabel ) +{ + embedded_data_t* ext_data = new embedded_data_t(image, wlabel); + mEmbeddedChars[wc] = ext_data; +} + +void LLFontGL::removeEmbeddedChar( llwchar wc ) +{ + embedded_map_t::iterator iter = mEmbeddedChars.find(wc); + if (iter != mEmbeddedChars.end()) + { + delete iter->second; + mEmbeddedChars.erase(wc); + } +} + +// static +LLString LLFontGL::nameFromFont(const LLFontGL* fontp) +{ + if (fontp == sSansSerifHuge) + { + return LLString("SansSerifHude"); + } + else if (fontp == sSansSerifSmall) + { + return LLString("SansSerifSmall"); + } + else if (fontp == sSansSerif) + { + return LLString("SansSerif"); + } + else if (fontp == sSansSerifBig) + { + return LLString("SansSerifBig"); + } + else if (fontp == sSansSerifBold) + { + return LLString("SansSerifBold"); + } + else if (fontp == sMonospace) + { + return LLString("Monospace"); + } + else + { + return LLString(); + } +} + +// static +LLFontGL* LLFontGL::fontFromName(const LLString& font_name) +{ + LLFontGL* gl_font = NULL; + if (font_name == "SansSerifHuge") + { + gl_font = LLFontGL::sSansSerifHuge; + } + else if (font_name == "SansSerifSmall") + { + gl_font = LLFontGL::sSansSerifSmall; + } + else if (font_name == "SansSerif") + { + gl_font = LLFontGL::sSansSerif; + } + else if (font_name == "SansSerifBig") + { + gl_font = LLFontGL::sSansSerifBig; + } + else if (font_name == "SansSerifBold") + { + gl_font = LLFontGL::sSansSerifBold; + } + else if (font_name == "Monospace") + { + gl_font = LLFontGL::sMonospace; + } + return gl_font; +} + +// static +LLString LLFontGL::nameFromHAlign(LLFontGL::HAlign align) +{ + if (align == LEFT) return LLString("left"); + else if (align == RIGHT) return LLString("right"); + else if (align == HCENTER) return LLString("center"); + else return LLString(); +} + +// static +LLFontGL::HAlign LLFontGL::hAlignFromName(const LLString& name) +{ + LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; + if (name == "left") + { + gl_hfont_align = LLFontGL::LEFT; + } + else if (name == "right") + { + gl_hfont_align = LLFontGL::RIGHT; + } + else if (name == "center") + { + gl_hfont_align = LLFontGL::HCENTER; + } + //else leave left + return gl_hfont_align; +} + +// static +LLString LLFontGL::nameFromVAlign(LLFontGL::VAlign align) +{ + if (align == TOP) return LLString("top"); + else if (align == VCENTER) return LLString("center"); + else if (align == BASELINE) return LLString("baseline"); + else if (align == BOTTOM) return LLString("bottom"); + else return LLString(); +} + +// static +LLFontGL::VAlign LLFontGL::vAlignFromName(const LLString& name) +{ + LLFontGL::VAlign gl_vfont_align = LLFontGL::BASELINE; + if (name == "top") + { + gl_vfont_align = LLFontGL::TOP; + } + else if (name == "center") + { + gl_vfont_align = LLFontGL::VCENTER; + } + else if (name == "baseline") + { + gl_vfont_align = LLFontGL::BASELINE; + } + else if (name == "bottom") + { + gl_vfont_align = LLFontGL::BOTTOM; + } + //else leave baseline + return gl_vfont_align; +} diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h new file mode 100644 index 0000000000..789879a5ca --- /dev/null +++ b/indra/llrender/llfontgl.h @@ -0,0 +1,233 @@ +/** + * @file llfontgl.h + * @author Doug Soo + * @brief Wrapper around FreeType + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLFONTGL_H +#define LL_LLFONTGL_H + +#include "llfont.h" +#include "llimagegl.h" +#include "v2math.h" +#include "llcoord.h" + +class LLColor4; + +class LLFontGL : public LLFont +{ +public: + enum HAlign + { + // Horizontal location of x,y coord to render. + LEFT = 0, // Left align + RIGHT = 1, // Right align + HCENTER = 2, // Center + }; + + enum VAlign + { + // Vertical location of x,y coord to render. + TOP = 3, // Top align + VCENTER = 4, // Center + BASELINE = 5, // Baseline + BOTTOM = 6 // Bottom + }; + + enum StyleFlags + { + // text style to render. May be combined (these are bit flags) + NORMAL = 0, + BOLD = 1, + ITALIC = 2, + UNDERLINE = 4, + DROP_SHADOW = 8 + }; + + // Takes a string with potentially several flags, i.e. "NORMAL|BOLD|ITALIC" + static U8 getStyleFromString(const LLString &style); + + LLFontGL(); + LLFontGL(const LLFontGL &source); + ~LLFontGL(); + + void init(); // Internal init, or reinitialization + void reset(); // Reset a font after GL cleanup. ONLY works on an already loaded font. + + LLFontGL &operator=(const LLFontGL &source); + + static BOOL initDefaultFonts(F32 screen_dpi, F32 x_scale, F32 y_scale, + const LLString& monospace_file, F32 monospace_size, + const LLString& sansserif_file, + const LLString& sansserif_fallback_file, F32 ss_fallback_scale, + F32 small_size, F32 medium_size, F32 large_size, F32 huge_size, + const LLString& sansserif_bold_file, F32 bold_size, + const LLString& app_dir = LLString::null); + + static void destroyDefaultFonts(); + static void destroyGL(); + + static bool loadFaceFallback(LLFontList *fontp, const LLString& fontname, const F32 point_size); + static bool loadFace(LLFontGL *fontp, const LLString& fontname, const F32 point_size, LLFontList *fallback_fontp); + BOOL loadFace(const LLString& filename, const F32 point_size, const F32 vert_dpi, const F32 horz_dpi); + + + S32 renderUTF8(const LLString &text, const S32 begin_offset, + S32 x, S32 y, + const LLColor4 &color) const + { + return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, + LEFT, BASELINE, NORMAL, + S32_MAX, S32_MAX, NULL, FALSE); + } + + S32 renderUTF8(const LLString &text, const S32 begin_offset, + S32 x, S32 y, + const LLColor4 &color, + HAlign halign, VAlign valign, U8 style = NORMAL) const + { + return renderUTF8(text, begin_offset, (F32)x, (F32)y, color, + halign, valign, style, + S32_MAX, S32_MAX, NULL, FALSE); + } + + // renderUTF8 does a conversion, so is slower! + S32 renderUTF8(const LLString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign, + VAlign valign, + U8 style, + S32 max_chars, + S32 max_pixels, + F32* right_x, + BOOL use_ellipses) const; + + S32 render(const LLWString &text, const S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color) const + { + return render(text, begin_offset, x, y, color, + LEFT, BASELINE, NORMAL, + S32_MAX, S32_MAX, NULL, FALSE, FALSE); + } + + + S32 render(const LLWString &text, + S32 begin_offset, + F32 x, F32 y, + const LLColor4 &color, + HAlign halign = LEFT, + VAlign valign = BASELINE, + U8 style = NORMAL, + S32 max_chars = S32_MAX, + S32 max_pixels = S32_MAX, + F32* right_x=NULL, + BOOL use_embedded = FALSE, + BOOL use_ellipses = FALSE) const; + + // font metrics - override for LLFont that returns units of virtual pixels + /*virtual*/ F32 getLineHeight() const { return (F32)llround(mLineHeight / sScaleY); } + /*virtual*/ F32 getAscenderHeight() const { return (F32)llround(mAscender / sScaleY); } + /*virtual*/ F32 getDescenderHeight() const { return (F32)llround(mDescender / sScaleY); } + + virtual S32 getWidth(const LLString& utf8text) const; + virtual S32 getWidth(const llwchar* wchars) const; + virtual S32 getWidth(const LLString& utf8text, const S32 offset, const S32 max_chars ) const; + virtual S32 getWidth(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE) const; + + virtual F32 getWidthF32(const LLString& utf8text) const; + virtual F32 getWidthF32(const llwchar* wchars) const; + virtual F32 getWidthF32(const LLString& text, const S32 offset, const S32 max_chars ) const; + virtual F32 getWidthF32(const llwchar* wchars, const S32 offset, const S32 max_chars, BOOL use_embedded = FALSE ) const; + + // The following are called often, frequently with large buffers, so do not use a string interface + + // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels + virtual S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars = S32_MAX, + BOOL end_on_word_boundary = FALSE, const BOOL use_embedded = FALSE, + F32* drawn_pixels = NULL) const; + + // Returns the index of the first complete characters from text that can be drawn in max_pixels + // starting on the right side (at character start_pos). + virtual S32 firstDrawableChar(const llwchar* wchars, F32 max_pixels, S32 text_len, S32 start_pos=S32_MAX, S32 max_chars = S32_MAX) const; + + // Returns the index of the character closest to pixel position x (ignoring text to the right of max_pixels and max_chars) + virtual S32 charFromPixelOffset(const llwchar* wchars, const S32 char_offset, + F32 x, F32 max_pixels=F32_MAX, S32 max_chars = S32_MAX, + BOOL round = TRUE, BOOL use_embedded = FALSE) const; + + + LLImageGL *getImageGL() const; + + void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLString& label); + void addEmbeddedChar( llwchar wc, LLImageGL* image, const LLWString& label); + void removeEmbeddedChar( llwchar wc ); + + static LLString nameFromFont(const LLFontGL* fontp); + static LLFontGL* fontFromName(const LLString& name); + + static LLString nameFromHAlign(LLFontGL::HAlign align); + static LLFontGL::HAlign hAlignFromName(const LLString& name); + + static LLString nameFromVAlign(LLFontGL::VAlign align); + static LLFontGL::VAlign vAlignFromName(const LLString& name); + +protected: + struct embedded_data_t + { + embedded_data_t(LLImageGL* image, const LLWString& label) : mImage(image), mLabel(label) {} + LLPointer<LLImageGL> mImage; + LLWString mLabel; + }; + const embedded_data_t* getEmbeddedCharData(const llwchar wch) const; + F32 getEmbeddedCharAdvance(const embedded_data_t* ext_data) const; + void clearEmbeddedChars(); + +public: + static F32 sVertDPI; + static F32 sHorizDPI; + static F32 sScaleX; + static F32 sScaleY; + static LLString sAppDir; // For loading fonts + + static LLFontGL* sMonospace; // medium + + static LLFontGL* sSansSerifSmall; // small + static LLFontList* sSSSmallFallback; + static LLFontGL* sSansSerif; // medium + static LLFontList* sSSFallback; + static LLFontGL* sSansSerifBig; // large + static LLFontList* sSSBigFallback; + static LLFontGL* sSansSerifHuge; // very large + static LLFontList* sSSHugeFallback; + + static LLFontGL* sSansSerifBold; // medium, bolded + static LLFontList* sSSBoldFallback; + + static LLColor4 sShadowColor; + + friend class LLTextBillboard; + friend class LLHUDText; + +protected: + /*virtual*/ BOOL addChar(const llwchar wch); + static LLString getFontPathLocal(); + static LLString getFontPathSystem(); + +protected: + LLPointer<LLImageRaw> mRawImageGLp; + LLPointer<LLImageGL> mImageGLp; + typedef std::map<llwchar,embedded_data_t*> embedded_map_t; + embedded_map_t mEmbeddedChars; + +public: + static LLCoordFont sCurOrigin; + static std::vector<LLCoordFont> sOriginStack; +}; + +#endif diff --git a/indra/llrender/llgldbg.cpp b/indra/llrender/llgldbg.cpp new file mode 100644 index 0000000000..2c61ebb851 --- /dev/null +++ b/indra/llrender/llgldbg.cpp @@ -0,0 +1,204 @@ +/** + * @file llgldbg.cpp + * @brief Definitions for OpenGL debugging support + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +// This file sets some global GL parameters, and implements some +// useful functions for GL operations. + +#include "linden_common.h" + +#include "llglheaders.h" + +#include "llgl.h" + + +//------------------------------------------------------------------------ +// cmstr() +//------------------------------------------------------------------------ +char *cmstr(int i) +{ + switch( i ) + { + case GL_EMISSION: return "GL_EMISSION"; + case GL_AMBIENT: return "GL_AMBIENT"; + case GL_DIFFUSE: return "GL_DIFFUSE"; + case GL_SPECULAR: return "GL_SPECULAR"; + case GL_AMBIENT_AND_DIFFUSE: return "GL_AMBIENT_AND_DIFFUSE"; + } + return "UNKNOWN"; +} + +//------------------------------------------------------------------------ +// facestr() +//------------------------------------------------------------------------ +char *facestr(int i) +{ + switch( i ) + { + case GL_FRONT: return "GL_FRONT"; + case GL_BACK: return "GL_BACK"; + case GL_FRONT_AND_BACK: return "GL_FRONT_AND_BACK"; + } + return "UNKNOWN"; +} + +//------------------------------------------------------------------------ +// boolstr() +//------------------------------------------------------------------------ +const char *boolstr(int b) +{ + return b ? "GL_TRUE" : "GL_FALSE"; +} + +//------------------------------------------------------------------------ +// fv4() +//------------------------------------------------------------------------ +char *fv4(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f %8.3f %8.3f %8.3f", f[0], f[1], f[2], f[3]); + return str; +} + +//------------------------------------------------------------------------ +// fv3() +//------------------------------------------------------------------------ +char *fv3(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f, %8.3f, %8.3f", f[0], f[1], f[2]); + return str; +} + +//------------------------------------------------------------------------ +// fv1() +//------------------------------------------------------------------------ +char *fv1(F32 *f) +{ + static char str[128]; + sprintf(str, "%8.3f", f[0]); + return str; +} + +//------------------------------------------------------------------------ +// llgl_dump() +//------------------------------------------------------------------------ +void llgl_dump() +{ + int i; + F32 fv[16]; + GLboolean b; + + llinfos << "==========================" << llendl; + llinfos << "OpenGL State" << llendl; + llinfos << "==========================" << llendl; + + llinfos << "-----------------------------------" << llendl; + llinfos << "Current Values" << llendl; + llinfos << "-----------------------------------" << llendl; + + glGetFloatv(GL_CURRENT_COLOR, fv); + llinfos << "GL_CURRENT_COLOR : " << fv4(fv) << llendl; + + glGetFloatv(GL_CURRENT_NORMAL, fv); + llinfos << "GL_CURRENT_NORMAL : " << fv3(fv) << llendl; + + llinfos << "-----------------------------------" << llendl; + llinfos << "Lighting" << llendl; + llinfos << "-----------------------------------" << llendl; + + llinfos << "GL_LIGHTING : " << boolstr(glIsEnabled(GL_LIGHTING)) << llendl; + + llinfos << "GL_COLOR_MATERIAL : " << boolstr(glIsEnabled(GL_COLOR_MATERIAL)) << llendl; + + glGetIntegerv(GL_COLOR_MATERIAL_PARAMETER, (GLint*)&i); + llinfos << "GL_COLOR_MATERIAL_PARAMETER: " << cmstr(i) << llendl; + + glGetIntegerv(GL_COLOR_MATERIAL_FACE, (GLint*)&i); + llinfos << "GL_COLOR_MATERIAL_FACE : " << facestr(i) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_AMBIENT, fv); + llinfos << "GL_AMBIENT material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_DIFFUSE, fv); + llinfos << "GL_DIFFUSE material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_SPECULAR, fv); + llinfos << "GL_SPECULAR material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_EMISSION, fv); + llinfos << "GL_EMISSION material : " << fv4(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetMaterialfv(GL_FRONT, GL_SHININESS, fv); + llinfos << "GL_SHININESS material : " << fv1(fv) << llendl; + + fv[0] = fv[1] = fv[2] = fv[3] = 12345.6789f; + glGetFloatv(GL_LIGHT_MODEL_AMBIENT, fv); + llinfos << "GL_LIGHT_MODEL_AMBIENT : " << fv4(fv) << llendl; + + glGetBooleanv(GL_LIGHT_MODEL_LOCAL_VIEWER, &b); + llinfos << "GL_LIGHT_MODEL_LOCAL_VIEWER: " << boolstr(b) << llendl; + + glGetBooleanv(GL_LIGHT_MODEL_TWO_SIDE, &b); + llinfos << "GL_LIGHT_MODEL_TWO_SIDE : " << boolstr(b) << llendl; + + for (int l=0; l<8; l++) + { + b = glIsEnabled(GL_LIGHT0+l); + llinfos << "GL_LIGHT" << l << " : " << boolstr(b) << llendl; + + if (!b) + continue; + + glGetLightfv(GL_LIGHT0+l, GL_AMBIENT, fv); + llinfos << " GL_AMBIENT light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_DIFFUSE, fv); + llinfos << " GL_DIFFUSE light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPECULAR, fv); + llinfos << " GL_SPECULAR light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_POSITION, fv); + llinfos << " GL_POSITION light : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_CONSTANT_ATTENUATION, fv); + llinfos << " GL_CONSTANT_ATTENUATION : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_QUADRATIC_ATTENUATION, fv); + llinfos << " GL_QUADRATIC_ATTENUATION : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_DIRECTION, fv); + llinfos << " GL_SPOT_DIRECTION : " << fv4(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_EXPONENT, fv); + llinfos << " GL_SPOT_EXPONENT : " << fv1(fv) << llendl; + + glGetLightfv(GL_LIGHT0+l, GL_SPOT_CUTOFF, fv); + llinfos << " GL_SPOT_CUTOFF : " << fv1(fv) << llendl; + } + + llinfos << "-----------------------------------" << llendl; + llinfos << "Pixel Operations" << llendl; + llinfos << "-----------------------------------" << llendl; + + llinfos << "GL_ALPHA_TEST : " << boolstr(glIsEnabled(GL_ALPHA_TEST)) << llendl; + llinfos << "GL_DEPTH_TEST : " << boolstr(glIsEnabled(GL_DEPTH_TEST)) << llendl; + + glGetBooleanv(GL_DEPTH_WRITEMASK, &b); + llinfos << "GL_DEPTH_WRITEMASK : " << boolstr(b) << llendl; + + llinfos << "GL_BLEND : " << boolstr(glIsEnabled(GL_BLEND)) << llendl; + llinfos << "GL_DITHER : " << boolstr(glIsEnabled(GL_DITHER)) << llendl; +} + +// End diff --git a/indra/llrender/llgldbg.h b/indra/llrender/llgldbg.h new file mode 100644 index 0000000000..9cb15dc316 --- /dev/null +++ b/indra/llrender/llgldbg.h @@ -0,0 +1,16 @@ +/** + * @file llgldbg.h + * @brief Definitions for OpenGL debugging support + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLGLDBG_H +#define LL_LLGLDBG_H + +// Dumps the current OpenGL state to the console. +void llgl_dump(); + + +#endif // LL_LLGLDBG_H diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp new file mode 100644 index 0000000000..f26223e32b --- /dev/null +++ b/indra/llrender/llimagegl.cpp @@ -0,0 +1,1205 @@ +/** + * @file llimagegl.cpp + * @brief Generic GL image handler + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +// TODO: create 2 classes for images w/ and w/o discard levels? + +#include "linden_common.h" + +#include "llimagegl.h" + +#include "llerror.h" +#include "llimage.h" + +#include "llmath.h" +#include "llgl.h" + +//---------------------------------------------------------------------------- + +const F32 MIN_TEXTURE_LIFETIME = 10.f; + +//statics +LLGLuint LLImageGL::sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS] = { 0 }; + +S32 LLImageGL::sGlobalTextureMemory = 0; +S32 LLImageGL::sBoundTextureMemory = 0; +S32 LLImageGL::sCurBoundTextureMemory = 0; +S32 LLImageGL::sCount = 0; + +BOOL LLImageGL::sGlobalUseAnisotropic = FALSE; +F32 LLImageGL::sLastFrameTime = 0.f; + +std::set<LLImageGL*> LLImageGL::sImageList; + +//---------------------------------------------------------------------------- + +//static +S32 LLImageGL::dataFormatBits(S32 dataformat) +{ + switch (dataformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 8; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 8; + case GL_LUMINANCE: return 8; + case GL_ALPHA: return 8; + case GL_COLOR_INDEX: return 8; + case GL_LUMINANCE_ALPHA: return 16; + case GL_RGB: return 24; + case GL_RGB8: return 24; + case GL_RGBA: return 32; + case GL_BGRA: return 32; // Used for QuickTime media textures on the Mac + default: + llerrs << "LLImageGL::Unknown format: " << dataformat << llendl; + return 0; + } +} + +//static +S32 LLImageGL::dataFormatBytes(S32 dataformat, S32 width, S32 height) +{ + if (dataformat >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && + dataformat <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + if (width < 4) width = 4; + if (height < 4) height = 4; + } + S32 bytes ((width*height*dataFormatBits(dataformat)+7)>>3); + S32 aligned = (bytes+3)&~3; + return aligned; +} + +//static +S32 LLImageGL::dataFormatComponents(S32 dataformat) +{ + switch (dataformat) + { + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return 3; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return 4; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return 4; + case GL_LUMINANCE: return 1; + case GL_ALPHA: return 1; + case GL_COLOR_INDEX: return 1; + case GL_LUMINANCE_ALPHA: return 2; + case GL_RGB: return 3; + case GL_RGBA: return 4; + case GL_BGRA: return 4; // Used for QuickTime media textures on the Mac + default: + llerrs << "LLImageGL::Unknown format: " << dataformat << llendl; + return 0; + } +} + +//---------------------------------------------------------------------------- + +// static +void LLImageGL::bindExternalTexture(LLGLuint gl_name, S32 stage, LLGLenum bind_target ) +{ + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + glBindTexture(bind_target, gl_name); + sCurrentBoundTextures[stage] = gl_name; +} + +// static +void LLImageGL::unbindTexture(S32 stage, LLGLenum bind_target) +{ + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + glBindTexture(bind_target, 0); + sCurrentBoundTextures[stage] = 0; +} + +// static +void LLImageGL::updateStats(F32 current_time) +{ + sLastFrameTime = current_time; + sBoundTextureMemory = sCurBoundTextureMemory; + sCurBoundTextureMemory = 0; +} + +//static +S32 LLImageGL::updateBoundTexMem(const S32 delta) +{ + LLImageGL::sCurBoundTextureMemory += delta; + return LLImageGL::sCurBoundTextureMemory; +} + +//---------------------------------------------------------------------------- + +//static +void LLImageGL::destroyGL(BOOL save_state) +{ + for (S32 stage = 0; stage < gGLManager.mNumTextureUnits; stage++) + { + LLImageGL::unbindTexture(stage, GL_TEXTURE_2D); + } + for (std::set<LLImageGL*>::iterator iter = sImageList.begin(); + iter != sImageList.end(); iter++) + { + LLImageGL* glimage = *iter; + if (glimage->mTexName && glimage->mComponents) + { + if (save_state) + { + glimage->mSaveData = new LLImageRaw; + glimage->readBackRaw(glimage->mCurrentDiscardLevel, glimage->mSaveData); + } + glimage->destroyGLTexture(); + stop_glerror(); + } + } +} + +//static +void LLImageGL::restoreGL() +{ + for (std::set<LLImageGL*>::iterator iter = sImageList.begin(); + iter != sImageList.end(); iter++) + { + LLImageGL* glimage = *iter; + if (glimage->mSaveData.notNull() && glimage->mSaveData->getComponents()) + { + if (glimage->getComponents()) + { + glimage->createGLTexture(glimage->mCurrentDiscardLevel, glimage->mSaveData); + stop_glerror(); + } + glimage->mSaveData = NULL; // deletes data + } + } +} + +//---------------------------------------------------------------------------- + +//static +BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, BOOL usemipmaps) +{ + dest = new LLImageGL(usemipmaps); + return TRUE; +} + +BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, U32 width, U32 height, U8 components, BOOL usemipmaps) +{ + dest = new LLImageGL(width, height, components, usemipmaps); + return TRUE; +} + +BOOL LLImageGL::create(LLPointer<LLImageGL>& dest, const LLImageRaw* imageraw, BOOL usemipmaps) +{ + dest = new LLImageGL(imageraw, usemipmaps); + return TRUE; +} + +//---------------------------------------------------------------------------- + +LLImageGL::LLImageGL(BOOL usemipmaps) + : mSaveData(0) +{ + init(usemipmaps); + setSize(0, 0, 0); + sImageList.insert(this); + sCount++; +} + +LLImageGL::LLImageGL(U32 width, U32 height, U8 components, BOOL usemipmaps) + : mSaveData(0) +{ + llassert( components <= 4 ); + init(usemipmaps); + setSize(width, height, components); + sImageList.insert(this); + sCount++; +} + +LLImageGL::LLImageGL(const LLImageRaw* imageraw, BOOL usemipmaps) + : mSaveData(0) +{ + init(usemipmaps); + setSize(0, 0, 0); + sImageList.insert(this); + sCount++; + createGLTexture(0, imageraw); +} + +LLImageGL::~LLImageGL() +{ + LLImageGL::cleanup(); + sImageList.erase(this); + sCount--; +} + +void LLImageGL::init(BOOL usemipmaps) +{ +#ifdef DEBUG_MISS + mMissed = FALSE; +#endif + + mTextureMemory = 0; + mLastBindTime = 0.f; + + mTarget = GL_TEXTURE_2D; + mBindTarget = GL_TEXTURE_2D; + mUseMipMaps = usemipmaps; + mHasMipMaps = FALSE; + mAutoGenMips = FALSE; + mTexName = 0; + mIsResident = 0; + mClampS = FALSE; + mClampT = FALSE; + mMipFilterNearest = FALSE; + mWidth = 0; + mHeight = 0; + mComponents = 0; + + mMaxDiscardLevel = MAX_DISCARD_LEVEL; + mCurrentDiscardLevel = -1; + mDontDiscard = FALSE; + + mFormatInternal = -1; + mFormatPrimary = (LLGLenum) 0; + mFormatType = GL_UNSIGNED_BYTE; + mFormatSwapBytes = FALSE; + mHasExplicitFormat = FALSE; +} + +void LLImageGL::cleanup() +{ + if (!gGLManager.mIsDisabled) + { + destroyGLTexture(); + } + mSaveData = NULL; // deletes data +} + +//---------------------------------------------------------------------------- + +static bool check_power_of_two(S32 dim) +{ + while(dim > 1) + { + if (dim & 1) + { + return false; + } + dim >>= 1; + } + return true; +} + +//static +bool LLImageGL::checkSize(S32 width, S32 height) +{ + return check_power_of_two(width) && check_power_of_two(height); +} + +void LLImageGL::setSize(S32 width, S32 height, S32 ncomponents) +{ + if (width != mWidth || height != mHeight || ncomponents != mComponents) + { + // Check if dimensions are a power of two! + if (!checkSize(width,height)) + { + llerrs << llformat("Texture has non power of two dimention: %dx%d",width,height) << llendl; + } + + if (mTexName) + { +// llwarns << "Setting Size of LLImageGL with existing mTexName = " << mTexName << llendl; + destroyGLTexture(); + } + + mWidth = width; + mHeight = height; + mComponents = ncomponents; + if (ncomponents > 0) + { + mMaxDiscardLevel = 0; + while (width > 1 && height > 1 && mMaxDiscardLevel < MAX_DISCARD_LEVEL) + { + mMaxDiscardLevel++; + width >>= 1; + height >>= 1; + } + } + else + { + mMaxDiscardLevel = MAX_DISCARD_LEVEL; + } + } +} + +//---------------------------------------------------------------------------- + +// virtual +void LLImageGL::dump() +{ + llinfos << "mMaxDiscardLevel " << S32(mMaxDiscardLevel) + << " mLastBindTime " << mLastBindTime + << " mTarget " << S32(mTarget) + << " mBindTarget " << S32(mBindTarget) + << " mUseMipMaps " << S32(mUseMipMaps) + << " mHasMipMaps " << S32(mHasMipMaps) + << " mCurrentDiscardLevel " << S32(mCurrentDiscardLevel) + << " mFormatInternal " << S32(mFormatInternal) + << " mFormatPrimary " << S32(mFormatPrimary) + << " mFormatType " << S32(mFormatType) + << " mFormatSwapBytes " << S32(mFormatSwapBytes) + << " mHasExplicitFormat " << S32(mHasExplicitFormat) +#if DEBUG_MISS + << " mMissed " << mMissed +#endif + << llendl; + + llinfos << " mTextureMemory " << mTextureMemory + << " mTexNames " << mTexName + << " mIsResident " << S32(mIsResident) + << llendl; +} + +//---------------------------------------------------------------------------- + +BOOL LLImageGL::bindTextureInternal(const S32 stage) const +{ + if (gGLManager.mIsDisabled) + { + llwarns << "Trying to bind a texture while GL is disabled!" << llendl; + } + + stop_glerror(); + + glActiveTextureARB(GL_TEXTURE0_ARB + stage); + //glClientActiveTextureARB(GL_TEXTURE0_ARB + stage); + + stop_glerror(); + + if (sCurrentBoundTextures[stage] && sCurrentBoundTextures[stage] == mTexName) + { + // already set! + return TRUE; + } + + if (mTexName != 0) + { +#ifdef DEBUG_MISS + mMissed = ! getIsResident(TRUE); +#endif + + glBindTexture(mBindTarget, mTexName); + sCurrentBoundTextures[stage] = mTexName; + stop_glerror(); + + if (mLastBindTime != sLastFrameTime) + { + // we haven't accounted for this texture yet this frame + updateBoundTexMem(mTextureMemory); + mLastBindTime = sLastFrameTime; + } + + return TRUE; + } + else + { + glBindTexture(mBindTarget, 0); + sCurrentBoundTextures[stage] = 0; + return FALSE; + } +} + +//virtual +BOOL LLImageGL::bind(const S32 stage) const +{ + if (stage == -1) + { + return FALSE; + } + BOOL res = bindTextureInternal(stage); + //llassert(res); + return res; +} + +void LLImageGL::setExplicitFormat( LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, BOOL swap_bytes ) +{ + // Note: must be called before createTexture() + // Note: it's up to the caller to ensure that the format matches the number of components. + mHasExplicitFormat = TRUE; + mFormatInternal = internal_format; + mFormatPrimary = primary_format; + if(type_format == 0) + mFormatType = GL_UNSIGNED_BYTE; + else + mFormatType = type_format; + mFormatSwapBytes = swap_bytes; +} + +//---------------------------------------------------------------------------- + +void LLImageGL::setImage(const LLImageRaw* imageraw) +{ + llassert((imageraw->getWidth() == getWidth(mCurrentDiscardLevel)) && + (imageraw->getHeight() == getHeight(mCurrentDiscardLevel)) && + (imageraw->getComponents() == getComponents())); + const U8* rawdata = imageraw->getData(); + setImage(rawdata, FALSE); +} + +void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) +{ +// LLFastTimer t1(LLFastTimer::FTM_TEMP1); + + bool is_compressed = false; + if (mFormatPrimary >= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT && mFormatPrimary <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + is_compressed = true; + } + + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP2); + llverify(bindTextureInternal(0)); + } + + if (mUseMipMaps) + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP3); + if (data_hasmips) + { + // NOTE: data_in points to largest image; smaller images + // are stored BEFORE the largest image + for (S32 d=mCurrentDiscardLevel; d<=mMaxDiscardLevel; d++) + { + S32 w = getWidth(d); + S32 h = getHeight(d); + S32 gl_level = d-mCurrentDiscardLevel; + if (d > mCurrentDiscardLevel) + { + data_in -= dataFormatBytes(mFormatPrimary, w, h); // see above comment + } + if (is_compressed) + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + S32 tex_size = dataFormatBytes(mFormatPrimary, w, h); + glCompressedTexImage2DARB(mTarget, gl_level, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in); + stop_glerror(); + } + else + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, gl_level, mFormatInternal, w, h, 0, mFormatPrimary, GL_UNSIGNED_BYTE, (GLvoid*)data_in); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + stop_glerror(); + } + stop_glerror(); + } + } + else if (!is_compressed) + { + if (mAutoGenMips) + { + glTexParameteri(mBindTarget, GL_GENERATE_MIPMAP_SGIS, TRUE); + stop_glerror(); + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP4); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, 0, mFormatInternal, + getWidth(mCurrentDiscardLevel), getHeight(mCurrentDiscardLevel), 0, + mFormatPrimary, mFormatType, + data_in); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + } + } + else + { + // Create mips by hand + // about 30% faster than autogen on ATI 9800, 50% slower on nVidia 4800 + // ~4x faster than gluBuild2DMipmaps + S32 width = getWidth(mCurrentDiscardLevel); + S32 height = getHeight(mCurrentDiscardLevel); + S32 nummips = mMaxDiscardLevel - mCurrentDiscardLevel + 1; + S32 w = width, h = height; + const U8* prev_mip_data = 0; + const U8* cur_mip_data = 0; + for (int m=0; m<nummips; m++) + { + if (m==0) + { + cur_mip_data = data_in; + } + else + { + S32 bytes = w * h * mComponents; + U8* new_data = new U8[bytes]; + LLImageBase::generateMip(prev_mip_data, new_data, w, h, mComponents); + cur_mip_data = new_data; + } + llassert(w > 0 && h > 0 && cur_mip_data); + { +// LLFastTimer t1(LLFastTimer::FTM_TEMP4); + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, m, mFormatInternal, w, h, 0, mFormatPrimary, mFormatType, cur_mip_data); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + } + if (prev_mip_data && prev_mip_data != data_in) + { + delete[] prev_mip_data; + } + prev_mip_data = cur_mip_data; + w >>= 1; + h >>= 1; + } + if (prev_mip_data && prev_mip_data != data_in) + { + delete[] prev_mip_data; + } + } + } + else + { + llerrs << "Compressed Image has mipmaps but data does not (can not auto generate compressed mips)" << llendl; + } + mHasMipMaps = TRUE; + } + else + { +// LLFastTimer t2(LLFastTimer::FTM_TEMP5); + S32 w = getWidth(); + S32 h = getHeight(); + if (is_compressed) + { + S32 tex_size = dataFormatBytes(mFormatPrimary, w, h); + glCompressedTexImage2DARB(mTarget, 0, mFormatPrimary, w, h, 0, tex_size, (GLvoid *)data_in); + stop_glerror(); + } + else + { + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + glTexImage2D(mTarget, 0, mFormatInternal, w, h, 0, + mFormatPrimary, mFormatType, (GLvoid *)data_in); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + } + mHasMipMaps = FALSE; + } + stop_glerror(); +} + +BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + if (!width || !height) + { + return TRUE; + } + if (mTexName == 0) + { + llwarns << "Setting subimage on image without GL texture" << llendl; + return FALSE; + } + + if (x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight()) + { + setImage(datap, FALSE); + } + else + { + if (mUseMipMaps) + { + dump(); + llerrs << "setSubImage called with mipmapped image (not supported)" << llendl; + } + llassert(mCurrentDiscardLevel == 0); + if (((x_pos + width) > getWidth()) || + (y_pos + height) > getHeight()) + { + dump(); + llerrs << "Subimage not wholly in target image!" + << " x_pos " << x_pos + << " y_pos " << y_pos + << " width " << width + << " height " << height + << " getWidth() " << getWidth() + << " getHeight() " << getHeight() + << llendl; + } + + if ((x_pos + width) > data_width || + (y_pos + height) > data_height) + { + dump(); + llerrs << "Subimage not wholly in source image!" + << " x_pos " << x_pos + << " y_pos " << y_pos + << " width " << width + << " height " << height + << " source_width " << data_width + << " source_height " << data_height + << llendl; + } + + + glPixelStorei(GL_UNPACK_ROW_LENGTH, data_width); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + stop_glerror(); + } + + datap += (y_pos * data_width + x_pos) * getComponents(); + // Update the GL texture + llverify(bindTextureInternal(0)); + stop_glerror(); + + glTexSubImage2D(mTarget, 0, x_pos, y_pos, + width, height, mFormatPrimary, mFormatType, datap); + stop_glerror(); + + if(mFormatSwapBytes) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + stop_glerror(); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + stop_glerror(); + } + + return TRUE; +} + +BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height); +} + +// Copy sub image from frame buffer +BOOL LLImageGL::setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height) +{ + if (bindTextureInternal(0)) + { + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, fb_x, fb_y, x_pos, y_pos, width, height); + stop_glerror(); + return TRUE; + } + else + { + return FALSE; + } +} + +BOOL LLImageGL::createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename/*=0*/) +{ + if (gGLManager.mIsDisabled) + { + llwarns << "Trying to create a texture while GL is disabled!" << llendl; + return FALSE; + } + llassert(gGLManager.mInited || gNoRender); + stop_glerror(); + + if (discard_level < 0) + { + llassert(mCurrentDiscardLevel >= 0); + discard_level = mCurrentDiscardLevel; + } + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + // Actual image width/height = raw image width/height * 2^discard_level + S32 w = imageraw->getWidth() << discard_level; + S32 h = imageraw->getHeight() << discard_level; + + // setSize may call destroyGLTexture if the size does not match + setSize(w, h, imageraw->getComponents()); + + if( !mHasExplicitFormat ) + { + switch (mComponents) + { + case 1: + // Use luminance alpha (for fonts) + mFormatInternal = GL_LUMINANCE8; + mFormatPrimary = GL_LUMINANCE; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 2: + // Use luminance alpha (for fonts) + mFormatInternal = GL_LUMINANCE8_ALPHA8; + mFormatPrimary = GL_LUMINANCE_ALPHA; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 3: + mFormatInternal = GL_RGB8; + mFormatPrimary = GL_RGB; + mFormatType = GL_UNSIGNED_BYTE; + break; + case 4: + mFormatInternal = GL_RGBA8; + mFormatPrimary = GL_RGBA; + mFormatType = GL_UNSIGNED_BYTE; + break; + default: + llerrs << "Bad number of components for texture: " << (U32)getComponents() << llendl; + } + } + + const U8* rawdata = imageraw->getData(); + return createGLTexture(discard_level, rawdata, FALSE, usename); +} + +BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_hasmips, S32 usename) +{ + llassert(data_in); + + if (discard_level < 0) + { + llassert(mCurrentDiscardLevel >= 0); + discard_level = mCurrentDiscardLevel; + } + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + if (mTexName != 0 && discard_level == mCurrentDiscardLevel) + { + // This will only be true if the size has not changed + setImage(data_in, data_hasmips); + return TRUE; + } + + GLuint old_name = mTexName; +// S32 old_discard = mCurrentDiscardLevel; + + if (usename != 0) + { + mTexName = usename; + } + else + { + glGenTextures(1, (GLuint*)&mTexName); + stop_glerror(); + { +// LLFastTimer t1(LLFastTimer::FTM_TEMP6); + llverify(bindTextureInternal(0)); + glTexParameteri(mBindTarget, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(mBindTarget, GL_TEXTURE_MAX_LEVEL, mMaxDiscardLevel-discard_level); + } + } + if (!mTexName) + { + llerrs << "LLImageGL::createGLTexture failed to make texture" << llendl; + } + + if (mUseMipMaps) + { + mAutoGenMips = gGLManager.mHasMipMapGeneration; +#if LL_DARWIN + // On the Mac GF2 and GF4MX drivers, auto mipmap generation doesn't work right with alpha-only textures. + if(gGLManager.mIsGF2or4MX && (mFormatInternal == GL_ALPHA8) && (mFormatPrimary == GL_ALPHA)) + { + mAutoGenMips = FALSE; + } +#endif + } + + mCurrentDiscardLevel = discard_level; + + setImage(data_in, data_hasmips); + + setClamp(mClampS, mClampT); + setMipFilterNearest(mMipFilterNearest); + + // things will break if we don't unbind after creation + unbindTexture(0, mBindTarget); + stop_glerror(); + + if (old_name != 0) + { + sGlobalTextureMemory -= mTextureMemory; + glDeleteTextures(1, &old_name); + stop_glerror(); + } + + mTextureMemory = getMipBytes(discard_level); + sGlobalTextureMemory += mTextureMemory; + + // mark this as bound at this point, so we don't throw it out immediately + mLastBindTime = sLastFrameTime; + + return TRUE; +} + +BOOL LLImageGL::setDiscardLevel(S32 discard_level) +{ + llassert(discard_level >= 0); + llassert(mCurrentDiscardLevel >= 0); + + discard_level = llclamp(discard_level, 0, (S32)mMaxDiscardLevel); + + if (mDontDiscard) + { + // don't discard! + return FALSE; + } + else if (discard_level == mCurrentDiscardLevel) + { + // nothing to do + return FALSE; + } + else if (discard_level < mCurrentDiscardLevel) + { + // larger image + dump(); + llerrs << "LLImageGL::setDiscardLevel() called with larger discard level; use createGLTexture()" << llendl; + return FALSE; + } + else if (mUseMipMaps) + { + LLPointer<LLImageRaw> imageraw = new LLImageRaw; + while(discard_level > mCurrentDiscardLevel) + { + if (readBackRaw(discard_level, imageraw)) + { + break; + } + discard_level--; + } + if (discard_level == mCurrentDiscardLevel) + { + // unable to increase the discard level + return FALSE; + } + return createGLTexture(discard_level, imageraw); + } + else + { +#ifndef LL_LINUX // FIXME: This should not be skipped for the linux client. + llerrs << "LLImageGL::setDiscardLevel() called on image without mipmaps" << llendl; +#endif + return FALSE; + } +} + +BOOL LLImageGL::readBackRaw(S32 discard_level, LLImageRaw* imageraw) +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + + if (mTexName == 0 || discard_level < mCurrentDiscardLevel) + { + return FALSE; + } + + S32 gl_discard = discard_level - mCurrentDiscardLevel; + + llverify(bindTextureInternal(0)); + + LLGLint glwidth = 0; + glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_WIDTH, (GLint*)&glwidth); + if (glwidth == 0) + { + // No mip data smaller than current discard level + return FALSE; + } + + S32 width = getWidth(discard_level); + S32 height = getHeight(discard_level); + S32 ncomponents = getComponents(); + if (ncomponents == 0) + { + return FALSE; + } + + if (width <= 0 || width > 2048 || height <= 0 || height > 2048 || ncomponents < 1 || ncomponents > 4) + { + llerrs << llformat("LLImageGL::readBackRaw: bogus params: %d x %d x %d",width,height,ncomponents) << llendl; + } + + LLGLint is_compressed = 0; + glGetTexLevelParameteriv(mTarget, is_compressed, GL_TEXTURE_COMPRESSED, (GLint*)&is_compressed); + if (is_compressed) + { + LLGLint glbytes; + glGetTexLevelParameteriv(mTarget, gl_discard, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, (GLint*)&glbytes); + imageraw->allocateDataSize(width, height, ncomponents, glbytes); + glGetCompressedTexImageARB(mTarget, gl_discard, (GLvoid*)(imageraw->getData())); + stop_glerror(); + } + else + { + imageraw->allocateDataSize(width, height, ncomponents); + glGetTexImage(GL_TEXTURE_2D, gl_discard, mFormatPrimary, mFormatType, (GLvoid*)(imageraw->getData())); + stop_glerror(); + } + + return TRUE; +} + +void LLImageGL::destroyGLTexture() +{ + stop_glerror(); + + if (mTexName != 0) + { + for (int i = 0; i < gGLManager.mNumTextureUnits; i++) + { + if (sCurrentBoundTextures[i] == mTexName) + { + unbindTexture(i, GL_TEXTURE_2D); + stop_glerror(); + } + } + + sGlobalTextureMemory -= mTextureMemory; + mTextureMemory = 0; + + glDeleteTextures(1, (GLuint*)&mTexName); + mTexName = 0; + + stop_glerror(); + } +} + +//---------------------------------------------------------------------------- + +void LLImageGL::setClamp(BOOL clamps, BOOL clampt) +{ + mClampS = clamps; + mClampT = clampt; + if (mTexName != 0) + { + glTexParameteri(mBindTarget, GL_TEXTURE_WRAP_S, clamps ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(mBindTarget, GL_TEXTURE_WRAP_T, clampt ? GL_CLAMP_TO_EDGE : GL_REPEAT); + } + stop_glerror(); +} + +void LLImageGL::setMipFilterNearest(BOOL nearest, BOOL min_nearest) +{ + mMipFilterNearest = nearest; + + if (mTexName != 0) + { + if (min_nearest) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else if (mHasMipMaps) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + else + { + glTexParameteri(mBindTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + if (mMipFilterNearest) + { + glTexParameteri(mBindTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else + { + glTexParameteri(mBindTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + if (gGLManager.mHasAnisotropic) + { + if (sGlobalUseAnisotropic && !mMipFilterNearest) + { + F32 largest_anisotropy; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_anisotropy); + glTexParameterf(mBindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_anisotropy); + } + else + { + glTexParameterf(mBindTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.f); + } + } + } + + stop_glerror(); +} + +BOOL LLImageGL::getIsResident(BOOL test_now) +{ + if (test_now) + { + if (mTexName != 0) + { + glAreTexturesResident(1, (GLuint*)&mTexName, &mIsResident); + } + else + { + mIsResident = FALSE; + } + } + + return mIsResident; +} + +S32 LLImageGL::getHeight(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 height = mHeight >> discard_level; + if (height < 1) height = 1; + return height; +} + +S32 LLImageGL::getWidth(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 width = mWidth >> discard_level; + if (width < 1) width = 1; + return width; +} + +S32 LLImageGL::getBytes(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 w = mWidth>>discard_level; + S32 h = mHeight>>discard_level; + if (w == 0) w = 1; + if (h == 0) h = 1; + return dataFormatBytes(mFormatPrimary, w, h); +} + +S32 LLImageGL::getMipBytes(S32 discard_level) const +{ + if (discard_level < 0) + { + discard_level = mCurrentDiscardLevel; + } + S32 w = mWidth>>discard_level; + S32 h = mHeight>>discard_level; + S32 res = dataFormatBytes(mFormatPrimary, w, h); + if (mUseMipMaps) + { + while (w > 1 && h > 1) + { + w >>= 1; if (w == 0) w = 1; + h >>= 1; if (h == 0) h = 1; + res += dataFormatBytes(mFormatPrimary, w, h); + } + } + return res; +} + +BOOL LLImageGL::getBoundRecently() const +{ + return (BOOL)(sLastFrameTime - mLastBindTime < MIN_TEXTURE_LIFETIME); +} + +void LLImageGL::setTarget(const LLGLenum target, const LLGLenum bind_target) +{ + mTarget = target; + mBindTarget = bind_target; +} + +//---------------------------------------------------------------------------- + +// Manual Mip Generation +/* + S32 width = getWidth(discard_level); + S32 height = getHeight(discard_level); + S32 w = width, h = height; + S32 nummips = 1; + while (w > 4 && h > 4) + { + w >>= 1; h >>= 1; + nummips++; + } + stop_glerror(); + w = width, h = height; + const U8* prev_mip_data = 0; + const U8* cur_mip_data = 0; + for (int m=0; m<nummips; m++) + { + if (m==0) + { + cur_mip_data = rawdata; + } + else + { + S32 bytes = w * h * mComponents; + U8* new_data = new U8[bytes]; + LLImageBase::generateMip(prev_mip_data, new_data, w, h, mComponents); + cur_mip_data = new_data; + } + llassert(w > 0 && h > 0 && cur_mip_data); + U8 test = cur_mip_data[w*h*mComponents-1]; + { + glTexImage2D(mTarget, m, mFormatInternal, w, h, 0, mFormatPrimary, mFormatType, cur_mip_data); + stop_glerror(); + } + if (prev_mip_data && prev_mip_data != rawdata) + { + delete prev_mip_data; + } + prev_mip_data = cur_mip_data; + w >>= 1; + h >>= 1; + } + if (prev_mip_data && prev_mip_data != rawdata) + { + delete prev_mip_data; + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, nummips); +*/ diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h new file mode 100644 index 0000000000..f8c6a008eb --- /dev/null +++ b/indra/llrender/llimagegl.h @@ -0,0 +1,185 @@ +/** + * @file llimagegl.h + * @brief Object for managing images and their textures + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + + +#ifndef LL_LLIMAGEGL_H +#define LL_LLIMAGEGL_H + +#include "llimage.h" + +#include "llgltypes.h" +#include "llmemory.h" + +//============================================================================ + +class LLImageGL : public LLThreadSafeRefCount +{ +public: + // Size calculation + static S32 dataFormatBits(S32 dataformat); + static S32 dataFormatBytes(S32 dataformat, S32 width, S32 height); + static S32 dataFormatComponents(S32 dataformat); + + // Wrapper for glBindTexture that keeps LLImageGL in sync. + // Usually you want stage = 0 and bind_target = GL_TEXTURE_2D + static void bindExternalTexture( LLGLuint gl_name, S32 stage, LLGLenum bind_target); + static void unbindTexture(S32 stage, LLGLenum target); + + // needs to be called every frame + static void updateStats(F32 current_time); + + // Save off / restore GL textures + static void destroyGL(BOOL save_state = TRUE); + static void restoreGL(); + + // Sometimes called externally for textures not using LLImageGL (should go away...) + static S32 updateBoundTexMem(const S32 delta); + + static bool checkSize(S32 width, S32 height); + + // Not currently necessary for LLImageGL, but required in some derived classes, + // so include for compatability + static BOOL create(LLPointer<LLImageGL>& dest, BOOL usemipmaps = TRUE); + static BOOL create(LLPointer<LLImageGL>& dest, U32 width, U32 height, U8 components, BOOL usemipmaps = TRUE); + static BOOL create(LLPointer<LLImageGL>& dest, const LLImageRaw* imageraw, BOOL usemipmaps = TRUE); + +public: + LLImageGL(BOOL usemipmaps = TRUE); + LLImageGL(U32 width, U32 height, U8 components, BOOL usemipmaps = TRUE); + LLImageGL(const LLImageRaw* imageraw, BOOL usemipmaps = TRUE); + +protected: + virtual ~LLImageGL(); + BOOL bindTextureInternal(const S32 stage = 0) const; + +public: + virtual void dump(); // debugging info to llinfos + virtual BOOL bind(const S32 stage = 0) const; + + void setSize(S32 width, S32 height, S32 ncomponents); + + BOOL createGLTexture(S32 discard_level, const LLImageRaw* imageraw, S32 usename = 0); + BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0); + void setImage(const LLImageRaw* imageraw); + void setImage(const U8* data_in, BOOL data_hasmips = FALSE); + BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setDiscardLevel(S32 discard_level); + BOOL readBackRaw(S32 discard_level, LLImageRaw* imageraw); // Read back a raw image for this discard level, if it exists + void destroyGLTexture(); + + void setClamp(BOOL clamps, BOOL clampt); + void setMipFilterNearest(BOOL nearest, BOOL min_nearest = FALSE); + void setExplicitFormat(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format = 0, BOOL swap_bytes = FALSE); + void dontDiscard() { mDontDiscard = 1; } + + S32 getDiscardLevel() const { return mCurrentDiscardLevel; } + S32 getMaxDiscardLevel() const { return mMaxDiscardLevel; } + + S32 getWidth(S32 discard_level = -1) const; + S32 getHeight(S32 discard_level = -1) const; + U8 getComponents() const { return mComponents; } + S32 getBytes(S32 discard_level = -1) const; + S32 getMipBytes(S32 discard_level = -1) const; + BOOL getBoundRecently() const; + LLGLenum getPrimaryFormat() const { return mFormatPrimary; } + + BOOL getClampS() const { return mClampS; } + BOOL getClampT() const { return mClampT; } + BOOL getMipFilterNearest() const { return mMipFilterNearest; } + + BOOL getHasGLTexture() const { return mTexName != 0; } + LLGLuint getTexName() const { return mTexName; } + + BOOL getIsResident(BOOL test_now = FALSE); // not const + + void setTarget(const LLGLenum target, const LLGLenum bind_target); + + BOOL getUseMipMaps() const { return mUseMipMaps; } + void setUseMipMaps(BOOL usemips) { mUseMipMaps = usemips; } + BOOL getUseDiscard() const { return mUseMipMaps && !mDontDiscard; } + BOOL getDontDiscard() const { return mDontDiscard; } + +protected: + void init(BOOL usemipmaps); + virtual void cleanup(); // Clean up the LLImageGL so it can be reinitialized. Be careful when using this in derived class destructors + +public: + // Various GL/Rendering options + S32 mTextureMemory; + mutable F32 mLastBindTime; // last time this was bound, by discard level + mutable F32 mLastBindAttempt; // last time bindTexture was called on this texture + +private: + LLPointer<LLImageRaw> mSaveData; // used for destroyGL/restoreGL + S8 mUseMipMaps; + S8 mHasMipMaps; + S8 mHasExplicitFormat; // If false (default), GL format is f(mComponents) + S8 mAutoGenMips; + +protected: + LLGLenum mTarget; // Normally GL_TEXTURE2D, sometimes something else (ex. cube maps) + LLGLenum mBindTarget; // NOrmally GL_TEXTURE2D, sometimes something else (ex. cube maps) + + LLGLuint mTexName; + + LLGLboolean mIsResident; + + U16 mWidth; + U16 mHeight; + + S8 mComponents; + S8 mMaxDiscardLevel; + S8 mCurrentDiscardLevel; + S8 mDontDiscard; // Keep full res version of this image (for UI, etc) + + S8 mClampS; // Need to save clamp state + S8 mClampT; + S8 mMipFilterNearest; // if TRUE, set magfilter to GL_NEAREST + + LLGLint mFormatInternal; // = GL internalformat + LLGLenum mFormatPrimary; // = GL format (pixel data format) + LLGLenum mFormatType; + BOOL mFormatSwapBytes;// if true, use glPixelStorei(GL_UNPACK_SWAP_BYTES, 1) + + // STATICS +public: + static std::set<LLImageGL*> LLImageGL::sImageList; + static S32 sCount; + + static F32 sLastFrameTime; + + static LLGLuint sCurrentBoundTextures[MAX_GL_TEXTURE_UNITS]; // Currently bound texture ID + + // Global memory statistics + static S32 sGlobalTextureMemory; // Tracks main memory texmem + static S32 sBoundTextureMemory; // Tracks bound texmem for last completed frame + static S32 sCurBoundTextureMemory; // Tracks bound texmem for current frame + + static BOOL sGlobalUseAnisotropic; + +#if DEBUG_MISS + BOOL mMissed; // Missed on last bind? + BOOL getMissed() const { return mMissed; }; +#else + BOOL getMissed() const { return FALSE; }; +#endif +}; + +//RN: maybe this needs to moved elsewhere? +class LLImageProviderInterface +{ +public: + LLImageProviderInterface() {}; + virtual ~LLImageProviderInterface() {}; + + virtual LLImageGL* getUIImageByID(const LLUUID& id, BOOL clamped = TRUE) = 0; +}; + +#endif // LL_LLIMAGEGL_H |