From 4bca28abb299be4d5455557507c74b7585d7db23 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 28 Feb 2014 01:57:24 -0800 Subject: [PATCH] fixed text renderer and frame rate display --- SvoViewer/src/TextRenderer.cpp | 190 +++++++++++++++++++++++++++++++++ SvoViewer/src/TextRenderer.h | 114 ++++++++++++++++++++ SvoViewer/src/svoviewer.cpp | 48 ++++++--- 3 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 SvoViewer/src/TextRenderer.cpp create mode 100644 SvoViewer/src/TextRenderer.h diff --git a/SvoViewer/src/TextRenderer.cpp b/SvoViewer/src/TextRenderer.cpp new file mode 100644 index 0000000000..3b2a308ba8 --- /dev/null +++ b/SvoViewer/src/TextRenderer.cpp @@ -0,0 +1,190 @@ +// +// TextRenderer.cpp +// interface +// +// Created by Andrzej Kapolka on 4/24/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. + +#include +#include +#include +#include +#include + +#include "SvoViewerConfig.h" +#include "TextRenderer.h" + +// the width/height of the cached glyph textures +const int IMAGE_SIZE = 256; + +Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) : + _textureID(textureID), _location(location), _bounds(bounds), _width(width) { +} + +TextRenderer::TextRenderer(const char* family, int pointSize, int weight, + bool italic, EffectType effectType, int effectThickness) + : _font(family, pointSize, weight, italic), _metrics(_font), _effectType(effectType), + _effectThickness(effectThickness), _x(IMAGE_SIZE), _y(IMAGE_SIZE), _rowHeight(0) { + _font.setKerning(false); +} + +TextRenderer::~TextRenderer() { + glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData()); +} + +int TextRenderer::calculateHeight(const char* str) { + int maxHeight = 0; + for (const char* ch = str; *ch != 0; ch++) { + const Glyph& glyph = getGlyph(*ch); + if (glyph.textureID() == 0) { + continue; + } + + if (glyph.bounds().height() > maxHeight) { + maxHeight = glyph.bounds().height(); + } + } + return maxHeight; +} + +int TextRenderer::draw(int x, int y, const char* str) { + glEnable(GL_TEXTURE_2D); + + int maxHeight = 0; + for (const char* ch = str; *ch != 0; ch++) { + const Glyph& glyph = getGlyph(*ch); + if (glyph.textureID() == 0) { + x += glyph.width(); + continue; + } + + if (glyph.bounds().height() > maxHeight) { + maxHeight = glyph.bounds().height(); + } + + glBindTexture(GL_TEXTURE_2D, glyph.textureID()); + + int left = x + glyph.bounds().x(); + int right = x + glyph.bounds().x() + glyph.bounds().width(); + int bottom = y + glyph.bounds().y(); + int top = y + glyph.bounds().y() + glyph.bounds().height(); + + float scale = 1.0 / IMAGE_SIZE; + float ls = glyph.location().x() * scale; + float rs = (glyph.location().x() + glyph.bounds().width()) * scale; + float bt = glyph.location().y() * scale; + float tt = (glyph.location().y() + glyph.bounds().height()) * scale; + + glBegin(GL_QUADS); + glTexCoord2f(ls, bt); + glVertex2f(left, bottom); + glTexCoord2f(rs, bt); + glVertex2f(right, bottom); + glTexCoord2f(rs, tt); + glVertex2f(right, top); + glTexCoord2f(ls, tt); + glVertex2f(left, top); + glEnd(); + + x += glyph.width(); + } + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + return maxHeight; +} + +int TextRenderer::computeWidth(char ch) +{ + return getGlyph(ch).width(); +} + +int TextRenderer::computeWidth(const char* str) +{ + int width = 0; + for (const char* ch = str; *ch != 0; ch++) { + width += computeWidth(*ch); + } + return width; +} + +const Glyph& TextRenderer::getGlyph(char c) { + Glyph& glyph = _glyphs[c]; + if (glyph.isValid()) { + return glyph; + } + // we use 'J' as a representative size for the solid block character + QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c); + QRect bounds = _metrics.boundingRect(ch); + if (bounds.isEmpty()) { + glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch)); + return glyph; + } + // grow the bounds to account for effect, if any + if (_effectType == SHADOW_EFFECT) { + bounds.adjust(-_effectThickness, 0, 0, _effectThickness); + + } else if (_effectType == OUTLINE_EFFECT) { + bounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness); + } + + // grow the bounds to account for antialiasing + bounds.adjust(-1, -1, 1, 1); + + if (_x + bounds.width() > IMAGE_SIZE) { + // we can't fit it on the current row; move to next + _y += _rowHeight; + _x = _rowHeight = 0; + } + if (_y + bounds.height() > IMAGE_SIZE) { + // can't fit it on current texture; make a new one + glGenTextures(1, &_currentTextureID); + _x = _y = _rowHeight = 0; + + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + _allTextureIDs.append(_currentTextureID); + + } else { + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + } + // render the glyph into an image and copy it into the texture + QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32); + if (c == SOLID_BLOCK_CHAR) { + image.fill(QColor(255, 255, 255)); + + } else { + image.fill(0); + QPainter painter(&image); + painter.setFont(_font); + if (_effectType == SHADOW_EFFECT) { + for (int i = 0; i < _effectThickness; i++) { + painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch); + } + } else if (_effectType == OUTLINE_EFFECT) { + QPainterPath path; + QFont font = _font; + font.setStyleStrategy(QFont::ForceOutline); + path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch); + QPen pen; + pen.setWidth(_effectThickness); + pen.setJoinStyle(Qt::RoundJoin); + pen.setCapStyle(Qt::RoundCap); + painter.setPen(pen); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawPath(path); + } + painter.setPen(QColor(255, 255, 255)); + painter.drawText(-bounds.x(), -bounds.y(), ch); + } + glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); + + glyph = Glyph(_currentTextureID, QPoint(_x, _y), bounds, _metrics.width(ch)); + _x += bounds.width(); + _rowHeight = qMax(_rowHeight, bounds.height()); + + glBindTexture(GL_TEXTURE_2D, 0); + return glyph; +} diff --git a/SvoViewer/src/TextRenderer.h b/SvoViewer/src/TextRenderer.h new file mode 100644 index 0000000000..b5348cd665 --- /dev/null +++ b/SvoViewer/src/TextRenderer.h @@ -0,0 +1,114 @@ +// +// TextRenderer.h +// interface +// +// Created by Andrzej Kapolka on 4/26/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__TextRenderer__ +#define __interface__TextRenderer__ + +#include +#include +#include +#include +#include + +#include "SvoViewerConfig.h" + +// a special "character" that renders as a solid block +const char SOLID_BLOCK_CHAR = 127; + +// the standard sans serif font family +#define SANS_FONT_FAMILY "Helvetica" + +// the standard mono font family +#define MONO_FONT_FAMILY "Courier" + +// the Inconsolata font family +#define INCONSOLATA_FONT_FAMILY "Inconsolata" + + +class Glyph; + +class TextRenderer { +public: + + enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT }; + + TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false, + EffectType effect = NO_EFFECT, int effectThickness = 1); + ~TextRenderer(); + + const QFontMetrics& metrics() const { return _metrics; } + + // returns the height of the tallest character + int calculateHeight(const char* str); + + // also returns the height of the tallest character + int draw(int x, int y, const char* str); + + int computeWidth(char ch); + int computeWidth(const char* str); + +private: + + const Glyph& getGlyph (char c); + + // the font to render + QFont _font; + + // the font metrics + QFontMetrics _metrics; + + // the type of effect to apply + EffectType _effectType; + + // the thickness of the effect + int _effectThickness; + + // maps characters to cached glyph info + QHash _glyphs; + + // the id of the glyph texture to which we're currently writing + GLuint _currentTextureID; + + // the position within the current glyph texture + int _x, _y; + + // the height of the current row of characters + int _rowHeight; + + // the list of all texture ids for which we're responsible + QVector _allTextureIDs; +}; + +class Glyph { +public: + + Glyph(int textureID = 0, const QPoint& location = QPoint(), const QRect& bounds = QRect(), int width = 0); + + GLuint textureID() const { return _textureID; } + const QPoint& location () const { return _location; } + const QRect& bounds() const { return _bounds; } + int width () const { return _width; } + + bool isValid() { return _width != 0; } + +private: + + // the id of the OpenGL texture containing the glyph + GLuint _textureID; + + // the location of the character within the texture + QPoint _location; + + // the bounds of the character + QRect _bounds; + + // the width of the character (distance to next, as opposed to bounds width) + int _width; +}; + +#endif /* defined(__interface__TextRenderer__) */ diff --git a/SvoViewer/src/svoviewer.cpp b/SvoViewer/src/svoviewer.cpp index 4cad85f424..310c1f2f00 100755 --- a/SvoViewer/src/svoviewer.cpp +++ b/SvoViewer/src/svoviewer.cpp @@ -7,6 +7,7 @@ #include "svoviewer.h" #include "GLCanvas.h" +#include "TextRenderer.h" #include #include @@ -211,7 +212,9 @@ void SvoViewer::init() { void SvoViewer::initializeGL() { int argc = 0; + #ifdef WIN32 glutInit(&argc, 0); + #endif init(); #ifdef WIN32 GLenum err = glewInit(); @@ -324,11 +327,12 @@ void SvoViewer::paintGL() // Update every x seconds for more stability quint64 tc = usecTimestampNow(); quint64 interval = tc - _lastTimeFpsUpdated; -#define FPS_UPDATE_TIME_INTERVAL 2 - if (interval > 1000 * FPS_UPDATE_TIME_INTERVAL) - { - int numFrames = _frameCount - _lastTrackedFrameCount; - _fps = (float)numFrames / (float)(FPS_UPDATE_TIME_INTERVAL); + const quint64 USECS_PER_SECOND = 1000 * 1000; + const int FPS_UPDATE_TIME_INTERVAL = 2; + if (interval > (USECS_PER_SECOND * FPS_UPDATE_TIME_INTERVAL)) { + int numFrames = _frameCount - _lastTrackedFrameCount; + float intervalSeconds = (float)((float)interval/(float)USECS_PER_SECOND); + _fps = (float)numFrames / intervalSeconds; _lastTrackedFrameCount = _frameCount; _lastTimeFpsUpdated = tc; } @@ -337,6 +341,27 @@ void SvoViewer::paintGL() PrintToScreen(10, 30, "Drawing %d of %d (%% %f) total elements", _numElemsDrawn, _totalPossibleElems, ((float)_numElemsDrawn / (float)_totalPossibleElems) * 100.0); } +void drawtext(int x, int y, float scale, float rotate, float thick, int mono, + char const* string, float r, float g, float b) { + // + // Draws text on screen as stroked so it can be resized + // + glPushMatrix(); + glTranslatef(static_cast(x), static_cast(y), 0.0f); + glColor3f(r,g,b); + glRotated(rotate,0,0,1); + // glLineWidth(thick); + glScalef(scale / 0.10, scale / 0.10, 1.0); + + TextRenderer textRenderer(SANS_FONT_FAMILY, 11, 50); + textRenderer.draw(0, 0, string); + + //textRenderer(mono)->draw(0, 0, string); + + glPopMatrix(); +} + + #define TEMP_STRING_BUFFER_MAX 1024 #define SHADOW_OFFSET 2 void SvoViewer::PrintToScreen(const int width, const int height, const char* szFormat, ...) @@ -352,28 +377,23 @@ void SvoViewer::PrintToScreen(const int width, const int height, const char* szF memset(szUBuff, 0, sizeof(szUBuff)); int len = strlen(szBuff); for (int i = 0; i < len; i++) szUBuff[i] = (unsigned char)szBuff[i]; + qDebug() << szBuff; + glEnable(GL_DEPTH_TEST); glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity(); - glOrtho(0, _width, 0, _height, 0, 1); + gluOrtho2D(0, _width, _height, 0); glDisable(GL_LIGHTING); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity(); - glColor3f(.8f, .8f, .8f); // Matt:: reverse ordering once depth enabled. - glRasterPos2i(width, height); - //glutBitmapString(GLUT_BITMAP_HELVETICA_18, szUBuff); + drawtext(width, height, 0.10f, 0, 1, 2, szBuff, 1,1,1); - glColor3f(0.0f, 0.0f, 0.0f); - glRasterPos2i(width - SHADOW_OFFSET, height - SHADOW_OFFSET ); - //glutBitmapString(GLUT_BITMAP_HELVETICA_18, szUBuff); - - //glEnable(GL_LIGHTING); glPopMatrix(); glMatrixMode( GL_PROJECTION ); glPopMatrix();