// // BandwidthMeter.h // interface // // Created by Tobias Schwinger on 6/20/13. // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #include "BandwidthMeter.h" #include "InterfaceConfig.h" #include "Util.h" namespace { // .cpp-local int const AREA_WIDTH = -280; // Width of the area used. Aligned to the right when negative. int const AREA_HEIGHT = -40; // Height of the area used. Aligned to the bottom when negative. int const BORDER_DISTANCE_HORIZ = -10; // Distance to edge of screen (use negative value when width is negative). int const BORDER_DISTANCE_VERT = -15; // Distance to edge of screen (use negative value when height is negative). int SPACING_VERT_BARS = 2; // Vertical distance between input and output bar int SPACING_RIGHT_CAPTION_IN_OUT = 4; // IN/OUT <--> |######## : | int SPACING_LEFT_CAPTION_UNIT = 4; // |######## : | <--> UNIT int PADDING_HORIZ_VALUE = 2; // |<-->X.XX<:-># | unsigned const COLOR_TEXT = 0xedededff; // ^ ^ ^ ^ ^ ^ unsigned const COLOR_FRAME = 0xe0e0e0b0; // | | | unsigned const COLOR_INDICATOR = 0xc0c0c0b0; // | char const* CAPTION_IN = "In"; char const* CAPTION_OUT = "Out"; char const* CAPTION_UNIT = "Mbps"; double const UNIT_SCALE = 8000.0 / (1024.0 * 1024.0); // Bytes/ms -> Mbps int const INITIAL_SCALE_MAXIMUM_INDEX = 250; // / 9: exponent, % 9: mantissa - 2, 0 o--o 2 * 10^-10 int const MIN_METER_SCALE = 10; // 10Mbps int const NUMBER_OF_MARKERS = 10; } BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = { { "Audio" , "Kbps", 8000.0 / 1024.0, 0x33cc99ff }, { "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 }, { "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 } }; BandwidthMeter::BandwidthMeter() : _textRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false), _scaleMaxIndex(INITIAL_SCALE_MAXIMUM_INDEX) { _channels = static_cast( malloc(sizeof(_CHANNELS)) ); memcpy(_channels, _CHANNELS, sizeof(_CHANNELS)); } BandwidthMeter::~BandwidthMeter() { free(_channels); } BandwidthMeter::Stream::Stream(float msToAverage) : _value(0.0f), _msToAverage(msToAverage) { gettimeofday(& _prevTime, NULL); } void BandwidthMeter::Stream::updateValue(double amount) { // Determine elapsed time timeval now; gettimeofday(& now, NULL); double dt = diffclock(& _prevTime, & now); // Ignore this value when timer imprecision yields dt = 0 if (dt == 0.0) { return; } memcpy(& _prevTime, & now, sizeof(timeval)); // Compute approximate average _value = glm::mix(_value, amount / dt, glm::clamp(dt / _msToAverage, 0.0, 1.0)); } void BandwidthMeter::setColorRGBA(unsigned c) { glColor4ub(GLubyte( c >> 24), GLubyte((c >> 16) & 0xff), GLubyte((c >> 8) & 0xff), GLubyte( c & 0xff)); } void BandwidthMeter::renderBox(int x, int y, int w, int h) { glBegin(GL_QUADS); glVertex2i(x, y); glVertex2i(x + w, y); glVertex2i(x + w, y + h); glVertex2i(x, y + h); glEnd(); } void BandwidthMeter::renderVerticalLine(int x, int y, int h) { glBegin(GL_LINES); glVertex2i(x, y); glVertex2i(x, y + h); glEnd(); } inline int BandwidthMeter::centered(int subject, int object) { return (object - subject) / 2; } bool BandwidthMeter::isWithinArea(int x, int y, int screenWidth, int screenHeight) { int minX = BORDER_DISTANCE_HORIZ + (AREA_WIDTH >= 0 ? 0 : screenWidth + AREA_WIDTH); int minY = BORDER_DISTANCE_VERT + (AREA_HEIGHT >= 0 ? 0 : screenHeight + AREA_HEIGHT); return x >= minX && x < minX + glm::abs(AREA_WIDTH) && y >= minY && y < minY + glm::abs(AREA_HEIGHT); } void BandwidthMeter::render(int screenWidth, int screenHeight) { int x = BORDER_DISTANCE_HORIZ + (AREA_WIDTH >= 0 ? 0 : screenWidth + AREA_WIDTH); int y = BORDER_DISTANCE_VERT + (AREA_HEIGHT >= 0 ? 0 : screenHeight + AREA_HEIGHT); int w = glm::abs(AREA_WIDTH), h = glm::abs(AREA_HEIGHT); // Determine total float totalIn = 0.0f, totalOut = 0.0f; for (int i = 0; i < N_CHANNELS; ++i) { totalIn += inputStream(ChannelIndex(i)).getValue(); totalOut += outputStream(ChannelIndex(i)).getValue(); } totalIn *= UNIT_SCALE; totalOut *= UNIT_SCALE; float totalMax = glm::max(totalIn, totalOut); // Get font / caption metrics QFontMetrics const& fontMetrics = _textRenderer.metrics(); int fontDescent = fontMetrics.descent(); int labelWidthIn = fontMetrics.width(CAPTION_IN); int labelWidthOut = fontMetrics.width(CAPTION_OUT); int labelWidthInOut = glm::max(labelWidthIn, labelWidthOut); int labelHeight = fontMetrics.ascent() + fontDescent; int labelWidthUnit = fontMetrics.width(CAPTION_UNIT); int labelsWidth = labelWidthInOut + SPACING_RIGHT_CAPTION_IN_OUT + SPACING_LEFT_CAPTION_UNIT + labelWidthUnit; // Calculate coordinates and dimensions int barX = x + labelWidthInOut + SPACING_RIGHT_CAPTION_IN_OUT; int barWidth = w - labelsWidth; int barHeight = (h - SPACING_VERT_BARS) / 2; int textYcenteredLine = h - centered(labelHeight, h) - fontDescent; int textYupperLine = barHeight - centered(labelHeight, barHeight) - fontDescent; int textYlowerLine = h - centered(labelHeight, barHeight) - fontDescent; // Center of coordinate system -> upper left of bar glPushMatrix(); glTranslatef(float(barX), float(y), 0.0f); // Render captions setColorRGBA(COLOR_TEXT); _textRenderer.draw(barWidth + SPACING_LEFT_CAPTION_UNIT, textYcenteredLine, CAPTION_UNIT); _textRenderer.draw(-labelWidthIn - SPACING_RIGHT_CAPTION_IN_OUT, textYupperLine, CAPTION_IN); _textRenderer.draw(-labelWidthOut - SPACING_RIGHT_CAPTION_IN_OUT, textYlowerLine, CAPTION_OUT); // Render vertical lines for the frame setColorRGBA(COLOR_FRAME); renderVerticalLine(0, 0, h); renderVerticalLine(barWidth, 0, h); // Adjust scale int steps; double step, scaleMax; bool commit = false; do { steps = (_scaleMaxIndex % 9) + 2; step = pow(10.0, (_scaleMaxIndex / 9) - 10); scaleMax = step * steps; if (commit) { // printLog("Bandwidth meter scale: %d\n", _scaleMaxIndex); break; } if (totalMax < scaleMax * 0.5) { _scaleMaxIndex = glm::max(0, _scaleMaxIndex - 1); commit = true; } else if (totalMax > scaleMax) { _scaleMaxIndex += 1; commit = true; } } while (commit); step = scaleMax / NUMBER_OF_MARKERS; if (scaleMax < MIN_METER_SCALE) { scaleMax = MIN_METER_SCALE; } // Render scale indicators setColorRGBA(COLOR_INDICATOR); for (int j = NUMBER_OF_MARKERS; --j > 0;) { renderVerticalLine(int(barWidth * j / NUMBER_OF_MARKERS), 0, h); } // Render bars int xIn = 0, xOut = 0; for (int i = 0; i < N_CHANNELS; ++i) { ChannelIndex chIdx = ChannelIndex(i); int wIn = int(barWidth * inputStream(chIdx).getValue() * UNIT_SCALE / scaleMax); int wOut = int(barWidth * outputStream(chIdx).getValue() * UNIT_SCALE / scaleMax); setColorRGBA(channelInfo(chIdx).colorRGBA); if (wIn > 0) { renderBox(xIn, 0, wIn, barHeight); } xIn += wIn; if (wOut > 0) { renderBox(xOut, h - barHeight, wOut, barHeight); } xOut += wOut; } // Render numbers char fmtBuf[8]; setColorRGBA(COLOR_TEXT); sprintf(fmtBuf, "%0.1f", totalIn); _textRenderer.draw(glm::max(xIn - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE, PADDING_HORIZ_VALUE), textYupperLine, fmtBuf); sprintf(fmtBuf, "%0.1f", totalOut); _textRenderer.draw(glm::max(xOut - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE, PADDING_HORIZ_VALUE), textYlowerLine, fmtBuf); glPopMatrix(); // After rendering, indicate that no data has been sent/received since the last feed. // This way, the meters fall when not continuously fed. for (int i = 0; i < N_CHANNELS; ++i) { inputStream(ChannelIndex(i)).updateValue(0); outputStream(ChannelIndex(i)).updateValue(0); } }