diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 0b8d28f266..499b7b5eb5 100755 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -74,7 +74,7 @@ add_subdirectory(external/fervor/) include_directories(external/fervor/) # run qt moc on qt-enabled headers -qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h src/Webcam.h) +qt4_wrap_cpp(INTERFACE_SRCS src/Application.h src/AvatarVoxelSystem.h src/Webcam.h src/ui/BandwidthDialog.h) # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 47182634da..cf3ef70090 100755 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -70,6 +70,8 @@ using namespace std; static char STAR_FILE[] = "https://s3-us-west-1.amazonaws.com/highfidelity/stars.txt"; static char STAR_CACHE_FILE[] = "cachedStars.txt"; +static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored + const glm::vec3 START_LOCATION(4.f, 0.f, 5.f); // Where one's own agent begins in the world // (will be overwritten if avatar data file is found) @@ -160,6 +162,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), + _bandwidthDialog(NULL), _displayLevels(false), _frameCount(0), _fps(120.0f), @@ -478,11 +481,30 @@ void Application::resizeGL(int width, int height) { glLoadIdentity(); } -static void sendVoxelServerAddScene() { +void Application::broadcastToAgents(unsigned char* data, size_t bytes, const char type) { + + int n = AgentList::getInstance()->broadcastToAgents(data, bytes, &type, 1); + + BandwidthMeter::ChannelIndex channel; + switch (type) { + case AGENT_TYPE_AVATAR: + case AGENT_TYPE_AVATAR_MIXER: + channel = BandwidthMeter::AVATARS; + break; + case AGENT_TYPE_VOXEL_SERVER: + channel = BandwidthMeter::VOXELS; + break; + default: + return; + } + getInstance()->_bandwidthMeter.outputStream(channel).updateValue(n * bytes); +} + +void Application::sendVoxelServerAddScene() { char message[100]; sprintf(message,"%c%s",'Z',"add scene"); int messageSize = strlen(message) + 1; - AgentList::getInstance()->broadcastToAgents((unsigned char*)message, messageSize, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents((unsigned char*)message, messageSize, AGENT_TYPE_VOXEL_SERVER); } void Application::keyPressEvent(QKeyEvent* event) { @@ -800,6 +822,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { _mouseX = event->x(); _mouseY = event->y(); _mousePressed = false; + checkBandwidthMeterClick(); } } } @@ -940,7 +963,7 @@ void Application::terminate() { } } -static void sendAvatarVoxelURLMessage(const QUrl& url) { +void Application::sendAvatarVoxelURLMessage(const QUrl& url) { uint16_t ownerID = AgentList::getInstance()->getOwnerID(); if (ownerID == UNKNOWN_AGENT_ID) { @@ -951,10 +974,10 @@ static void sendAvatarVoxelURLMessage(const QUrl& url) { message.append((const char*)&ownerID, sizeof(ownerID)); message.append(url.toEncoded()); - AgentList::getInstance()->broadcastToAgents((unsigned char*)message.data(), message.size(), &AGENT_TYPE_AVATAR_MIXER, 1); + broadcastToAgents((unsigned char*)message.data(), message.size(), AGENT_TYPE_AVATAR_MIXER); } -static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { +void Application::processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes) { // skip the header packetData++; dataBytes--; @@ -979,6 +1002,37 @@ static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataB QMetaObject::invokeMethod(avatar->getVoxels(), "setVoxelURL", Q_ARG(QUrl, url)); } +void Application::checkBandwidthMeterClick() { + // ... to be called upon button release + + if (_bandwidthDisplayOn->isChecked() && + glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY))) <= BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH && + _bandwidthMeter.isWithinArea(_mouseX, _mouseY, _glWidget->width(), _glWidget->height())) { + + // The bandwidth meter is visible, the click didn't get dragged too far and + // we actually hit the bandwidth meter + bandwidthDetails(); + } +} + +void Application::bandwidthDetails() { + + if (! _bandwidthDialog) { + _bandwidthDialog = new BandwidthDialog(_glWidget, getBandwidthMeter()); + connect(_bandwidthDialog, SIGNAL(closed()), SLOT(bandwidthDetailsClosed())); + + _bandwidthDialog->show(); + } + _bandwidthDialog->raise(); +} + +void Application::bandwidthDetailsClosed() { + + QDialog* dlg = _bandwidthDialog; + _bandwidthDialog = NULL; + delete dlg; +} + void Application::editPreferences() { QDialog dialog(_glWidget); dialog.setWindowTitle("Interface Preferences"); @@ -1139,12 +1193,12 @@ void Application::updateVoxelModeActions() { } } -static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { +void Application::sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail) { unsigned char* bufferOut; int sizeOut; if (createVoxelEditMessage(header, 0, 1, &detail, bufferOut, sizeOut)){ - AgentList::getInstance()->broadcastToAgents(bufferOut, sizeOut, &AGENT_TYPE_VOXEL_SERVER, 1); + Application::broadcastToAgents(bufferOut, sizeOut, AGENT_TYPE_VOXEL_SERVER); delete[] bufferOut; } } @@ -1219,7 +1273,7 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { // if we have room don't have room in the buffer, then send the previously generated message first if (args->bufferInUse + codeAndColorLength > MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE) { - AgentList::getInstance()->broadcastToAgents(args->messageBuffer, args->bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args->messageBuffer, args->bufferInUse, AGENT_TYPE_VOXEL_SERVER); args->bufferInUse = sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int); // reset } @@ -1283,7 +1337,7 @@ void Application::importVoxels() { // If we have voxels left in the packet, then send the packet if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { - AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args.messageBuffer, args.bufferInUse, AGENT_TYPE_VOXEL_SERVER); } if (calculatedOctCode) { @@ -1335,7 +1389,7 @@ void Application::pasteVoxels() { // If we have voxels left in the packet, then send the packet if (args.bufferInUse > (sizeof(PACKET_HEADER_SET_VOXEL_DESTRUCTIVE) + sizeof(unsigned short int))) { - AgentList::getInstance()->broadcastToAgents(args.messageBuffer, args.bufferInUse, &AGENT_TYPE_VOXEL_SERVER, 1); + broadcastToAgents(args.messageBuffer, args.bufferInUse, AGENT_TYPE_VOXEL_SERVER); } if (calculatedOctCode) { @@ -1412,7 +1466,11 @@ void Application::initMenu() { (_logOn = toolsMenu->addAction("Log"))->setCheckable(true); _logOn->setChecked(false); _logOn->setShortcut(Qt::CTRL | Qt::Key_L); - + (_bandwidthDisplayOn = toolsMenu->addAction("Bandwidth Display"))->setCheckable(true); + _bandwidthDisplayOn->setChecked(true); + toolsMenu->addAction("Bandwidth Details", this, SLOT(bandwidthDetails())); + + QMenu* voxelMenu = menuBar->addMenu("Voxels"); _voxelModeActions = new QActionGroup(this); _voxelModeActions->setExclusive(false); // exclusivity implies one is always checked @@ -1792,7 +1850,12 @@ void Application::update(float deltaTime) { } } } - + + // Update bandwidth dialog, if any + if (_bandwidthDialog) { + _bandwidthDialog->update(); + } + // Update audio stats for procedural sounds #ifndef _WIN32 _audio.setLastAcceleration(_myAvatar.getThrust()); @@ -1875,8 +1938,8 @@ void Application::updateAvatar(float deltaTime) { endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite); - const char broadcastReceivers[2] = {AGENT_TYPE_VOXEL_SERVER, AGENT_TYPE_AVATAR_MIXER}; - AgentList::getInstance()->broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, broadcastReceivers, sizeof(broadcastReceivers)); + broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, AGENT_TYPE_VOXEL_SERVER); + broadcastToAgents(broadcastString, endOfBroadcastStringWrite - broadcastString, AGENT_TYPE_AVATAR_MIXER); // once in a while, send my voxel url const float AVATAR_VOXEL_URL_SEND_INTERVAL = 1.0f; // seconds @@ -2264,6 +2327,9 @@ void Application::displayOverlay() { glPointSize(1.0f); if (_renderStatsOn->isChecked()) { displayStats(); } + + if (_bandwidthDisplayOn->isChecked()) { _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); } + if (_logOn->isChecked()) { LogDisplay::instance.render(_glWidget->width(), _glWidget->height()); } // Show chat entry field @@ -2779,6 +2845,7 @@ void* Application::networkReceive(void* args) { AgentList::getInstance()->processBulkAgentData(&senderAddress, app->_incomingPacket, bytesReceived); + getInstance()->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived); break; case PACKET_HEADER_AVATAR_VOXEL_URL: processAvatarVoxelURLMessage(app->_incomingPacket, bytesReceived); diff --git a/interface/src/Application.h b/interface/src/Application.h index 73f97a35f0..e2b0cd21f4 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -21,6 +21,9 @@ #include +#include "BandwidthMeter.h" +#include "ui/BandwidthDialog.h" + #ifndef _WIN32 #include "Audio.h" #endif @@ -32,6 +35,7 @@ #include "Stars.h" #include "ViewFrustum.h" #include "VoxelSystem.h" +#include "PacketHeaders.h" #include "Webcam.h" #include "renderer/GeometryCache.h" #include "ui/ChatEntry.h" @@ -84,6 +88,7 @@ public: Environment* getEnvironment() { return &_environment; } SerialInterface* getSerialHeadSensor() { return &_serialHeadSensor; } Webcam* getWebcam() { return &_webcam; } + BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } bool shouldEchoAudio() { return _echoAudioMode->isChecked(); } bool shouldLowPassFilter() { return _shouldLowPassFilter->isChecked(); } @@ -97,8 +102,10 @@ private slots: void timer(); void idle(); void terminate(); - + + void bandwidthDetails(); void editPreferences(); + void bandwidthDetailsClosed(); void pair(); @@ -145,7 +152,12 @@ private slots: void runTests(); private: + static void broadcastToAgents(unsigned char* data, size_t bytes, const char type); + static void sendVoxelServerAddScene(); static bool sendVoxelsOperation(VoxelNode* node, void* extraData); + static void sendAvatarVoxelURLMessage(const QUrl& url); + static void processAvatarVoxelURLMessage(unsigned char *packetData, size_t dataBytes); + static void sendVoxelEditMessage(PACKET_HEADER header, VoxelDetail& detail); void initMenu(); void updateFrustumRenderModeAction(); @@ -161,7 +173,9 @@ private: void displayOverlay(); void displayStats(); void renderViewFrustum(ViewFrustum& viewFrustum); - + + void checkBandwidthMeterClick(); + void setupPaintingVoxel(); void shiftPaintingColor(); void maybeEditVoxelUnderCursor(); @@ -212,6 +226,7 @@ private: QAction* _manualFirstPerson; // Whether to force first-person mode QAction* _manualThirdPerson; // Whether to force third-person mode QAction* _logOn; // Whether to show on-screen log + QAction* _bandwidthDisplayOn; // Whether to show on-screen bandwidth bars QActionGroup* _voxelModeActions; // The group of voxel edit mode actions QAction* _addVoxelMode; // Whether add voxel mode is enabled QAction* _deleteVoxelMode; // Whether delete voxel mode is enabled @@ -226,6 +241,9 @@ private: QAction* _frustumRenderModeAction; QAction* _settingsAutosave; // Whether settings are saved automatically + BandwidthMeter _bandwidthMeter; + BandwidthDialog* _bandwidthDialog; + SerialInterface _serialHeadSensor; QNetworkAccessManager* _networkAccessManager; QSettings* _settings; @@ -240,7 +258,7 @@ private: timeval _timerStart, _timerEnd; timeval _lastTimeIdle; bool _justStarted; - + Stars _stars; VoxelSystem _voxels; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index d0a49f4fe5..cfc4f6de19 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -120,10 +120,12 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o // copy the audio data to the last BUFFER_LENGTH_BYTES bytes of the data packet memcpy(currentPacketPtr, inputLeft, BUFFER_LENGTH_BYTES_PER_CHANNEL); - agentList->getAgentSocket()->send(audioMixer->getActiveSocket(), dataPacket, BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); + + interface->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO) + .updateValue(BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); } } @@ -335,13 +337,20 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples) : // Manually initialize the portaudio stream to ask for minimum latency PaStreamParameters inputParameters, outputParameters; - inputParameters.device = Pa_GetDefaultInputDevice(); + inputParameters.device = Pa_GetDefaultInputDevice(); + outputParameters.device = Pa_GetDefaultOutputDevice(); + + if (inputParameters.device == -1 || outputParameters.device == -1) { + printLog("Audio: Missing device.\n"); + outputPortAudioError(Pa_Terminate()); + return; + } + inputParameters.channelCount = 2; // Stereo input inputParameters.sampleFormat = (paInt16 | paNonInterleaved); inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; inputParameters.hostApiSpecificStreamInfo = NULL; - outputParameters.device = Pa_GetDefaultOutputDevice(); outputParameters.channelCount = 2; // Stereo output outputParameters.sampleFormat = (paInt16 | paNonInterleaved); outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; @@ -438,7 +447,10 @@ void Audio::addReceivedAudioToBuffer(unsigned char* receivedData, int receivedBy //printf("Got audio packet %d\n", _packetsReceivedThisPlayback); _ringBuffer.parseData((unsigned char*) receivedData, PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); - + + Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO) + .updateValue(PACKET_LENGTH_BYTES + sizeof(PACKET_HEADER)); + _lastReceiveTime = currentReceiveTime; } diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/BandwidthMeter.cpp new file mode 100644 index 0000000000..ba89864807 --- /dev/null +++ b/interface/src/BandwidthMeter.cpp @@ -0,0 +1,230 @@ +// +// 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 "Log.h" +#include "Util.h" + +namespace { // .cpp-local + + int const AREA_WIDTH = -400; // 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 = -20; // Distance to edge of screen (use negative value when width is negative). + int const BORDER_DISTANCE_VERT = 40; // 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 = 0xe0e0e0e0; // ^ ^ ^ ^ ^ ^ + 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 +} + +BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = { + { "Audio" , "Kbps", 8000.0 / 1024.0, 0x40ff40d0 }, + { "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 }, + { "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 } +}; + +BandwidthMeter::BandwidthMeter() : + _textRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT), + _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); + 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); + + // Render scale indicators + setColorRGBA(COLOR_INDICATOR); + for (int j = int((scaleMax + step - 0.000001) / step); --j > 0;) { + renderVerticalLine(int(barWidth * j * step / scaleMax), 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.2f", totalIn); + _textRenderer.draw(glm::max(xIn - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE, + PADDING_HORIZ_VALUE), + textYupperLine, fmtBuf); + sprintf(fmtBuf, "%0.2f", totalOut); + _textRenderer.draw(glm::max(xOut - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE, + PADDING_HORIZ_VALUE), + textYlowerLine, fmtBuf); + + glPopMatrix(); +} + + diff --git a/interface/src/BandwidthMeter.h b/interface/src/BandwidthMeter.h new file mode 100644 index 0000000000..0221306ed7 --- /dev/null +++ b/interface/src/BandwidthMeter.h @@ -0,0 +1,83 @@ +// +// BandwidthMeter.h +// interface +// +// Created by Tobias Schwinger on 6/20/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__BandwidthMeter__ +#define __interface__BandwidthMeter__ + +#include + +#include "ui/TextRenderer.h" + + +class BandwidthMeter { + +public: + + BandwidthMeter(); + ~BandwidthMeter(); + + void render(int screenWidth, int screenHeight); + bool isWithinArea(int x, int y, int screenWidth, int screenHeight); + + // Number of channels / streams. + static size_t const N_CHANNELS = 3; + static size_t const N_STREAMS = N_CHANNELS * 2; + + // Channel usage. + enum ChannelIndex { AUDIO, AVATARS, VOXELS }; + + // Meta information held for a communication channel (bidirectional). + struct ChannelInfo { + + char const* const caption; + char const* unitCaption; + double unitScale; + unsigned colorRGBA; + }; + + // Representation of a data stream (unidirectional; input or output). + class Stream { + + public: + + Stream(float msToAverage = 3000.0f); + void updateValue(double amount); + double getValue() const { return _value; } + + private: + double _value; // Current value. + double _msToAverage; // Milliseconds to average. + timeval _prevTime; // Time of last feed. + }; + + // Data model accessors + Stream& inputStream(ChannelIndex i) { return _streams[i * 2]; } + Stream const& inputStream(ChannelIndex i) const { return _streams[i * 2]; } + Stream& outputStream(ChannelIndex i) { return _streams[i * 2 + 1]; } + Stream const& outputStream(ChannelIndex i) const { return _streams[i * 2 + 1]; } + ChannelInfo& channelInfo(ChannelIndex i) { return _channels[i]; } + ChannelInfo const& channelInfo(ChannelIndex i) const { return _channels[i]; } + +private: + static void setColorRGBA(unsigned c); + static void renderBox(int x, int y, int w, int h); + static void renderVerticalLine(int x, int y, int h); + + static inline int centered(int subject, int object); + + + static ChannelInfo _CHANNELS[]; + + TextRenderer _textRenderer; + ChannelInfo* _channels; + Stream _streams[N_STREAMS]; + int _scaleMaxIndex; +}; + +#endif /* defined(__interface__BandwidthMeter__) */ + diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 69edf376ee..55d7e76d62 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -160,7 +160,9 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) { setupNewVoxelsForDrawing(); pthread_mutex_unlock(&_treeLock); - + + Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::VOXELS).updateValue(numBytes); + return numBytes; } diff --git a/interface/src/ui/BandwidthDialog.cpp b/interface/src/ui/BandwidthDialog.cpp new file mode 100644 index 0000000000..1e0e2e616e --- /dev/null +++ b/interface/src/ui/BandwidthDialog.cpp @@ -0,0 +1,72 @@ + +#include "ui/BandwidthDialog.h" + +#include +#include + +#include +#include + +#include "Log.h" + +BandwidthDialog::BandwidthDialog(QWidget* parent, BandwidthMeter* model) : + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), + _model(model) { + + char strBuf[64]; + + this->setWindowTitle("Bandwidth Details"); + + // Create layouter + QFormLayout* form = new QFormLayout(); + this->QDialog::setLayout(form); + + // Setup labels + for (int i = 0; i < BandwidthMeter::N_STREAMS; ++i) { + bool input = i % 2 == 0; + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(BandwidthMeter::ChannelIndex(i / 2)); + QLabel* label = _labels[i] = new QLabel(); + label->setAlignment(Qt::AlignRight); + + // Set foreground color to 62.5% brightness of the meter (otherwise will be hard to read on the bright background) + QPalette palette = label->palette(); + unsigned rgb = ch.colorRGBA >> 8; + rgb = ((rgb & 0xfefefeu) >> 1) + ((rgb & 0xf8f8f8) >> 3); + palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb)); + label->setPalette(palette); + + snprintf(strBuf, sizeof(strBuf), " %s %s Bandwidth:", input ? "Input" : "Output", ch.caption); + form->addRow(strBuf, label); + } +} + +void BandwidthDialog::paintEvent(QPaintEvent* event) { + + // Update labels + char strBuf[64]; + for (int i = 0; i < BandwidthMeter::N_STREAMS; ++i) { + BandwidthMeter::ChannelIndex chIdx = BandwidthMeter::ChannelIndex(i / 2); + bool input = i % 2 == 0; + BandwidthMeter::ChannelInfo& ch = _model->channelInfo(chIdx); + BandwidthMeter::Stream& s = input ? _model->inputStream(chIdx) : _model->outputStream(chIdx); + QLabel* label = _labels[i]; + snprintf(strBuf, sizeof(strBuf), "%0.2f %s", s.getValue() * ch.unitScale, ch.unitCaption); + label->setText(strBuf); + } + + this->QDialog::paintEvent(event); + this->setFixedSize(this->width(), this->height()); +} + +void BandwidthDialog::reject() { + + // Just regularly close upon ESC + this->QDialog::close(); +} + +void BandwidthDialog::closeEvent(QCloseEvent* event) { + + this->QDialog::closeEvent(event); + emit closed(); +} + diff --git a/interface/src/ui/BandwidthDialog.h b/interface/src/ui/BandwidthDialog.h new file mode 100644 index 0000000000..636e91dce9 --- /dev/null +++ b/interface/src/ui/BandwidthDialog.h @@ -0,0 +1,47 @@ +// +// BandwidthDialog.h +// interface +// +// Created by Tobias Schwinger on 6/21/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__BandwidthDialog__ +#define __hifi__BandwidthDialog__ + +#include +#include + +#include "BandwidthMeter.h" + + +class BandwidthDialog : public QDialog { + Q_OBJECT +public: + + // Sets up the UI based on the configuration of the BandwidthMeter + BandwidthDialog(QWidget* parent, BandwidthMeter* model); + +signals: + + void closed(); + +public slots: + + void reject(); + +protected: + + // State <- data model held by BandwidthMeter + void paintEvent(QPaintEvent*); + + // Emits a 'closed' signal when this dialog is closed. + void closeEvent(QCloseEvent*); + +private: + BandwidthMeter* _model; + QLabel* _labels[BandwidthMeter::N_STREAMS]; +}; + +#endif /* defined(__interface__BandwidthDialog__) */ + diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index efe52bdf57..29ff920317 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -11,6 +11,7 @@ #include #include +#include #include diff --git a/libraries/shared/src/AgentList.cpp b/libraries/shared/src/AgentList.cpp index 237b367252..2db1e68318 100644 --- a/libraries/shared/src/AgentList.cpp +++ b/libraries/shared/src/AgentList.cpp @@ -350,14 +350,17 @@ void AgentList::addAgentToList(Agent* newAgent) { Agent::printLog(*newAgent); } -void AgentList::broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes) { +unsigned AgentList::broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes) { + unsigned n = 0; for(AgentList::iterator agent = begin(); agent != end(); agent++) { // only send to the AgentTypes we are asked to send to. if (agent->getActiveSocket() != NULL && memchr(agentTypes, agent->getType(), numAgentTypes)) { // we know which socket is good for this agent, send there _agentSocket.send(agent->getActiveSocket(), broadcastData, dataBytes); + ++n; } } + return n; } void AgentList::handlePingReply(sockaddr *agentAddress) { diff --git a/libraries/shared/src/AgentList.h b/libraries/shared/src/AgentList.h index 8a283009fe..c817adb6f3 100644 --- a/libraries/shared/src/AgentList.h +++ b/libraries/shared/src/AgentList.h @@ -82,7 +82,7 @@ public: int updateAgentWithData(sockaddr *senderAddress, unsigned char *packetData, size_t dataBytes); int updateAgentWithData(Agent *agent, unsigned char *packetData, int dataBytes); - void broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes); + unsigned broadcastToAgents(unsigned char *broadcastData, size_t dataBytes, const char* agentTypes, int numAgentTypes); Agent* soloAgentOfType(char agentType);