From 659fa2387c0b76dd2e9561fbcccb7867715dc90f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 16 Jun 2015 21:00:13 -0700 Subject: [PATCH] Working on overlays / stats --- interface/resources/qml/Stats.qml | 147 +++++ interface/resources/qml/Tooltip.qml | 29 + interface/src/Application.cpp | 2 + interface/src/ui/ApplicationCompositor.cpp | 165 +---- interface/src/ui/ApplicationCompositor.h | 1 + interface/src/ui/ApplicationOverlay.cpp | 103 ++- interface/src/ui/ApplicationOverlay.h | 14 +- interface/src/ui/Stats.cpp | 620 +++++++----------- interface/src/ui/Stats.h | 128 +++- .../render-utils/src/OffscreenQmlSurface.cpp | 4 +- libraries/ui/src/Tooltip.cpp | 51 ++ libraries/ui/src/Tooltip.h | 45 ++ 12 files changed, 671 insertions(+), 638 deletions(-) create mode 100644 interface/resources/qml/Stats.qml create mode 100644 interface/resources/qml/Tooltip.qml create mode 100644 libraries/ui/src/Tooltip.cpp create mode 100644 libraries/ui/src/Tooltip.h diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml new file mode 100644 index 0000000000..6173b8517d --- /dev/null +++ b/interface/resources/qml/Stats.qml @@ -0,0 +1,147 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Hifi.Stats { + id: root + objectName: "Stats" + implicitHeight: row.height + implicitWidth: row.width + readonly property int sTATS_GENERAL_MIN_WIDTH: 165 + readonly property int sTATS_PING_MIN_WIDTH: 190 + readonly property int sTATS_GEO_MIN_WIDTH: 240 + readonly property int sTATS_OCTREE_MIN_WIDTH: 410 + + onParentChanged: { + root.x = parent.width - root.width; + } + + Row { + z: 100 + id: row + spacing: 8 + Rectangle { + width: generalCol.width + 8; + height: generalCol.height + 8; + color: "#99333333"; + + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + } + + Column { + id: generalCol + spacing: 4; x: 4; y: 4; + width: sTATS_GENERAL_MIN_WIDTH + Text { color: "white"; text: "Servers: " + root.serverCount } + Text { color: "white"; text: "Avatars: " + root.avatarCount } + Text { color: "white"; text: "Framerate: " + root.framerate } + Text { color: "white"; text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount } + Text { color: "white"; text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) } + } + } + + Rectangle { + width: pingCol.width + 8 + height: pingCol.height + 8 + color: "#99333333" + visible: root.audioPing != -2 + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + } + Column { + id: pingCol + spacing: 4; x: 4; y: 4; + width: sTATS_PING_MIN_WIDTH + Text { color: "white"; text: "Audio ping: " + root.audioPing } + Text { color: "white"; text: "Avatar ping: " + root.avatarPing } + Text { color: "white"; text: "Entities avg ping: " + root.entitiesPing } + Text { color: "white"; text: "Voxel max ping: " + 0; visible: root.expanded; } + } + } + Rectangle { + width: geoCol.width + height: geoCol.height + color: "#99333333" + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + } + Column { + spacing: 4 + id: geoCol + width: sTATS_GEO_MIN_WIDTH + Text { + color: "white"; + text: "Position: " + root.position.x.toFixed(1) + ", " + + root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) + } + Text { + color: "white"; + text: "Velocity: " + root.velocity.toFixed(1) + } + Text { + color: "white"; + text: "Yaw: " + root.yaw.toFixed(1) + } + Text { + color: "white"; + visible: root.expanded; + text: "Avatar Mixer: " + root.avatarMixerKbps + " kbps, " + + root.avatarMixerPps + "pps"; + } + Text { + color: "white"; + visible: root.expanded; + text: "Downloads: "; + } + } + } + Rectangle { + width: octreeCol.width + 8 + height: octreeCol.height + 8 + color: "#99333333" + MouseArea { + anchors.fill: parent + onClicked: { root.expanded = !root.expanded; } + } + Column { + id: octreeCol + spacing: 4; x: 4; y: 4; + width: sTATS_OCTREE_MIN_WIDTH + Text { + color: "white"; + text: "Triangles: " + root.triangles + + " / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches + } + Text { + color: "white"; + visible: root.expanded; + text: "\tMesh Parts Rendered Opaque: " + root.meshOpaque + + " / Translucent: " + root.meshTranslucent; + } + Text { + color: "white"; + visible: root.expanded; + text: "\tOpaque considered: " + root.opaqueConsidered + + " / Out of view: " + root.opaqueOutOfView + " / Too small: " + root.opaqueTooSmall; + } + Text { + color: "white"; + visible: !root.expanded + text: "Octree Elements Server: "; + } + } + } + } + + Connections { + target: root.parent + onWidthChanged: { + root.x = root.parent.width - root.width; + } + } +} + diff --git a/interface/resources/qml/Tooltip.qml b/interface/resources/qml/Tooltip.qml new file mode 100644 index 0000000000..f1504317e2 --- /dev/null +++ b/interface/resources/qml/Tooltip.qml @@ -0,0 +1,29 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.3 as Original +import "controls" +import "styles" + +Hifi.Tooltip { + id: root + HifiConstants { id: hifi } + x: lastMousePosition.x + y: lastMousePosition.y + implicitWidth: border.implicitWidth + implicitHeight: border.implicitHeight + + Border { + id: border + anchors.fill: parent + implicitWidth: text.implicitWidth + implicitHeight: Math.max(text.implicitHeight, 64) + + Text { + id: text + anchors.fill: parent + anchors.margins: 16 + font.pixelSize: hifi.fonts.pixelSize / 2 + text: root.text + wrapMode: Original.Text.WordWrap + } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6270c8f597..2aa718a27c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -88,6 +88,7 @@ #include #include #include +#include #include #include #include @@ -822,6 +823,7 @@ void Application::initializeUi() { LoginDialog::registerType(); MessageDialog::registerType(); VrMenu::registerType(); + Tooltip::registerType(); auto offscreenUi = DependencyManager::get(); offscreenUi->create(_glWidget->context()->contextHandle()); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index ab5014dfaa..b0c21cf77d 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "AudioClient.h" @@ -41,7 +42,7 @@ static const float MAG_SPEED = 0.08f; static const quint64 MSECS_TO_USECS = 1000ULL; -static const quint64 TOOLTIP_DELAY = 2000000ULL; +static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; static const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; static const float RETICLE_COLOR[] = { 0.0f, 198.0f / 255.0f, 244.0f / 255.0f }; @@ -59,7 +60,7 @@ static gpu::BufferPointer _hemiVertices; static gpu::BufferPointer _hemiIndices; static int _hemiIndexCount{ 0 }; EntityItemID ApplicationCompositor::_noItemId; - +static QString _tooltipId; // Return a point's cartesian coordinates on a sphere from pitch and yaw glm::vec3 getPoint(float yaw, float pitch) { @@ -159,6 +160,11 @@ ApplicationCompositor::ApplicationCompositor() { _hoverItemHref.clear(); auto cursor = Cursor::Manager::instance().getCursor(); cursor->setIcon(Cursor::Icon::DEFAULT); + if (!_tooltipId.isEmpty()) { + qDebug() << "Closing tooltip " << _tooltipId; + Tooltip::closeTip(_tooltipId); + _tooltipId.clear(); + } } }); } @@ -166,6 +172,7 @@ ApplicationCompositor::ApplicationCompositor() { ApplicationCompositor::~ApplicationCompositor() { } + void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorIndex) { auto& cursorManager = Cursor::Manager::instance(); auto cursor = cursorManager.getCursor(cursorIndex); @@ -184,17 +191,10 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { return; } + updateTooltips(); + vec2 canvasSize = qApp->getCanvasSize(); - _textureAspectRatio = aspect(canvasSize); - - if (_hoverItemId != _noItemId) { - quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs; - if (!_hoverItemHref.isEmpty() && hoverDuration > TOOLTIP_DELAY) { - // TODO Enable and position the tooltip - } - } - //Handle fading and deactivation/activation of UI gpu::Batch batch; @@ -262,6 +262,11 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int return; } + vec2 canvasSize = qApp->getCanvasSize(); + _textureAspectRatio = aspect(canvasSize); + + updateTooltips(); + renderArgs->_context->syncCache(); auto geometryCache = DependencyManager::get(); @@ -392,13 +397,6 @@ bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& positi //Renders optional pointers void ApplicationCompositor::renderPointers(gpu::Batch& batch) { - //glEnable(GL_TEXTURE_2D); - //glEnable(GL_BLEND); - //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - //glActiveTexture(GL_TEXTURE0); - //bindCursorTexture(); - if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { //If we are in oculus, render reticle later if (_lastMouseMove == 0) { @@ -442,8 +440,6 @@ void ApplicationCompositor::renderPointers(gpu::Batch& batch) { _magActive[MOUSE] = false; renderControllerPointers(batch); } - //glBindTexture(GL_TEXTURE_2D, 0); - //glDisable(GL_TEXTURE_2D); } @@ -718,7 +714,6 @@ void ApplicationCompositor::drawSphereSection(gpu::Batch& batch) { batch.drawIndexed(gpu::TRIANGLES, _hemiIndexCount); } - glm::vec2 ApplicationCompositor::directionToSpherical(const glm::vec3& direction) { glm::vec2 result; // Compute yaw @@ -788,127 +783,13 @@ glm::vec2 ApplicationCompositor::overlayToScreen(const glm::vec2& overlayPos) co return sphericalToScreen(overlayToSpherical(overlayPos)); } -#if 0 - -gpu::PipelinePointer ApplicationOverlay::getDrawPipeline() { - if (!_standardDrawPipeline) { - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); - auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(standardDrawTexture_frag))); - auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); - gpu::Shader::makeProgram((*program)); - - auto state = gpu::StatePointer(new gpu::State()); - - // enable decal blend - state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - - _standardDrawPipeline.reset(gpu::Pipeline::create(program, state)); - } - - return _standardDrawPipeline; -} - - -// Draws the FBO texture for the screen -void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { - if (_alpha == 0.0f) { - return; - } - - renderArgs->_context->syncCache(); - - gpu::Batch batch; - Transform model; - //DependencyManager::get()->bindSimpleProgram(batch, true); - batch.setPipeline(getDrawPipeline()); - batch.setModelTransform(Transform()); - batch.setProjectionTransform(mat4()); - batch.setViewTransform(model); - batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - DependencyManager::get()->renderUnitQuad(batch, vec4(vec3(1), _alpha)); - - //draw the mouse pointer - glm::vec2 canvasSize = qApp->getCanvasSize(); - - // Get the mouse coordinates and convert to NDC [-1, 1] - vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); - mousePosition /= canvasSize; - mousePosition *= 2.0f; - mousePosition -= 1.0f; - mousePosition.y *= -1.0f; - model.setTranslation(vec3(mousePosition, 0)); - glm::vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; - model.setScale(vec3(mouseSize, 1.0f)); - batch.setModelTransform(model); - bindCursorTexture(batch); - glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; - DependencyManager::get()->renderUnitQuad(batch, vec4(1)); - renderArgs->_context->render(batch); -} - - - -// Draws the FBO texture for Oculus rift. -void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera) { - if (_alpha == 0.0f) { - return; - } - - renderArgs->_context->syncCache(); - - gpu::Batch batch; - batch.setPipeline(getDrawPipeline()); - batch._glDisable(GL_DEPTH_TEST); - batch._glDisable(GL_CULL_FACE); - batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - batch.setProjectionTransform(whichCamera.getProjection()); - batch.setViewTransform(Transform()); - - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - const quat& avatarOrientation = myAvatar->getOrientation(); - quat hmdOrientation = qApp->getCamera()->getHmdRotation(); - vec3 hmdPosition = glm::inverse(avatarOrientation) * qApp->getCamera()->getHmdPosition(); - mat4 overlayXfm = glm::mat4_cast(glm::inverse(hmdOrientation)) * glm::translate(mat4(), -hmdPosition); - batch.setModelTransform(Transform(overlayXfm)); - drawSphereSection(batch); - - - bindCursorTexture(batch); - auto geometryCache = DependencyManager::get(); - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); - //Controller Pointers - for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - PalmData& palm = myAvatar->getHand()->getPalms()[i]; - if (palm.isActive()) { - glm::vec2 polar = getPolarCoordinates(palm); - // Convert to quaternion - mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - mat4 reticleXfm = overlayXfm * pointerXfm; - reticleXfm = glm::scale(reticleXfm, reticleScale); - batch.setModelTransform(reticleXfm); - // Render reticle at location - geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); +void ApplicationCompositor::updateTooltips() { + if (_hoverItemId != _noItemId) { + quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs; + if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemHref.isEmpty() && hoverDuration > TOOLTIP_DELAY) { + // TODO Enable and position the tooltip + _hoverItemEnterUsecs = UINT64_MAX; + _tooltipId = Tooltip::showTip("URL: " + _hoverItemHref); } } - - //Mouse Pointer - if (_reticleActive[MOUSE]) { - glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(), - _reticlePosition[MOUSE].y())); - mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - mat4 reticleXfm = overlayXfm * pointerXfm; - reticleXfm = glm::scale(reticleXfm, reticleScale); - batch.setModelTransform(reticleXfm); - geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); - } - - renderArgs->_context->render(batch); } - - - -#endif diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 5145e14006..0063319796 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -74,6 +74,7 @@ private: void bindCursorTexture(gpu::Batch& batch, uint8_t cursorId = 0); void buildHemiVertices(const float fov, const float aspectRatio, const int slices, const int stacks); void drawSphereSection(gpu::Batch& batch); + void updateTooltips(); void renderPointers(gpu::Batch& batch); void renderMagnifier(gpu::Batch& batch, const glm::vec2& magPos, float sizeMult, bool showBorder); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 6471fcfb16..86f06ba7fa 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -50,6 +50,8 @@ static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f; static const float MIRROR_REARVIEW_DISTANCE = 0.722f; static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.56f; static const float MIRROR_FIELD_OF_VIEW = 30.0f; +static const float ORTHO_NEAR_CLIP = -10000; +static const float ORTHO_FAR_CLIP = 10000; ApplicationOverlay::ApplicationOverlay() : @@ -85,6 +87,7 @@ ApplicationOverlay::~ApplicationOverlay() { void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); + Stats::getInstance()->updateStats(); glm::vec2 size = qApp->getCanvasSize(); // TODO Handle fading and deactivation/activation of UI @@ -98,41 +101,38 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { glViewport(0, 0, size.x, size.y); // Now render the overlay components together into a single texture - gpu::Batch batch; - auto geometryCache = DependencyManager::get(); - geometryCache->useSimpleDrawPipeline(batch); - batch._glDisable(GL_DEPTH); - batch._glDisable(GL_LIGHTING); - batch._glEnable(GL_BLEND); + //renderOverlays(renderArgs); + //renderAudioMeter(renderArgs); + //renderCameraToggle(renderArgs); + //renderStatsAndLogs(renderArgs); - renderOverlays(batch, renderArgs); - renderAudioMeter(batch); - renderCameraToggle(batch); - renderStatsAndLogs(batch); - renderDomainConnectionStatusBorder(batch); - renderQmlUi(batch); + renderDomainConnectionStatusBorder(renderArgs); + renderQmlUi(renderArgs); - renderArgs->_context->syncCache(); - renderArgs->_context->render(batch); _overlayFramebuffer->release(); } -void ApplicationOverlay::renderQmlUi(gpu::Batch& batch) { +void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { if (_uiTexture) { + gpu::Batch batch; + auto geometryCache = DependencyManager::get(); + geometryCache->useSimpleDrawPipeline(batch); + batch._glDisable(GL_DEPTH_TEST); + batch._glDisable(GL_LIGHTING); + batch._glEnable(GL_BLEND); batch.setProjectionTransform(mat4()); batch.setModelTransform(mat4()); batch._glBindTexture(GL_TEXTURE_2D, _uiTexture); - auto geometryCache = DependencyManager::get(); geometryCache->renderUnitQuad(batch, glm::vec4(1)); + renderArgs->_context->syncCache(); + renderArgs->_context->render(batch); } } -void ApplicationOverlay::renderOverlays(gpu::Batch& batch, RenderArgs* renderArgs) { - static const float NEAR_CLIP = -10000; - static const float FAR_CLIP = 10000; +void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { glm::vec2 size = qApp->getCanvasSize(); - mat4 legacyProjection = glm::ortho(0, size.x, size.y, 0, NEAR_CLIP, FAR_CLIP); + mat4 legacyProjection = glm::ortho(0, size.x, size.y, 0, ORTHO_NEAR_CLIP, ORTHO_FAR_CLIP); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf(glm::value_ptr(legacyProjection)); @@ -152,8 +152,7 @@ void ApplicationOverlay::renderOverlays(gpu::Batch& batch, RenderArgs* renderArg glMatrixMode(GL_MODELVIEW); } -void ApplicationOverlay::renderCameraToggle(gpu::Batch& batch) { - /* +void ApplicationOverlay::renderCameraToggle(RenderArgs* renderArgs) { if (Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)) { return; } @@ -169,11 +168,9 @@ void ApplicationOverlay::renderCameraToggle(gpu::Batch& batch) { } DependencyManager::get()->render(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, audioMeterY, boxed); - */ } -void ApplicationOverlay::renderAudioMeter(gpu::Batch& batch) { - /* +void ApplicationOverlay::renderAudioMeter(RenderArgs* renderArgs) { auto audio = DependencyManager::get(); // Audio VU Meter and Mute Icon @@ -287,10 +284,9 @@ void ApplicationOverlay::renderAudioMeter(gpu::Batch& batch) { audioLevel, AUDIO_METER_HEIGHT, quadColor, _audioBlueQuad); } - */ } -void ApplicationOverlay::renderRearView(gpu::Batch& batch) { +void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { // // Grab current viewport to reset it at the end // int viewport[4]; // glGetIntegerv(GL_VIEWPORT, viewport); @@ -369,37 +365,18 @@ void ApplicationOverlay::renderRearView(gpu::Batch& batch) { //} } -void ApplicationOverlay::renderStatsAndLogs(gpu::Batch& batch) { - /* - Application* application = Application::getInstance(); - QSharedPointer bandwidthRecorder = DependencyManager::get(); - - const OctreePacketProcessor& octreePacketProcessor = application->getOctreePacketProcessor(); - NodeBounds& nodeBoundsDisplay = application->getNodeBoundsDisplay(); +void ApplicationOverlay::renderStatsAndLogs(RenderArgs* renderArgs) { // Display stats and log text onscreen - batch._glLineWidth(1.0f); - glPointSize(1.0f); // Determine whether to compute timing details bool shouldDisplayTimingDetail = Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails) && - Menu::getInstance()->isOptionChecked(MenuOption::Stats) && - Stats::getInstance()->isExpanded(); + Menu::getInstance()->isOptionChecked(MenuOption::Stats); //&& + // Stats::getInstance()->isExpanded(); if (shouldDisplayTimingDetail != PerformanceTimer::isActive()) { PerformanceTimer::setActive(shouldDisplayTimingDetail); } - - if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - // let's set horizontal offset to give stats some margin to mirror - int voxelPacketsToProcess = octreePacketProcessor.packetsToProcessCount(); - // Onscreen text about position, servers, etc - Stats::getInstance()->display(WHITE_TEXT, STATS_HORIZONTAL_OFFSET, application->getFps(), - bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(), - bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(), - bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(), - bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(), - voxelPacketsToProcess); - } + Stats::getInstance()->updateStats(); // Show on-screen msec timer if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) { @@ -412,16 +389,26 @@ void ApplicationOverlay::renderStatsAndLogs(gpu::Batch& batch) { drawText(canvasSize.x - 100, canvasSize.y - timerBottom, 0.30f, 0.0f, 0, frameTimer.toUtf8().constData(), WHITE_TEXT); } + + glPointSize(1.0f); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + NodeBounds& nodeBoundsDisplay = qApp->getNodeBoundsDisplay(); nodeBoundsDisplay.drawOverlay(); - */ + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); } -void ApplicationOverlay::renderDomainConnectionStatusBorder(gpu::Batch& batch) { +void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderArgs) { auto geometryCache = DependencyManager::get(); std::once_flag once; std::call_once(once, [&] { QVector points; - static const float B = 0.99; + static const float B = 0.99f; points.push_back(vec2(-B)); points.push_back(vec2(B, -B)); points.push_back(vec2(B)); @@ -430,9 +417,13 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder(gpu::Batch& batch) { geometryCache->updateVertices(_domainStatusBorder, points, CONNECTION_STATUS_BORDER_COLOR); }); auto nodeList = DependencyManager::get(); - - if (nodeList && !nodeList->getDomainHandler().isConnected()) { + gpu::Batch batch; + auto geometryCache = DependencyManager::get(); + geometryCache->useSimpleDrawPipeline(batch); + batch._glDisable(GL_DEPTH); + batch._glDisable(GL_LIGHTING); + batch._glEnable(GL_BLEND); batch.setProjectionTransform(mat4()); batch.setModelTransform(mat4()); batch.setUniformTexture(0, DependencyManager::get()->getWhiteTexture()); @@ -445,6 +436,8 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder(gpu::Batch& batch) { //batch.setModelTransform(glm::scale(mat4(), vec3(scaleAmount))); geometryCache->renderVertices(batch, gpu::LINE_STRIP, _domainStatusBorder); + renderArgs->_context->syncCache(); + renderArgs->_context->render(batch); } } diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 9076d61b18..efdec01fb5 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -29,13 +29,13 @@ public: private: - void renderAudioMeter(gpu::Batch& batch); - void renderCameraToggle(gpu::Batch& batch); - void renderStatsAndLogs(gpu::Batch& batch); - void renderDomainConnectionStatusBorder(gpu::Batch& batch); - void renderRearView(gpu::Batch& batch); - void renderQmlUi(gpu::Batch& batch); - void renderOverlays(gpu::Batch& batch, RenderArgs* renderArgs); + void renderAudioMeter(RenderArgs* renderArgs); + void renderCameraToggle(RenderArgs* renderArgs); + void renderStatsAndLogs(RenderArgs* renderArgs); + void renderDomainConnectionStatusBorder(RenderArgs* renderArgs); + void renderRearView(RenderArgs* renderArgs); + void renderQmlUi(RenderArgs* renderArgs); + void renderOverlays(RenderArgs* renderArgs); void buildFramebufferObject(); float _alpha{ 1.0f }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8359480b03..fc9ceb8a67 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -9,9 +9,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include -#include +#include "Stats.h" #include #include @@ -25,147 +25,30 @@ #include #include -#include "Stats.h" #include "BandwidthRecorder.h" #include "InterfaceConfig.h" #include "Menu.h" #include "Util.h" #include "SequenceNumberStats.h" +HIFI_QML_DEF(Stats) + using namespace std; -const int STATS_PELS_PER_LINE = 16; -const int STATS_PELS_INITIALOFFSET = 12; - -const int STATS_GENERAL_MIN_WIDTH = 165; -const int STATS_PING_MIN_WIDTH = 190; -const int STATS_GEO_MIN_WIDTH = 240; -const int STATS_OCTREE_MIN_WIDTH = 410; +static Stats* INSTANCE{ nullptr }; Stats* Stats::getInstance() { - static Stats stats; - return &stats; + if (!INSTANCE) { + Stats::registerType(); + Stats::show(); + //Stats::toggle(); + Q_ASSERT(INSTANCE); + } + return INSTANCE; } -Stats::Stats(): - _expanded(false), - _recentMaxPackets(0), - _resetRecentMaxPacketsSoon(true), - _generalStatsWidth(STATS_GENERAL_MIN_WIDTH), - _pingStatsWidth(STATS_PING_MIN_WIDTH), - _geoStatsWidth(STATS_GEO_MIN_WIDTH), - _octreeStatsWidth(STATS_OCTREE_MIN_WIDTH), - _lastHorizontalOffset(0) -{ - auto canvasSize = Application::getInstance()->getCanvasSize(); - resetWidth(canvasSize.x, 0); -} - -void Stats::toggleExpanded() { - _expanded = !_expanded; -} - -// called on mouse click release -// check for clicks over stats in order to expand or contract them -void Stats::checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset) { - auto canvasSize = Application::getInstance()->getCanvasSize(); - - if (0 != glm::compMax(glm::abs(glm::ivec2(mouseX - mouseDragStartedX, mouseY - mouseDragStartedY)))) { - // not worried about dragging on stats - return; - } - - int statsHeight = 0, - statsWidth = 0, - statsX = 0, - statsY = 0, - lines = 0; - - statsX = horizontalOffset; - - // top-left stats click - lines = _expanded ? 5 : 3; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - if (mouseX > statsX && mouseX < statsX + _generalStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { - toggleExpanded(); - return; - } - statsX += _generalStatsWidth; - - // ping stats click - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { - lines = _expanded ? 4 : 3; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - if (mouseX > statsX && mouseX < statsX + _pingStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { - toggleExpanded(); - return; - } - statsX += _pingStatsWidth; - } - - // geo stats panel click - lines = _expanded ? 4 : 3; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - if (mouseX > statsX && mouseX < statsX + _geoStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { - toggleExpanded(); - return; - } - statsX += _geoStatsWidth; - - // top-right stats click - lines = _expanded ? 11 : 3; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - statsWidth = canvasSize.x - statsX; - if (mouseX > statsX && mouseX < statsX + statsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { - toggleExpanded(); - return; - } -} - -void Stats::resetWidth(int width, int horizontalOffset) { - auto canvasSize = Application::getInstance()->getCanvasSize(); - int extraSpace = canvasSize.x - horizontalOffset - 2 - - STATS_GENERAL_MIN_WIDTH - - (Menu::getInstance()->isOptionChecked(MenuOption::TestPing) ? STATS_PING_MIN_WIDTH -1 : 0) - - STATS_GEO_MIN_WIDTH - - STATS_OCTREE_MIN_WIDTH; - - int panels = 4; - - _generalStatsWidth = STATS_GENERAL_MIN_WIDTH; - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { - _pingStatsWidth = STATS_PING_MIN_WIDTH; - } else { - _pingStatsWidth = 0; - panels = 3; - } - _geoStatsWidth = STATS_GEO_MIN_WIDTH; - _octreeStatsWidth = STATS_OCTREE_MIN_WIDTH; - - if (extraSpace > panels) { - _generalStatsWidth += (int) extraSpace / panels; - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { - _pingStatsWidth += (int) extraSpace / panels; - } - _geoStatsWidth += (int) extraSpace / panels; - _octreeStatsWidth += canvasSize.x - - (_generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3); - } -} - - -// translucent background box that makes stats more readable -void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int height) { - glm::vec4 color(((rgba >> 24) & 0xff) / 255.0f, - ((rgba >> 16) & 0xff) / 255.0f, - ((rgba >> 8) & 0xff) / 255.0f, - (rgba & 0xff) / 255.0f); - - // FIX ME: is this correct? It seems to work to fix textures bleeding into us... - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - - DependencyManager::get()->renderQuad(x, y, width, height, color); +Stats::Stats(QQuickItem* parent) : QQuickItem(parent) { + INSTANCE = this; } bool Stats::includeTimingRecord(const QString& name) { @@ -190,161 +73,63 @@ bool Stats::includeTimingRecord(const QString& name) { return false; } -// display expanded or contracted stats -void Stats::display( - const float* color, - int horizontalOffset, - float fps, - int inPacketsPerSecond, - int outPacketsPerSecond, - int inKbitsPerSecond, - int outKbitsPerSecond, - int voxelPacketsToProcess) -{ - auto canvasSize = Application::getInstance()->getCanvasSize(); - - unsigned int backgroundColor = 0x33333399; - int verticalOffset = 0, lines = 0; - float scale = 0.10f; - float rotation = 0.0f; - int font = 2; - - QLocale locale(QLocale::English); - std::stringstream octreeStats; - - QSharedPointer bandwidthRecorder = DependencyManager::get(); - - if (_lastHorizontalOffset != horizontalOffset) { - resetWidth(canvasSize.x, horizontalOffset); - _lastHorizontalOffset = horizontalOffset; +#define STAT_UPDATE(name, src) \ + { \ + auto val = src; \ + if (_##name != val) { \ + _##name = val; \ + emit name##Changed(); \ + } \ } - glPointSize(1.0f); +#define STAT_UPDATE_FLOAT(name, src, epsilon) \ + { \ + float val = src; \ + if (abs(_##name - val) >= epsilon) { \ + _##name = val; \ + emit name##Changed(); \ + } \ + } + +void Stats::updateStats() { + if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + if (isVisible()) { + setVisible(false); + } + return; + } else { + if (!isVisible()) { + setVisible(true); + } + } + + auto nodeList = DependencyManager::get(); + auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves - int totalAvatars = DependencyManager::get()->size() - 1; - int totalServers = DependencyManager::get()->size(); + STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(serverCount, nodeList->size()); + STAT_UPDATE(framerate, (int)qApp->getFps()); - lines = 5; - int columnOneWidth = _generalStatsWidth; - - bool performanceTimerIsActive = PerformanceTimer::isActive(); - bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails); - if (displayPerf && performanceTimerIsActive) { - PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up - - columnOneWidth = _generalStatsWidth + _pingStatsWidth + _geoStatsWidth; // 3 columns wide... - // we will also include room for 1 line per timing record and a header of 4 lines - lines += 4; - - const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); - QMapIterator i(allRecords); - bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); - int statsLines = 0; - while (i.hasNext()) { - i.next(); - if (includeTimingRecord(i.key())) { - lines++; - statsLines++; - if (onlyDisplayTopTen && statsLines == 10) { - break; - } - } - } - } - - drawBackground(backgroundColor, horizontalOffset, 0, columnOneWidth, (lines + 1) * STATS_PELS_PER_LINE); - horizontalOffset += 5; - - int columnOneHorizontalOffset = horizontalOffset; - - QString serverNodes = QString("Servers: %1").arg(totalServers); - QString avatarNodes = QString("Avatars: %1").arg(totalAvatars); - QString framesPerSecond = QString("Framerate: %1 FPS").arg(fps, 3, 'f', 0); - - verticalOffset = STATS_PELS_INITIALOFFSET; // first one is offset by less than a line - drawText(horizontalOffset, verticalOffset, scale, rotation, font, serverNodes.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarNodes.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, framesPerSecond.toUtf8().constData(), color); - - QString packetsPerSecondString = QString("Packets In/Out: %1/%2").arg(inPacketsPerSecond).arg(outPacketsPerSecond); - QString averageMegabitsPerSecond = QString("Mbps In/Out: %1/%2"). - arg((float)inKbitsPerSecond * 1.0f / 1000.0f). - arg((float)outKbitsPerSecond * 1.0f / 1000.0f); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, packetsPerSecondString.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond.toUtf8().constData(), color); - - - // TODO: the display of these timing details should all be moved to JavaScript - if (displayPerf && performanceTimerIsActive) { - bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); - // Timing details... - verticalOffset += STATS_PELS_PER_LINE * 4; // skip 3 lines to be under the other columns - drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, - "-------------------------------------------------------- Function " - "------------------------------------------------------- --msecs- -calls--", color); - - // First iterate all the records, and for the ones that should be included, insert them into - // a new Map sorted by average time... - QMap sortedRecords; - const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); - QMapIterator i(allRecords); - - while (i.hasNext()) { - i.next(); - if (includeTimingRecord(i.key())) { - float averageTime = (float)i.value().getMovingAverage() / (float)USECS_PER_MSEC; - sortedRecords.insertMulti(averageTime, i.key()); - } - } - - int linesDisplayed = 0; - QMapIterator j(sortedRecords); - j.toBack(); - while (j.hasPrevious()) { - j.previous(); - QChar noBreakingSpace = QChar::Nbsp; - QString functionName = j.value(); - const PerformanceTimerRecord& record = allRecords.value(functionName); - - QString perfLine = QString("%1: %2 [%3]"). - arg(QString(qPrintable(functionName)), 120, noBreakingSpace). - arg((float)record.getMovingAverage() / (float)USECS_PER_MSEC, 8, 'f', 3, noBreakingSpace). - arg((int)record.getCount(), 6, 10, noBreakingSpace); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine.toUtf8().constData(), color); - linesDisplayed++; - if (onlyDisplayTopTen && linesDisplayed == 10) { - break; - } - } - } - - - verticalOffset = STATS_PELS_INITIALOFFSET; - horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + 1; + auto bandwidthRecorder = DependencyManager::get(); + STAT_UPDATE(packetInCount, bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond()); + STAT_UPDATE(packetOutCount, bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond()); + STAT_UPDATE_FLOAT(mbpsIn, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f); + STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond() / 1000.0f, 0.01f); + // Second column: ping if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { - int pingAudio = -1, pingAvatar = -1, pingVoxel = -1, pingOctreeMax = -1; - - auto nodeList = DependencyManager::get(); SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1); + STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1); - pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : -1; - pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : -1; - - // Now handle voxel servers, since there could be more than one, we average their ping times + //// Now handle voxel servers, since there could be more than one, we average their ping times unsigned long totalPingOctree = 0; int octreeServerCount = 0; - - nodeList->eachNode([&totalPingOctree, &pingOctreeMax, &octreeServerCount](const SharedNodePointer& node){ + int pingOctreeMax = 0; + int pingVoxel; + nodeList->eachNode([&](const SharedNodePointer& node) { // TODO: this should also support entities if (node->getType() == NodeType::EntityServer) { totalPingOctree += node->getPingMs(); @@ -359,146 +144,101 @@ void Stats::display( pingVoxel = totalPingOctree / octreeServerCount; } - lines = _expanded ? 4 : 3; - - // only draw our background if column one didn't draw a wide background - if (columnOneWidth == _generalStatsWidth) { - drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, (lines + 1) * STATS_PELS_PER_LINE); - } - horizontalOffset += 5; - - - QString audioPing; - if (pingAudio >= 0) { - audioPing = QString("Audio ping: %1").arg(pingAudio); - } else { - audioPing = QString("Audio ping: --"); - } - - QString avatarPing; - if (pingAvatar >= 0) { - avatarPing = QString("Avatar ping: %1").arg(pingAvatar); - } else { - avatarPing = QString("Avatar ping: --"); - } - - QString voxelAvgPing; - if (pingVoxel >= 0) { - voxelAvgPing = QString("Entities avg ping: %1").arg(pingVoxel); - } else { - voxelAvgPing = QString("Entities avg ping: --"); - } - - drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioPing.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPing.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelAvgPing.toUtf8().constData(), color); - - if (_expanded) { - QString voxelMaxPing; - if (pingVoxel >= 0) { // Average is only meaningful if pingVoxel is valid. - voxelMaxPing = QString("Voxel max ping: %1").arg(pingOctreeMax); - } else { - voxelMaxPing = QString("Voxel max ping: --"); - } - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing.toUtf8().constData(), color); - } - - verticalOffset = STATS_PELS_INITIALOFFSET; - horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2; + STAT_UPDATE(entitiesPing, pingVoxel); + //if (_expanded) { + // QString voxelMaxPing; + // if (pingVoxel >= 0) { // Average is only meaningful if pingVoxel is valid. + // voxelMaxPing = QString("Voxel max ping: %1").arg(pingOctreeMax); + // } else { + // voxelMaxPing = QString("Voxel max ping: --"); + // } + } else { + // -2 causes the QML to hide the ping column + STAT_UPDATE(audioPing, -2); } - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + // Third column, avatar stats + MyAvatar* myAvatar = avatarManager->getMyAvatar(); glm::vec3 avatarPos = myAvatar->getPosition(); - - lines = _expanded ? 7 : 3; - - if (columnOneWidth == _generalStatsWidth) { - drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, (lines + 1) * STATS_PELS_PER_LINE); - } - horizontalOffset += 5; - - QString avatarPosition = QString("Position: %1, %2, %3"). - arg(avatarPos.x, -1, 'f', 1). - arg(avatarPos.y, -1, 'f', 1). - arg(avatarPos.z, -1, 'f', 1); - QString avatarVelocity = QString("Velocity: %1").arg(glm::length(myAvatar->getVelocity()), -1, 'f', 1); - QString avatarBodyYaw = QString("Yaw: %1").arg(myAvatar->getBodyYaw(), -1, 'f', 1); - QString avatarMixerStats; - - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPosition.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarVelocity.toUtf8().constData(), color); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarBodyYaw.toUtf8().constData(), color); - + STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z)); + STAT_UPDATE_FLOAT(velocity, glm::length(myAvatar->getVelocity()), 0.1f); + STAT_UPDATE_FLOAT(yaw, myAvatar->getBodyYaw(), 0.1f); if (_expanded) { - SharedNodePointer avatarMixer = DependencyManager::get()->soloNodeOfType(NodeType::AvatarMixer); + SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); if (avatarMixer) { - avatarMixerStats = QString("Avatar Mixer: %1 kbps, %2 pps"). - arg(roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + - bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer))). - arg(roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) + - bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(avatarMixerKbps, roundf( + bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AvatarMixer) + + bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AvatarMixer))); + STAT_UPDATE(avatarMixerPps, roundf( + bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AvatarMixer) + + bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AvatarMixer))); } else { - avatarMixerStats = QString("No Avatar Mixer"); + STAT_UPDATE(avatarMixerKbps, -1); + STAT_UPDATE(avatarMixerPps, -1); + } + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + if (audioMixerNode) { + STAT_UPDATE(audioMixerKbps, roundf( + bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + + bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(audioMixerPps, roundf( + bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) + + bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer))); + } else { + STAT_UPDATE(audioMixerKbps, -1); + STAT_UPDATE(audioMixerPps, -1); } - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarMixerStats.toUtf8().constData(), color); - - stringstream downloads; - downloads << "Downloads: "; - foreach (Resource* resource, ResourceCache::getLoadingRequests()) { - downloads << (int)(resource->getProgress() * 100.0f) << "% "; - } - downloads << "(" << ResourceCache::getPendingRequestCount() << " pending)"; - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color); - } + STAT_UPDATE(downloads, ResourceCache::getLoadingRequests().size()); + STAT_UPDATE(downloadsPending, ResourceCache::getPendingRequestCount()); + // TODO fix to match original behavior + //stringstream downloads; + //downloads << "Downloads: "; + //foreach(Resource* resource, ) { + // downloads << (int)(resource->getProgress() * 100.0f) << "% "; + //} + //downloads << "(" << << " pending)"; + } // expanded avatar column - verticalOffset = STATS_PELS_INITIALOFFSET; - horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3; - - lines = _expanded ? 10 : 3; - - drawBackground(backgroundColor, horizontalOffset, 0, canvasSize.x - horizontalOffset, - (lines + 1) * STATS_PELS_PER_LINE); - horizontalOffset += 5; - - // Model/Entity render details - octreeStats.str(""); - octreeStats << "Triangles: " << _renderDetails._trianglesRendered - << " / Quads:" << _renderDetails._quadsRendered - << " / Material Switches:" << _renderDetails._materialSwitches; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); + // Fourth column, octree stats +} +void Stats::setRenderDetails(const RenderDetails& details) { + //STATS_PROPERTY(int, triangles, 0) + //STATS_PROPERTY(int, quads, 0) + //STATS_PROPERTY(int, materialSwitches, 0) + //STATS_PROPERTY(int, meshOpaque, 0) + //STATS_PROPERTY(int, meshTranslucent, 0) + //STATS_PROPERTY(int, opaqueConsidered, 0) + //STATS_PROPERTY(int, opaqueOutOfView, 0) + //STATS_PROPERTY(int, opaqueTooSmall, 0) + //STATS_PROPERTY(int, translucentConsidered, 0) + //STATS_PROPERTY(int, translucentOutOfView, 0) + //STATS_PROPERTY(int, translucentTooSmall, 0) + //STATS_PROPERTY(int, octreeElementsServer, 0) + //STATS_PROPERTY(int, octreeElementsLocal, 0) + STAT_UPDATE(triangles, details._trianglesRendered); + STAT_UPDATE(quads, details._quadsRendered); + STAT_UPDATE(materialSwitches, details._materialSwitches); if (_expanded) { - octreeStats.str(""); - octreeStats << " Mesh Parts Rendered Opaque: " << _renderDetails._opaque._rendered - << " / Translucent:" << _renderDetails._translucent._rendered; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); - - octreeStats.str(""); - octreeStats << " Opaque considered: " << _renderDetails._opaque._considered - << " / Out of view:" << _renderDetails._opaque._outOfView - << " / Too small:" << _renderDetails._opaque._tooSmall; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); - - octreeStats.str(""); - octreeStats << " Translucent considered: " << _renderDetails._translucent._considered - << " / Out of view:" << _renderDetails._translucent._outOfView - << " / Too small:" << _renderDetails._translucent._tooSmall; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); + STAT_UPDATE(meshOpaque, details._opaque._rendered); + STAT_UPDATE(meshTranslucent, details._opaque._rendered); + STAT_UPDATE(opaqueConsidered, details._opaque._considered); + STAT_UPDATE(opaqueOutOfView, details._opaque._outOfView); + STAT_UPDATE(opaqueTooSmall, details._opaque._tooSmall); + STAT_UPDATE(translucentConsidered, details._translucent._considered); + STAT_UPDATE(translucentOutOfView, details._translucent._outOfView); + STAT_UPDATE(translucentTooSmall, details._translucent._tooSmall); } +} + +/* +// display expanded or contracted stats +void Stats::display( + int voxelPacketsToProcess) +{ // iterate all the current voxel stats, and list their sending modes, and total voxel counts std::stringstream sendingMode(""); sendingMode << "Octree Sending Mode: ["; @@ -619,3 +359,81 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); } + +//// Performance timer + + + bool performanceTimerIsActive = PerformanceTimer::isActive(); + bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails); + if (displayPerf && performanceTimerIsActive) { + PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up + columnOneWidth = _generalStatsWidth + _pingStatsWidth + _geoStatsWidth; // 3 columns wide... + // we will also include room for 1 line per timing record and a header of 4 lines + lines += 4; + + const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + QMapIterator i(allRecords); + bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); + int statsLines = 0; + while (i.hasNext()) { + i.next(); + if (includeTimingRecord(i.key())) { + lines++; + statsLines++; + if (onlyDisplayTopTen && statsLines == 10) { + break; + } + } + } + } + + + // TODO: the display of these timing details should all be moved to JavaScript + if (displayPerf && performanceTimerIsActive) { + bool onlyDisplayTopTen = Menu::getInstance()->isOptionChecked(MenuOption::OnlyDisplayTopTen); + // Timing details... + verticalOffset += STATS_PELS_PER_LINE * 4; // skip 3 lines to be under the other columns + drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, + "-------------------------------------------------------- Function " + "------------------------------------------------------- --msecs- -calls--", color); + + // First iterate all the records, and for the ones that should be included, insert them into + // a new Map sorted by average time... + QMap sortedRecords; + const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + QMapIterator i(allRecords); + + while (i.hasNext()) { + i.next(); + if (includeTimingRecord(i.key())) { + float averageTime = (float)i.value().getMovingAverage() / (float)USECS_PER_MSEC; + sortedRecords.insertMulti(averageTime, i.key()); + } + } + + int linesDisplayed = 0; + QMapIterator j(sortedRecords); + j.toBack(); + while (j.hasPrevious()) { + j.previous(); + QChar noBreakingSpace = QChar::Nbsp; + QString functionName = j.value(); + const PerformanceTimerRecord& record = allRecords.value(functionName); + + QString perfLine = QString("%1: %2 [%3]"). + arg(QString(qPrintable(functionName)), 120, noBreakingSpace). + arg((float)record.getMovingAverage() / (float)USECS_PER_MSEC, 8, 'f', 3, noBreakingSpace). + arg((int)record.getCount(), 6, 10, noBreakingSpace); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine.toUtf8().constData(), color); + linesDisplayed++; + if (onlyDisplayTopTen && linesDisplayed == 10) { + break; + } + } + } + + + +*/ \ No newline at end of file diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 4c6d6ede4e..553b54d04b 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -13,47 +13,113 @@ #define hifi_Stats_h #include +#include +#include +#include #include -class Stats: public QObject { +#define STATS_PROPERTY(type, name, initialValue) \ + Q_PROPERTY(type name READ name NOTIFY name##Changed) \ +public: \ + type name() { return _##name; }; \ +private: \ + type _##name{ initialValue }; + + +class Stats : public QQuickItem { Q_OBJECT + HIFI_QML_DECL + Q_PROPERTY(bool expanded READ isExpanded NOTIFY expandedChanged) + STATS_PROPERTY(int, serverCount, 0) + STATS_PROPERTY(int, framerate, 0) + STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, packetInCount, 0) + STATS_PROPERTY(int, packetOutCount, 0) + STATS_PROPERTY(float, mbpsIn, 0) + STATS_PROPERTY(float, mbpsOut, 0) + STATS_PROPERTY(int, audioPing, 0) + STATS_PROPERTY(int, avatarPing, 0) + STATS_PROPERTY(int, entitiesPing, 0) + STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0) ) + STATS_PROPERTY(float, velocity, 0) + STATS_PROPERTY(float, yaw, 0) + STATS_PROPERTY(int, avatarMixerKbps, 0) + STATS_PROPERTY(int, avatarMixerPps, 0) + STATS_PROPERTY(int, audioMixerKbps, 0) + STATS_PROPERTY(int, audioMixerPps, 0) + STATS_PROPERTY(int, downloads, 0) + STATS_PROPERTY(int, downloadsPending, 0) + STATS_PROPERTY(int, triangles, 0) + STATS_PROPERTY(int, quads, 0) + STATS_PROPERTY(int, materialSwitches, 0) + STATS_PROPERTY(int, meshOpaque, 0) + STATS_PROPERTY(int, meshTranslucent, 0) + STATS_PROPERTY(int, opaqueConsidered, 0) + STATS_PROPERTY(int, opaqueOutOfView, 0) + STATS_PROPERTY(int, opaqueTooSmall, 0) + STATS_PROPERTY(int, translucentConsidered, 0) + STATS_PROPERTY(int, translucentOutOfView, 0) + STATS_PROPERTY(int, translucentTooSmall, 0) + STATS_PROPERTY(int, octreeElementsServer, 0) + STATS_PROPERTY(int, octreeElementsLocal, 0) public: static Stats* getInstance(); - Stats(); - - static void drawBackground(unsigned int rgba, int x, int y, int width, int height); - - void toggleExpanded(); - bool isExpanded() { return _expanded; } - - void checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset); - void resetWidth(int width, int horizontalOffset); - void display(const float* color, int horizontalOffset, float fps, int inPacketsPerSecond, int outPacketsPerSecond, - int inKbitsPerSecond, int outKbitsPerSecond, int voxelPacketsToProcess); + Stats(QQuickItem* parent = nullptr); bool includeTimingRecord(const QString& name); - - void setRenderDetails(const RenderDetails& details) { _renderDetails = details; } - + void setRenderDetails(const RenderDetails& details); + + void updateStats(); + + bool isExpanded() { return _expanded; } + + void setExpanded(bool expanded) { + if (expanded != _expanded) { + _expanded = expanded; + } + } + +signals: + void expandedChanged(); + void serverCountChanged(); + void framerateChanged(); + void avatarCountChanged(); + void packetInCountChanged(); + void packetOutCountChanged(); + void mbpsInChanged(); + void mbpsOutChanged(); + void audioPingChanged(); + void avatarPingChanged(); + void entitiesPingChanged(); + void positionChanged(); + void velocityChanged(); + void yawChanged(); + void avatarMixerKbpsChanged(); + void avatarMixerPpsChanged(); + void audioMixerKbpsChanged(); + void audioMixerPpsChanged(); + void downloadsChanged(); + void downloadsPendingChanged(); + void trianglesChanged(); + void quadsChanged(); + void materialSwitchesChanged(); + void meshOpaqueChanged(); + void meshTranslucentChanged(); + void opaqueConsideredChanged(); + void opaqueOutOfViewChanged(); + void opaqueTooSmallChanged(); + void translucentConsideredChanged(); + void translucentOutOfViewChanged(); + void translucentTooSmallChanged(); + void octreeElementsServerChanged(); + void octreeElementsLocalChanged(); + private: - static Stats* _sharedInstance; - - bool _expanded; - - int _recentMaxPackets; // recent max incoming voxel packets to process - bool _resetRecentMaxPacketsSoon; - - int _generalStatsWidth; - int _bandwidthStatsWidth; - int _pingStatsWidth; - int _geoStatsWidth; - int _octreeStatsWidth; - - int _lastHorizontalOffset; - - RenderDetails _renderDetails; + int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process + bool _resetRecentMaxPacketsSoon{ true }; + bool _expanded{ false }; }; #endif // hifi_Stats_h diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 9b497964e7..07a887be2c 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -91,6 +91,7 @@ void OffscreenQmlSurface::resize(const QSize& newSize) { // Qt bug in 5.4 forces this check of pixel ratio, // even though we're rendering offscreen. qreal pixelRatio = 1.0; + _qmlEngine->rootContext()->setContextProperty("surfaceSize", newSize); if (_renderControl && _renderControl->_renderWindow) { pixelRatio = _renderControl->_renderWindow->devicePixelRatio(); } else { @@ -111,7 +112,6 @@ void OffscreenQmlSurface::resize(const QSize& newSize) { _quickWindow->contentItem()->setSize(newSize); } - // Update our members if (_rootItem) { _rootItem->setSize(newSize); @@ -376,4 +376,4 @@ void OffscreenQmlSurface::setProxyWindow(QWindow* window) { QQuickWindow* OffscreenQmlSurface::getWindow() { return _quickWindow; -} \ No newline at end of file +} diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp new file mode 100644 index 0000000000..35b17b74a2 --- /dev/null +++ b/libraries/ui/src/Tooltip.cpp @@ -0,0 +1,51 @@ +// +// Tooltip.cpp +// +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Tooltip.h" +#include + +HIFI_QML_DEF(Tooltip) + +Tooltip::Tooltip(QQuickItem* parent) : QQuickItem(parent) { +} + +Tooltip::~Tooltip() { +} + +QString Tooltip::text() const { + return _text; +} + +void Tooltip::setText(const QString& arg) { + if (arg != _text) { + _text = arg; + emit textChanged(); + } +} + +void Tooltip::setVisible(bool visible) { + QQuickItem::setVisible(visible); +} + +QString Tooltip::showTip(const QString& text) { + const QString newTipId = QUuid().createUuid().toString(); + Tooltip::show([&](QQmlContext*, QObject* object) { + object->setObjectName(newTipId); + object->setProperty("text", text); + }); + return newTipId; +} + +void Tooltip::closeTip(const QString& tipId) { + auto rootItem = DependencyManager::get()->getRootItem(); + QQuickItem* that = rootItem->findChild(tipId); + if (that) { + that->deleteLater(); + } +} \ No newline at end of file diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h new file mode 100644 index 0000000000..7292fe9399 --- /dev/null +++ b/libraries/ui/src/Tooltip.h @@ -0,0 +1,45 @@ +// +// Tooltip.h +// +// Created by Bradley Austin Davis on 2015/04/14 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Tooltip_h +#define hifi_Tooltip_h + +#include "OffscreenQmlDialog.h" + +class Tooltip : public QQuickItem +{ + Q_OBJECT + HIFI_QML_DECL + +private: + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + +public: + Tooltip(QQuickItem* parent = 0); + virtual ~Tooltip(); + + QString text() const; + + static QString showTip(const QString& text); + static void closeTip(const QString& tipId); + +public slots: + virtual void setVisible(bool v); + void setText(const QString& arg); + +signals: + void textChanged(); + +private: + QString _text; +}; + +#endif // hifi_Tooltip_h