summaryrefslogtreecommitdiff
path: root/indra/llrender
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llrender
Print done when done.
Diffstat (limited to 'indra/llrender')
-rw-r--r--indra/llrender/llfontgl.cpp1434
-rw-r--r--indra/llrender/llfontgl.h233
-rw-r--r--indra/llrender/llgldbg.cpp204
-rw-r--r--indra/llrender/llgldbg.h16
-rw-r--r--indra/llrender/llimagegl.cpp1205
-rw-r--r--indra/llrender/llimagegl.h185
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