// // ApplicationOverlay.cpp // interface/src/ui/overlays // // Created by Benjamin Arnold on 5/27/14. // Copyright 2014 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 "InterfaceConfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" #include "audio/AudioScope.h" #include "Application.h" #include "ApplicationOverlay.h" #include "Util.h" #include "ui/Stats.h" #include "ui/AvatarInputs.h" const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; const int AUDIO_METER_GAP = 5; const int MUTE_ICON_PADDING = 10; const vec4 CONNECTION_STATUS_BORDER_COLOR{ 1.0f, 0.0f, 0.0f, 0.8f }; const float CONNECTION_STATUS_BORDER_LINE_WIDTH = 4.0f; static const float ORTHO_NEAR_CLIP = -10000; static const float ORTHO_FAR_CLIP = 10000; // TODO move somewhere useful static void fboViewport(QOpenGLFramebufferObject* fbo) { auto size = fbo->size(); glViewport(0, 0, size.width(), size.height()); } ApplicationOverlay::ApplicationOverlay() { auto geometryCache = DependencyManager::get(); _domainStatusBorder = geometryCache->allocateID(); _magnifierBorder = geometryCache->allocateID(); // Once we move UI rendering and screen rendering to different // threads, we need to use a sync object to deteremine when // the current UI texture is no longer being read from, and only // then release it back to the UI for re-use auto offscreenUi = DependencyManager::get(); connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) { auto offscreenUi = DependencyManager::get(); offscreenUi->lockTexture(textureId); assert(!glGetError()); std::swap(_uiTexture, textureId); if (textureId) { offscreenUi->releaseTexture(textureId); } }); } ApplicationOverlay::~ApplicationOverlay() { } // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { CHECK_GL_ERROR(); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); // TODO move to Application::idle()? Stats::getInstance()->updateStats(); AvatarInputs::getInstance()->update(); buildFramebufferObject(); // Execute the batch into our framebuffer _overlayFramebuffer->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); fboViewport(_overlayFramebuffer); // Now render the overlay components together into a single texture renderOverlays(renderArgs); renderStatsAndLogs(renderArgs); renderDomainConnectionStatusBorder(renderArgs); renderQmlUi(renderArgs); _overlayFramebuffer->release(); CHECK_GL_ERROR(); } void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { if (_uiTexture) { gpu::Batch batch; auto geometryCache = DependencyManager::get(); geometryCache->useSimpleDrawPipeline(batch); batch.setProjectionTransform(mat4()); batch.setModelTransform(mat4()); batch._glBindTexture(GL_TEXTURE_2D, _uiTexture); geometryCache->renderUnitQuad(batch, glm::vec4(1)); renderArgs->_context->syncCache(); renderArgs->_context->render(batch); } } void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { glm::vec2 size = qApp->getCanvasSize(); 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)); glMatrixMode(GL_MODELVIEW); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // give external parties a change to hook in emit qApp->renderingOverlay(); qApp->getOverlays().renderHUD(renderArgs); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); renderArgs->_context->syncCache(); fboViewport(_overlayFramebuffer); } void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) { } void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { } void ApplicationOverlay::renderStatsAndLogs(RenderArgs* renderArgs) { // Display stats and log text onscreen // Determine whether to compute timing details /* // Show on-screen msec timer if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) { auto canvasSize = qApp->getCanvasSize(); quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); QString frameTimer = QString("%1\n").arg((int)(mSecsNow % 1000)); int timerBottom = (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) ? 80 : 20; 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); fboViewport(_overlayFramebuffer); */ } void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderArgs) { auto geometryCache = DependencyManager::get(); static std::once_flag once; std::call_once(once, [&] { QVector points; static const float B = 0.99f; points.push_back(vec2(-B)); points.push_back(vec2(B, -B)); points.push_back(vec2(B)); points.push_back(vec2(-B, B)); points.push_back(vec2(-B)); 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.setProjectionTransform(mat4()); batch.setModelTransform(mat4()); batch.setUniformTexture(0, DependencyManager::get()->getWhiteTexture()); batch._glLineWidth(CONNECTION_STATUS_BORDER_LINE_WIDTH); // TODO animate the disconnect border for some excitement while not connected? //double usecs = usecTimestampNow(); //double secs = usecs / 1000000.0; //float scaleAmount = 1.0f + (0.01f * sin(secs * 5.0f)); //batch.setModelTransform(glm::scale(mat4(), vec3(scaleAmount))); geometryCache->renderVertices(batch, gpu::LINE_STRIP, _domainStatusBorder); renderArgs->_context->syncCache(); renderArgs->_context->render(batch); } } GLuint ApplicationOverlay::getOverlayTexture() { if (!_overlayFramebuffer) { return 0; } return _overlayFramebuffer->texture(); } void ApplicationOverlay::buildFramebufferObject() { QSize fboSize = qApp->getDeviceSize(); if (_overlayFramebuffer && fboSize == _overlayFramebuffer->size()) { // Already built return; } if (_overlayFramebuffer) { delete _overlayFramebuffer; } _overlayFramebuffer = new QOpenGLFramebufferObject(fboSize, QOpenGLFramebufferObject::Depth); glBindTexture(GL_TEXTURE_2D, getOverlayTexture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GLfloat borderColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); glBindTexture(GL_TEXTURE_2D, 0); }