diff --git a/.gitignore b/.gitignore index cebf9eb113..9dc509bc19 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ CMakeFiles/ CMakeScripts/ cmake_install.cmake build/ +Makefile # Xcode *.xcodeproj diff --git a/CMakeLists.txt b/CMakeLists.txt index 80c5de1df6..a7265bb3fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,14 @@ cmake_minimum_required(VERSION 2.8) project(hifi) +message($ENV{QT_CMAKE_PREFIX_PATH}) +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{QT_CMAKE_PREFIX_PATH}) + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) + add_subdirectory(animation-server) add_subdirectory(assignment-server) add_subdirectory(avatar-mixer) diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index c6a84e1838..79bf16e1a0 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -8,9 +8,8 @@ MACRO(SETUP_HIFI_LIBRARY TARGET) # create a library and set the property so it can be referenced later add_library(${TARGET} ${LIB_SRCS}) - find_package(Qt5 REQUIRED QtCore) - include(${QT_USE_FILE}) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") + find_package(Qt5Core REQUIRED) + qt5_use_modules(${TARGET} Core) target_link_libraries(${TARGET} ${QT_LIBRARIES}) ENDMACRO(SETUP_HIFI_LIBRARY _target) \ No newline at end of file diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 80ced2ab73..455ca89701 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -8,9 +8,8 @@ MACRO(SETUP_HIFI_PROJECT TARGET INCLUDE_QT) add_executable(${TARGET} ${TARGET_SRCS}) IF (${INCLUDE_QT}) - find_package(Qt5 REQUIRED QtCore) - include(${QT_USE_FILE}) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") + find_package(Qt5Core REQUIRED) + qt5_use_modules(${TARGET} Core) ENDIF() target_link_libraries(${TARGET} ${QT_LIBRARIES}) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index e354bb4bd2..8f6c35fe50 100755 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -67,21 +67,22 @@ if (APPLE) endif (APPLE) -find_package(Qt5 REQUIRED QtCore QtGui QtNetwork QtOpenGL QtWebKit QtSvg) -include(${QT_USE_FILE}) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${QT_QTGUI_INCLUDE_DIR}") +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Network REQUIRED) +find_package(Qt5OpenGL REQUIRED) +find_package(Qt5WebKit REQUIRED) +find_package(Qt5Svg REQUIRED) set(QUAZIP_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external/quazip) add_subdirectory(external/fervor/) include_directories(external/fervor/) -# run qt moc on qt-enabled headers -qt5_wrap_cpp(INTERFACE_SRCS src/Application.h src/Webcam.h src/avatar/AvatarVoxelSystem.h - src/avatar/Face.h src/ui/BandwidthDialog.h) - # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS}) +qt5_use_modules(${TARGET_NAME} Core Gui Network OpenGL WebKit Svg) + # link in the hifi shared library include(${MACRO_DIR}/LinkHifiLibrary.cmake) diff --git a/interface/external/fervor/CMakeLists.txt b/interface/external/fervor/CMakeLists.txt index 9cba3816f8..3434615aed 100644 --- a/interface/external/fervor/CMakeLists.txt +++ b/interface/external/fervor/CMakeLists.txt @@ -1,21 +1,25 @@ cmake_minimum_required(VERSION 2.8) project(Fervor) -find_package(Qt5 REQUIRED) + +find_package(Qt5Core REQUIRED) +find_package(Qt5Network REQUIRED) +find_package(Qt5Widgets REQUIRED) add_definitions(-DFV_GUI) file(GLOB FERVOR_SOURCES *.cpp) file(GLOB FERVOR_HEADERS *.h) +file(GLOB FERVOR_UI *.ui) + +qt5_wrap_ui(FERVOR_WRAPPED_UI ${FERVOR_UI}) + LIST(GET FERVOR_HEADERS 1 FIRST_HEADER) GET_FILENAME_COMPONENT(HEADER_PATH ${FIRST_HEADER} PATH) list(REMOVE_ITEM FERVOR_HEADERS ${HEADER_PATH}/fvversioncomparator.h) file(GLOB FERVOR_UI *.ui) -qt5_wrap_ui(FERVOR_WRAPPED_UI ${FERVOR_UI}) -qt5_wrap_cpp(FERVOR_MOC_SOURCES ${FERVOR_HEADERS}) - set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") find_package(Quazip REQUIRED) @@ -25,4 +29,6 @@ include_directories( ) add_library(fervor ${FERVOR_SOURCES} ${FERVOR_HEADERS} ${FERVOR_MOC_SOURCES} ${FERVOR_WRAPPED_UI}) -target_link_libraries(fervor ${QUAZIP_LIBRARIES}) \ No newline at end of file +target_link_libraries(fervor ${QUAZIP_LIBRARIES}) + +qt5_use_modules(fervor Core Network Widgets) diff --git a/interface/external/fervor/fvplatform.h b/interface/external/fervor/fvplatform.h index a527518097..a98f04a1ce 100755 --- a/interface/external/fervor/fvplatform.h +++ b/interface/external/fervor/fvplatform.h @@ -1,7 +1,7 @@ #ifndef FVPLATFORM_H #define FVPLATFORM_H -#include +#include class FvPlatform : public QObject { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9571b44839..958affebb5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -56,6 +56,8 @@ #include #include +#include + #include "Application.h" #include "InterfaceConfig.h" #include "LogDisplay.h" @@ -164,9 +166,9 @@ void GLCanvas::wheelEvent(QWheelEvent* event) { Application::getInstance()->wheelEvent(event); } -void messageHandler(QtMsgType type, const char* message) { - fprintf(stdout, "%s", message); - LogDisplay::instance.addMessage(message); +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) { + fprintf(stdout, "%s", message.toLocal8Bit().constData()); + LogDisplay::instance.addMessage(message.toLocal8Bit().constData()); } Application::Application(int& argc, char** argv, timeval &startup_time) : @@ -174,6 +176,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), _bandwidthDialog(NULL), + _voxelStatsDialog(NULL), _displayLevels(false), _frameCount(0), _fps(120.0f), @@ -220,7 +223,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _applicationStartupTime = startup_time; _window->setWindowTitle("Interface"); - qInstallMsgHandler(messageHandler); + qInstallMessageHandler(messageHandler); unsigned int listenPort = 0; // bind to an ephemeral port by default const char** constArgv = const_cast(argv); @@ -1147,6 +1150,21 @@ void Application::bandwidthDetailsClosed() { delete dlg; } +void Application::voxelStatsDetails() { + if (!_voxelStatsDialog) { + _voxelStatsDialog = new VoxelStatsDialog(_glWidget, &_voxelSceneStats); + connect(_voxelStatsDialog, SIGNAL(closed()), SLOT(voxelStatsDetailsClosed())); + _voxelStatsDialog->show(); + } + _voxelStatsDialog->raise(); +} + +void Application::voxelStatsDetailsClosed() { + QDialog* dlg = _voxelStatsDialog; + _voxelStatsDialog = NULL; + delete dlg; +} + void Application::editPreferences() { QDialog dialog(_glWidget); dialog.setWindowTitle("Interface Preferences"); @@ -1199,7 +1217,7 @@ void Application::editPreferences() { if (domainServerHostname->text().size() > 0) { // the user input a new hostname, use that - newHostname = domainServerHostname->text().toAscii(); + newHostname = domainServerHostname->text().toLocal8Bit(); } else { // the user left the field blank, use the default hostname newHostname = QByteArray(DEFAULT_DOMAIN_HOSTNAME); @@ -1495,12 +1513,12 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) { } void Application::exportVoxels() { - QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString suggestedName = desktopLocation.append("/voxels.svo"); QString fileNameString = QFileDialog::getSaveFileName(_glWidget, tr("Export Voxels"), suggestedName, tr("Sparse Voxel Octree Files (*.svo)")); - QByteArray fileNameAscii = fileNameString.toAscii(); + QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); VoxelNode* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode) { @@ -1515,11 +1533,11 @@ void Application::exportVoxels() { const char* IMPORT_FILE_TYPES = "Sparse Voxel Octree Files, Square PNG, Schematic Files (*.svo *.png *.schematic)"; void Application::importVoxelsToClipboard() { - QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Import Voxels to Clipboard"), desktopLocation, tr(IMPORT_FILE_TYPES)); - QByteArray fileNameAscii = fileNameString.toAscii(); + QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); _clipboardTree.eraseAllVoxels(); @@ -1549,11 +1567,11 @@ void Application::importVoxelsToClipboard() { } void Application::importVoxels() { - QString desktopLocation = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation); + QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Import Voxels"), desktopLocation, tr(IMPORT_FILE_TYPES)); - QByteArray fileNameAscii = fileNameString.toAscii(); + QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); VoxelTree importVoxels; @@ -1751,6 +1769,7 @@ void Application::initMenu() { (_bandwidthDisplayOn = toolsMenu->addAction("Bandwidth Display"))->setCheckable(true); _bandwidthDisplayOn->setChecked(true); toolsMenu->addAction("Bandwidth Details", this, SLOT(bandwidthDetails())); + toolsMenu->addAction("Voxel Stats Details", this, SLOT(voxelStatsDetails())); QMenu* voxelMenu = menuBar->addMenu("Voxels"); @@ -1986,7 +2005,7 @@ void Application::update(float deltaTime) { // Set where I am looking based on my mouse ray (so that other people can see) glm::vec3 eyePosition; - if (_isLookingAtOtherAvatar = isLookingAtOtherAvatar(mouseRayOrigin, mouseRayDirection, eyePosition)) { + if ((_isLookingAtOtherAvatar = isLookingAtOtherAvatar(mouseRayOrigin, mouseRayDirection, eyePosition))) { // If the mouse is over another avatar's head... glm::vec3 myLookAtFromMouse(eyePosition); _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); @@ -2191,6 +2210,9 @@ void Application::update(float deltaTime) { if (_bandwidthDialog) { _bandwidthDialog->update(); } + if (_voxelStatsDialog) { + _voxelStatsDialog->update(); + } // Update audio stats for procedural sounds #ifndef _WIN32 @@ -2248,7 +2270,7 @@ void Application::updateAvatar(float deltaTime) { _viewFrustum.computePickRay(MIDPOINT_OF_SCREEN, MIDPOINT_OF_SCREEN, screenCenterRayOrigin, screenCenterRayDirection); glm::vec3 eyePosition; - if (_isLookingAtOtherAvatar = isLookingAtOtherAvatar(screenCenterRayOrigin, screenCenterRayDirection, eyePosition)) { + if ((_isLookingAtOtherAvatar = isLookingAtOtherAvatar(screenCenterRayOrigin, screenCenterRayDirection, eyePosition))) { glm::vec3 myLookAtFromMouse(eyePosition); _myAvatar.getHead().setLookAtPosition(myLookAtFromMouse); } @@ -2843,27 +2865,19 @@ void Application::displayStats() { drawtext(10, statsVerticalOffset + 230, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); - voxelStats << "Voxels Created: " << _voxels.getVoxelsCreated() / 1000.f << "K (" << _voxels.getVoxelsCreatedPerSecondAverage() / 1000.f - << "Kps) "; + char* voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_VOXELS); + voxelStats << "Voxels Sent from Server: " << voxelDetails; drawtext(10, statsVerticalOffset + 250, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - voxelStats << "Voxels Colored: " << _voxels.getVoxelsColored() / 1000.f << "K (" << _voxels.getVoxelsColoredPerSecondAverage() / 1000.f - << "Kps) "; - drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); - - voxelStats.str(""); - voxelStats << "Voxel Bits Read: " << _voxels.getVoxelsBytesRead() * 8.f / 1000000.f - << "M (" << _voxels.getVoxelsBytesReadPerSecondAverage() * 8.f / 1000000.f << " Mbps)"; - drawtext(10, statsVerticalOffset + 290,0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); voxelStats.str(""); - float voxelsBytesPerColored = _voxels.getVoxelsColored() - ? ((float) _voxels.getVoxelsBytesRead() / _voxels.getVoxelsColored()) - : 0; - - voxelStats << "Voxels Bits per Colored: " << voxelsBytesPerColored * 8; - drawtext(10, statsVerticalOffset + 310, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ELAPSED); + voxelStats << "Scene Send Time from Server: " << voxelDetails; + drawtext(10, statsVerticalOffset + 270, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); + + voxelStats.str(""); + voxelDetails = _voxelSceneStats.getItemValue(VoxelSceneStats::ITEM_ENCODE); + voxelStats << "Encode Time on Server: " << voxelDetails; + drawtext(10, statsVerticalOffset + 290, 0.10f, 0, 1.0, 0, (char *)voxelStats.str().c_str()); Node *avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); char avatarMixerStats[200]; @@ -3418,16 +3432,37 @@ void* Application::networkReceive(void* args) { case PACKET_TYPE_VOXEL_DATA_MONOCHROME: case PACKET_TYPE_Z_COMMAND: case PACKET_TYPE_ERASE_VOXEL: + case PACKET_TYPE_VOXEL_STATS: case PACKET_TYPE_ENVIRONMENT_DATA: { + + unsigned char* messageData = app->_incomingPacket; + ssize_t messageLength = bytesReceived; + + // note: PACKET_TYPE_VOXEL_STATS can have PACKET_TYPE_VOXEL_DATA or PACKET_TYPE_VOXEL_DATA_MONOCHROME + // immediately following them inside the same packet. So, we process the PACKET_TYPE_VOXEL_STATS first + // then process any remaining bytes as if it was another packet + if (messageData[0] == PACKET_TYPE_VOXEL_STATS) { + int statsMessageLength = app->_voxelSceneStats.unpackFromMessage(messageData, messageLength); + if (messageLength > statsMessageLength) { + messageData += statsMessageLength; + messageLength -= statsMessageLength; + if (!packetVersionMatch(messageData)) { + break; // bail since piggyback data doesn't match our versioning + } + } else { + break; // bail since no piggyback data + } + } // fall through to piggyback message + if (app->_renderVoxels->isChecked()) { Node* voxelServer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_VOXEL_SERVER); if (voxelServer && socketMatch(voxelServer->getActiveSocket(), &senderAddress)) { voxelServer->lock(); - if (app->_incomingPacket[0] == PACKET_TYPE_ENVIRONMENT_DATA) { - app->_environment.parseData(&senderAddress, app->_incomingPacket, bytesReceived); + if (messageData[0] == PACKET_TYPE_ENVIRONMENT_DATA) { + app->_environment.parseData(&senderAddress, messageData, messageLength); } else { - app->_voxels.parseData(app->_incomingPacket, bytesReceived); + app->_voxels.parseData(messageData, messageLength); } voxelServer->unlock(); @@ -3549,7 +3584,7 @@ void Application::saveSettings(QSettings* settings) { } void Application::importSettings() { - QString locationDir(QDesktopServices::displayName(QDesktopServices::DesktopLocation)); + QString locationDir(QStandardPaths::displayName(QStandardPaths::DesktopLocation)); QString fileName = QFileDialog::getOpenFileName(_window, tr("Open .ini config file"), locationDir, @@ -3561,7 +3596,7 @@ void Application::importSettings() { } void Application::exportSettings() { - QString locationDir(QDesktopServices::displayName(QDesktopServices::DesktopLocation)); + QString locationDir(QStandardPaths::displayName(QStandardPaths::DesktopLocation)); QString fileName = QFileDialog::getSaveFileName(_window, tr("Save .ini config file"), locationDir, diff --git a/interface/src/Application.h b/interface/src/Application.h index d29328c0b5..1c3d61ffc9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -43,6 +43,7 @@ #include "avatar/HandControl.h" #include "ui/BandwidthDialog.h" #include "ui/ChatEntry.h" +#include "ui/VoxelStatsDialog.h" class QAction; class QActionGroup; @@ -120,6 +121,9 @@ private slots: void bandwidthDetails(); void editPreferences(); void bandwidthDetailsClosed(); + + void voxelStatsDetails(); + void voxelStatsDetailsClosed(); void pair(); @@ -284,6 +288,7 @@ private: BandwidthMeter _bandwidthMeter; BandwidthDialog* _bandwidthDialog; + VoxelStatsDialog* _voxelStatsDialog; SerialInterface _serialHeadSensor; QNetworkAccessManager* _networkAccessManager; @@ -416,6 +421,8 @@ private: ToolsPalette _palette; Swatch _swatch; + + VoxelSceneStats _voxelSceneStats; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/avatar/AvatarVoxelSystem.cpp b/interface/src/avatar/AvatarVoxelSystem.cpp index c85ea1a343..a9f6b31072 100644 --- a/interface/src/avatar/AvatarVoxelSystem.cpp +++ b/interface/src/avatar/AvatarVoxelSystem.cpp @@ -121,7 +121,7 @@ void AvatarVoxelSystem::setVoxelURL(const QUrl& url) { // handle "file://" urls... if (url.isLocalFile()) { QString pathString = url.path(); - QByteArray pathAsAscii = pathString.toAscii(); + QByteArray pathAsAscii = pathString.toLocal8Bit(); const char* path = pathAsAscii.data(); readFromSVOFile(path); return; @@ -255,7 +255,7 @@ void AvatarVoxelSystem::handleVoxelDownloadProgress(qint64 bytesReceived, qint64 } void AvatarVoxelSystem::handleVoxelReplyError() { - qDebug("%s\n", _voxelReply->errorString().toAscii().constData()); + qDebug("%s\n", _voxelReply->errorString().toLocal8Bit().constData()); _voxelReply->disconnect(this); _voxelReply->deleteLater(); diff --git a/interface/src/ui/ChatEntry.cpp b/interface/src/ui/ChatEntry.cpp index 588c394eb1..f9ea4eb1f6 100644 --- a/interface/src/ui/ChatEntry.cpp +++ b/interface/src/ui/ChatEntry.cpp @@ -66,7 +66,7 @@ bool ChatEntry::keyPressEvent(QKeyEvent* event) { return true; } if (_contents.size() < MAX_CONTENT_LENGTH) { - _contents.insert(_cursorPos, 1, text.at(0).toAscii()); + _contents.insert(_cursorPos, 1, text.at(0).toLatin1()); _cursorPos++; } return true; diff --git a/interface/src/ui/VoxelStatsDialog.cpp b/interface/src/ui/VoxelStatsDialog.cpp new file mode 100644 index 0000000000..bfe93ec119 --- /dev/null +++ b/interface/src/ui/VoxelStatsDialog.cpp @@ -0,0 +1,79 @@ +// +// VoxelStatsDialog.cpp +// interface +// +// Created by Brad Hefta-Gaub on 7/19/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include + +#include +#include + +#include + +#include "ui/VoxelStatsDialog.h" + + +VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, VoxelSceneStats* model) : + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint), + _model(model) { + + char strBuf[64]; + + this->setWindowTitle("Voxel Statistics"); + + // Create layouter + QFormLayout* form = new QFormLayout(); + this->QDialog::setLayout(form); + + // Setup labels + for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; ++i) { + VoxelSceneStats::ItemInfo& itemInfo = _model->getItemInfo(i); + 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 = itemInfo.colorRGBA >> 8; + const unsigned colorpart1 = 0xfefefeu; + const unsigned colorpart2 = 0xf8f8f8; + rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3); + palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb)); + label->setPalette(palette); + + // This is my hackery attempt at making QDialog auto-size to a width that will hold our info. It kinda works. + label->setText("123456789012345678901234567890123456789012345678901234567890"); + + snprintf(strBuf, sizeof(strBuf), " %s:", itemInfo.caption); + form->addRow(strBuf, label); + } +} + +void VoxelStatsDialog::paintEvent(QPaintEvent* event) { + + // Update labels + char strBuf[256]; + for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; ++i) { + QLabel* label = _labels[i]; + snprintf(strBuf, sizeof(strBuf), "%s", _model->getItemValue(i)); + label->setText(strBuf); + } + + this->QDialog::paintEvent(event); + this->setFixedSize(this->width(), this->height()); +} + +void VoxelStatsDialog::reject() { + // Just regularly close upon ESC + this->QDialog::close(); +} + +void VoxelStatsDialog::closeEvent(QCloseEvent* event) { + this->QDialog::closeEvent(event); + emit closed(); +} + + diff --git a/interface/src/ui/VoxelStatsDialog.h b/interface/src/ui/VoxelStatsDialog.h new file mode 100644 index 0000000000..eab5b6a45a --- /dev/null +++ b/interface/src/ui/VoxelStatsDialog.h @@ -0,0 +1,42 @@ +// +// VoxelStatsDialog.h +// interface +// +// Created by Brad Hefta-Gaub on 7/19/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__VoxelStatsDialog__ +#define __hifi__VoxelStatsDialog__ + +#include +#include + +#include + +class VoxelStatsDialog : public QDialog { + Q_OBJECT +public: + // Sets up the UI + VoxelStatsDialog(QWidget* parent, VoxelSceneStats* 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: + QLabel* _labels[VoxelSceneStats::ITEM_COUNT]; + VoxelSceneStats* _model; +}; + +#endif /* defined(__interface__VoxelStatsDialog__) */ + diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index 5827f94ebb..0570c30725 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -8,13 +8,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm set(TARGET_NAME avatars) -find_package(Qt5 REQUIRED QtCore) -include(${QT_USE_FILE}) -qt5_wrap_cpp(WRAPPED_SRCS src/AvatarData.h) +find_package(Qt5Core REQUIRED) include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME}) +qt5_use_modules(${TARGET_NAME} Core) + include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index a162815097..99302b955f 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -6,13 +6,13 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros) set(TARGET_NAME shared) project(${TARGET_NAME}) -find_package(Qt5 REQUIRED QtCore) -include(${QT_USE_FILE}) -qt5_wrap_cpp(WRAPPED_SRCS src/NodeData.h) +find_package(Qt5Core REQUIRED) include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME}) +qt5_use_modules(${TARGET_NAME} Core) + set(EXTERNAL_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external) if (WIN32) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 202ff09ce8..cad9a4c797 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -522,7 +522,7 @@ void NodeList::loadData(QSettings *settings) { if (domainServerHostname.size() > 0) { memset(_domainHostname, 0, MAX_HOSTNAME_BYTES); - memcpy(_domainHostname, domainServerHostname.toAscii().constData(), domainServerHostname.size()); + memcpy(_domainHostname, domainServerHostname.toLocal8Bit().constData(), domainServerHostname.size()); } settings->endGroup(); diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 8bd90f3b63..24d755652c 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -36,6 +36,7 @@ const PACKET_TYPE PACKET_TYPE_DOMAIN_LIST_REQUEST = 'L'; const PACKET_TYPE PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY = 'C'; const PACKET_TYPE PACKET_TYPE_REQUEST_ASSIGNMENT = 'r'; const PACKET_TYPE PACKET_TYPE_SEND_ASSIGNMENT = 's'; +const PACKET_TYPE PACKET_TYPE_VOXEL_STATS = '#'; typedef char PACKET_VERSION; diff --git a/libraries/voxels/src/VoxelNode.cpp b/libraries/voxels/src/VoxelNode.cpp index 1268449ac6..34eb2b2261 100644 --- a/libraries/voxels/src/VoxelNode.cpp +++ b/libraries/voxels/src/VoxelNode.cpp @@ -44,6 +44,8 @@ void VoxelNode::init(unsigned char * octalCode) { _children[i] = NULL; } _childCount = 0; + _subtreeNodeCount = 1; // that's me + _subtreeLeafNodeCount = 0; // that's me _glBufferIndex = GLBUFFER_INDEX_UNKNOWN; _isDirty = true; @@ -79,6 +81,24 @@ void VoxelNode::handleSubtreeChanged(VoxelTree* myTree) { if (myTree->getShouldReaverage()) { setColorFromAverageOfChildren(); } + + recalculateSubTreeNodeCount(); +} + +void VoxelNode::recalculateSubTreeNodeCount() { + // Assuming the tree below me as changed, I need to recalculate my node count + _subtreeNodeCount = 1; // that's me + if (isLeaf()) { + _subtreeLeafNodeCount = 1; + } else { + _subtreeLeafNodeCount = 0; + for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { + if (_children[i]) { + _subtreeNodeCount += _children[i]->_subtreeNodeCount; + _subtreeLeafNodeCount += _children[i]->_subtreeLeafNodeCount; + } + } + } } diff --git a/libraries/voxels/src/VoxelNode.h b/libraries/voxels/src/VoxelNode.h index 431592c2f9..c84ce3c7d6 100644 --- a/libraries/voxels/src/VoxelNode.h +++ b/libraries/voxels/src/VoxelNode.h @@ -107,6 +107,12 @@ public: static int addDeleteHook(VoxelNodeDeleteHook hook, void* extraData = NULL); static void removeDeleteHook(int hookID); + + void recalculateSubTreeNodeCount(); + unsigned long getSubTreeNodeCount() const { return _subtreeNodeCount; }; + unsigned long getSubTreeInternalNodeCount() const { return _subtreeNodeCount - _subtreeLeafNodeCount; }; + unsigned long getSubTreeLeafNodeCount() const { return _subtreeLeafNodeCount; }; + private: void calculateAABox(); void init(unsigned char * octalCode); @@ -126,6 +132,8 @@ private: unsigned char* _octalCode; VoxelNode* _children[8]; int _childCount; + unsigned long _subtreeNodeCount; + unsigned long _subtreeLeafNodeCount; float _density; // If leaf: density = 1, if internal node: 0-1 density of voxels inside static VoxelNodeDeleteHook _hooks[VOXEL_NODE_MAX_DELETE_HOOKS]; diff --git a/libraries/voxels/src/VoxelSceneStats.cpp b/libraries/voxels/src/VoxelSceneStats.cpp new file mode 100644 index 0000000000..b226886a25 --- /dev/null +++ b/libraries/voxels/src/VoxelSceneStats.cpp @@ -0,0 +1,556 @@ +// +// VoxelSceneStats.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 7/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#include +#include + +#include "VoxelNode.h" +#include "VoxelSceneStats.h" + + +const int samples = 100; +VoxelSceneStats::VoxelSceneStats() : + _elapsedAverage(samples), + _bitsPerVoxelAverage(samples) +{ + reset(); + _isReadyToSend = false; + _isStarted = false; +} + +void VoxelSceneStats::sceneStarted(bool isFullScene, bool isMoving, VoxelNode* root) { + reset(); // resets packet and voxel stats + _isStarted = true; + _start = usecTimestampNow(); + _totalVoxels = root->getSubTreeNodeCount(); + _totalInternal = root->getSubTreeInternalNodeCount(); + _totalLeaves = root->getSubTreeLeafNodeCount(); + + _isFullScene = isFullScene; + _isMoving = isMoving; +} + +void VoxelSceneStats::sceneCompleted() { + if (_isStarted) { + _end = usecTimestampNow(); + _elapsed = _end - _start; + _elapsedAverage.updateAverage((float)_elapsed); + + _statsMessageLength = packIntoMessage(_statsMessage, sizeof(_statsMessage)); + _isReadyToSend = true; + _isStarted = false; + } +} + +void VoxelSceneStats::encodeStarted() { + _encodeStart = usecTimestampNow(); +} + +void VoxelSceneStats::encodeStopped() { + _totalEncodeTime += (usecTimestampNow() - _encodeStart); +} + +void VoxelSceneStats::reset() { + _totalEncodeTime = 0; + _encodeStart = 0; + + _packets = 0; + _bytes = 0; + _passes = 0; + + _totalVoxels = 0; + _totalInternal = 0; + _totalLeaves = 0; + + _traversed = 0; + _internal = 0; + _leaves = 0; + + _skippedDistance = 0; + _internalSkippedDistance = 0; + _leavesSkippedDistance = 0; + + _skippedOutOfView = 0; + _internalSkippedOutOfView = 0; + _leavesSkippedOutOfView = 0; + + _skippedWasInView = 0; + _internalSkippedWasInView = 0; + _leavesSkippedWasInView = 0; + + _skippedNoChange = 0; + _internalSkippedNoChange = 0; + _leavesSkippedNoChange = 0; + + _skippedOccluded = 0; + _internalSkippedOccluded = 0; + _leavesSkippedOccluded = 0; + + _colorSent = 0; + _internalColorSent = 0; + _leavesColorSent = 0; + + _didntFit = 0; + _internalDidntFit = 0; + _leavesDidntFit = 0; + + _colorBitsWritten = 0; + _existsBitsWritten = 0; + _existsInPacketBitsWritten = 0; + _treesRemoved = 0; +} + +void VoxelSceneStats::packetSent(int bytes) { + _packets++; + _bytes += bytes; +} + +void VoxelSceneStats::traversed(const VoxelNode* node) { + _traversed++; + if (node->isLeaf()) { + _leaves++; + } else { + _internal++; + } +} + +void VoxelSceneStats::skippedDistance(const VoxelNode* node) { + _skippedDistance++; + if (node->isLeaf()) { + _leavesSkippedDistance++; + } else { + _internalSkippedDistance++; + } +} + +void VoxelSceneStats::skippedOutOfView(const VoxelNode* node) { + _skippedOutOfView++; + if (node->isLeaf()) { + _leavesSkippedOutOfView++; + } else { + _internalSkippedOutOfView++; + } +} + +void VoxelSceneStats::skippedWasInView(const VoxelNode* node) { + _skippedWasInView++; + if (node->isLeaf()) { + _leavesSkippedWasInView++; + } else { + _internalSkippedWasInView++; + } +} + +void VoxelSceneStats::skippedNoChange(const VoxelNode* node) { + _skippedNoChange++; + if (node->isLeaf()) { + _leavesSkippedNoChange++; + } else { + _internalSkippedNoChange++; + } +} + +void VoxelSceneStats::skippedOccluded(const VoxelNode* node) { + _skippedOccluded++; + if (node->isLeaf()) { + _leavesSkippedOccluded++; + } else { + _internalSkippedOccluded++; + } +} + +void VoxelSceneStats::colorSent(const VoxelNode* node) { + _colorSent++; + if (node->isLeaf()) { + _leavesColorSent++; + } else { + _internalColorSent++; + } +} + +void VoxelSceneStats::didntFit(const VoxelNode* node) { + _didntFit++; + if (node->isLeaf()) { + _leavesDidntFit++; + } else { + _internalDidntFit++; + } +} + +void VoxelSceneStats::colorBitsWritten() { + _colorBitsWritten++; +} + +void VoxelSceneStats::existsBitsWritten() { + _existsBitsWritten++; +} + +void VoxelSceneStats::existsInPacketBitsWritten() { + _existsInPacketBitsWritten++; +} + +void VoxelSceneStats::childBitsRemoved(bool includesExistsBits, bool includesColors) { + _existsInPacketBitsWritten--; + if (includesExistsBits) { + _existsBitsWritten--; + } + if (includesColors) { + _colorBitsWritten--; + } + _treesRemoved++; +} + +int VoxelSceneStats::packIntoMessage(unsigned char* destinationBuffer, int availableBytes) { + unsigned char* bufferStart = destinationBuffer; + + int headerLength = populateTypeAndVersion(destinationBuffer, PACKET_TYPE_VOXEL_STATS); + destinationBuffer += headerLength; + + memcpy(destinationBuffer, &_start, sizeof(_start)); + destinationBuffer += sizeof(_start); + memcpy(destinationBuffer, &_end, sizeof(_end)); + destinationBuffer += sizeof(_end); + memcpy(destinationBuffer, &_elapsed, sizeof(_elapsed)); + destinationBuffer += sizeof(_elapsed); + memcpy(destinationBuffer, &_totalEncodeTime, sizeof(_totalEncodeTime)); + destinationBuffer += sizeof(_totalEncodeTime); + memcpy(destinationBuffer, &_isFullScene, sizeof(_isFullScene)); + destinationBuffer += sizeof(_isFullScene); + memcpy(destinationBuffer, &_isMoving, sizeof(_isMoving)); + destinationBuffer += sizeof(_isMoving); + memcpy(destinationBuffer, &_packets, sizeof(_packets)); + destinationBuffer += sizeof(_packets); + memcpy(destinationBuffer, &_bytes, sizeof(_bytes)); + destinationBuffer += sizeof(_bytes); + + memcpy(destinationBuffer, &_totalInternal, sizeof(_totalInternal)); + destinationBuffer += sizeof(_totalInternal); + memcpy(destinationBuffer, &_totalLeaves, sizeof(_totalLeaves)); + destinationBuffer += sizeof(_totalLeaves); + memcpy(destinationBuffer, &_internal, sizeof(_internal)); + destinationBuffer += sizeof(_internal); + memcpy(destinationBuffer, &_leaves, sizeof(_leaves)); + destinationBuffer += sizeof(_leaves); + memcpy(destinationBuffer, &_internalSkippedDistance, sizeof(_internalSkippedDistance)); + destinationBuffer += sizeof(_internalSkippedDistance); + memcpy(destinationBuffer, &_leavesSkippedDistance, sizeof(_leavesSkippedDistance)); + destinationBuffer += sizeof(_leavesSkippedDistance); + memcpy(destinationBuffer, &_internalSkippedOutOfView, sizeof(_internalSkippedOutOfView)); + destinationBuffer += sizeof(_internalSkippedOutOfView); + memcpy(destinationBuffer, &_leavesSkippedOutOfView, sizeof(_leavesSkippedOutOfView)); + destinationBuffer += sizeof(_leavesSkippedOutOfView); + memcpy(destinationBuffer, &_internalSkippedWasInView, sizeof(_internalSkippedWasInView)); + destinationBuffer += sizeof(_internalSkippedWasInView); + memcpy(destinationBuffer, &_leavesSkippedWasInView, sizeof(_leavesSkippedWasInView)); + destinationBuffer += sizeof(_leavesSkippedWasInView); + memcpy(destinationBuffer, &_internalSkippedNoChange, sizeof(_internalSkippedNoChange)); + destinationBuffer += sizeof(_internalSkippedNoChange); + memcpy(destinationBuffer, &_leavesSkippedNoChange, sizeof(_leavesSkippedNoChange)); + destinationBuffer += sizeof(_leavesSkippedNoChange); + memcpy(destinationBuffer, &_internalSkippedOccluded, sizeof(_internalSkippedOccluded)); + destinationBuffer += sizeof(_internalSkippedOccluded); + memcpy(destinationBuffer, &_leavesSkippedOccluded, sizeof(_leavesSkippedOccluded)); + destinationBuffer += sizeof(_leavesSkippedOccluded); + memcpy(destinationBuffer, &_internalColorSent, sizeof(_internalColorSent)); + destinationBuffer += sizeof(_internalColorSent); + memcpy(destinationBuffer, &_leavesColorSent, sizeof(_leavesColorSent)); + destinationBuffer += sizeof(_leavesColorSent); + memcpy(destinationBuffer, &_internalDidntFit, sizeof(_internalDidntFit)); + destinationBuffer += sizeof(_internalDidntFit); + memcpy(destinationBuffer, &_leavesDidntFit, sizeof(_leavesDidntFit)); + destinationBuffer += sizeof(_leavesDidntFit); + memcpy(destinationBuffer, &_colorBitsWritten, sizeof(_colorBitsWritten)); + destinationBuffer += sizeof(_colorBitsWritten); + memcpy(destinationBuffer, &_existsBitsWritten, sizeof(_existsBitsWritten)); + destinationBuffer += sizeof(_existsBitsWritten); + memcpy(destinationBuffer, &_existsInPacketBitsWritten, sizeof(_existsInPacketBitsWritten)); + destinationBuffer += sizeof(_existsInPacketBitsWritten); + memcpy(destinationBuffer, &_treesRemoved, sizeof(_treesRemoved)); + destinationBuffer += sizeof(_treesRemoved); + + return destinationBuffer - bufferStart; // includes header! +} + +int VoxelSceneStats::unpackFromMessage(unsigned char* sourceBuffer, int availableBytes) { + unsigned char* startPosition = sourceBuffer; + + // increment to push past the packet header + int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer); + sourceBuffer += numBytesPacketHeader; + + memcpy(&_start, sourceBuffer, sizeof(_start)); + sourceBuffer += sizeof(_start); + memcpy(&_end, sourceBuffer, sizeof(_end)); + sourceBuffer += sizeof(_end); + memcpy(&_elapsed, sourceBuffer, sizeof(_elapsed)); + sourceBuffer += sizeof(_elapsed); + memcpy(&_totalEncodeTime, sourceBuffer, sizeof(_totalEncodeTime)); + sourceBuffer += sizeof(_totalEncodeTime); + memcpy(&_isFullScene, sourceBuffer, sizeof(_isFullScene)); + sourceBuffer += sizeof(_isFullScene); + memcpy(&_isMoving, sourceBuffer, sizeof(_isMoving)); + sourceBuffer += sizeof(_isMoving); + memcpy(&_packets, sourceBuffer, sizeof(_packets)); + sourceBuffer += sizeof(_packets); + memcpy(&_bytes, sourceBuffer, sizeof(_bytes)); + sourceBuffer += sizeof(_bytes); + + memcpy(&_totalInternal, sourceBuffer, sizeof(_totalInternal)); + sourceBuffer += sizeof(_totalInternal); + memcpy(&_totalLeaves, sourceBuffer, sizeof(_totalLeaves)); + sourceBuffer += sizeof(_totalLeaves); + _totalVoxels = _totalInternal + _totalLeaves; + + memcpy(&_internal, sourceBuffer, sizeof(_internal)); + sourceBuffer += sizeof(_internal); + memcpy(&_leaves, sourceBuffer, sizeof(_leaves)); + sourceBuffer += sizeof(_leaves); + _traversed = _internal + _leaves; + + memcpy(&_internalSkippedDistance, sourceBuffer, sizeof(_internalSkippedDistance)); + sourceBuffer += sizeof(_internalSkippedDistance); + memcpy(&_leavesSkippedDistance, sourceBuffer, sizeof(_leavesSkippedDistance)); + sourceBuffer += sizeof(_leavesSkippedDistance); + _skippedDistance = _internalSkippedDistance + _leavesSkippedDistance; + + memcpy(&_internalSkippedOutOfView, sourceBuffer, sizeof(_internalSkippedOutOfView)); + sourceBuffer += sizeof(_internalSkippedOutOfView); + memcpy(&_leavesSkippedOutOfView, sourceBuffer, sizeof(_leavesSkippedOutOfView)); + sourceBuffer += sizeof(_leavesSkippedOutOfView); + _skippedOutOfView = _internalSkippedOutOfView + _leavesSkippedOutOfView; + + memcpy(&_internalSkippedWasInView, sourceBuffer, sizeof(_internalSkippedWasInView)); + sourceBuffer += sizeof(_internalSkippedWasInView); + memcpy(&_leavesSkippedWasInView, sourceBuffer, sizeof(_leavesSkippedWasInView)); + sourceBuffer += sizeof(_leavesSkippedWasInView); + _skippedWasInView = _internalSkippedWasInView + _leavesSkippedWasInView; + + memcpy(&_internalSkippedNoChange, sourceBuffer, sizeof(_internalSkippedNoChange)); + sourceBuffer += sizeof(_internalSkippedNoChange); + memcpy(&_leavesSkippedNoChange, sourceBuffer, sizeof(_leavesSkippedNoChange)); + sourceBuffer += sizeof(_leavesSkippedNoChange); + _skippedNoChange = _internalSkippedNoChange + _leavesSkippedNoChange; + + memcpy(&_internalSkippedOccluded, sourceBuffer, sizeof(_internalSkippedOccluded)); + sourceBuffer += sizeof(_internalSkippedOccluded); + memcpy(&_leavesSkippedOccluded, sourceBuffer, sizeof(_leavesSkippedOccluded)); + sourceBuffer += sizeof(_leavesSkippedOccluded); + _skippedOccluded = _internalSkippedOccluded + _leavesSkippedOccluded; + + memcpy(&_internalColorSent, sourceBuffer, sizeof(_internalColorSent)); + sourceBuffer += sizeof(_internalColorSent); + memcpy(&_leavesColorSent, sourceBuffer, sizeof(_leavesColorSent)); + sourceBuffer += sizeof(_leavesColorSent); + _colorSent = _internalColorSent + _leavesColorSent; + + memcpy(&_internalDidntFit, sourceBuffer, sizeof(_internalDidntFit)); + sourceBuffer += sizeof(_internalDidntFit); + memcpy(&_leavesDidntFit, sourceBuffer, sizeof(_leavesDidntFit)); + sourceBuffer += sizeof(_leavesDidntFit); + _didntFit = _internalDidntFit + _leavesDidntFit; + + memcpy(&_colorBitsWritten, sourceBuffer, sizeof(_colorBitsWritten)); + sourceBuffer += sizeof(_colorBitsWritten); + memcpy(&_existsBitsWritten, sourceBuffer, sizeof(_existsBitsWritten)); + sourceBuffer += sizeof(_existsBitsWritten); + memcpy(&_existsInPacketBitsWritten, sourceBuffer, sizeof(_existsInPacketBitsWritten)); + sourceBuffer += sizeof(_existsInPacketBitsWritten); + memcpy(&_treesRemoved, sourceBuffer, sizeof(_treesRemoved)); + sourceBuffer += sizeof(_treesRemoved); + + // running averages + _elapsedAverage.updateAverage((float)_elapsed); + unsigned long total = _existsInPacketBitsWritten + _colorSent; + float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; + _bitsPerVoxelAverage.updateAverage(calculatedBPV); + + + return sourceBuffer - startPosition; // includes header! +} + + +void VoxelSceneStats::printDebugDetails() { + qDebug("\n------------------------------\n"); + qDebug("VoxelSceneStats:\n"); + qDebug(" start : %llu \n", _start); + qDebug(" end : %llu \n", _end); + qDebug(" elapsed : %llu \n", _elapsed); + qDebug(" encoding : %llu \n", _totalEncodeTime); + qDebug("\n"); + qDebug(" full scene: %s\n", debug::valueOf(_isFullScene)); + qDebug(" moving: %s\n", debug::valueOf(_isMoving)); + qDebug("\n"); + qDebug(" packets: %d\n", _packets); + qDebug(" bytes : %ld\n", _bytes); + qDebug("\n"); + qDebug(" total voxels : %lu\n", _totalVoxels ); + qDebug(" internal : %lu\n", _totalInternal ); + qDebug(" leaves : %lu\n", _totalLeaves ); + qDebug(" traversed : %lu\n", _traversed ); + qDebug(" internal : %lu\n", _internal ); + qDebug(" leaves : %lu\n", _leaves ); + qDebug(" skipped distance : %lu\n", _skippedDistance ); + qDebug(" internal : %lu\n", _internalSkippedDistance ); + qDebug(" leaves : %lu\n", _leavesSkippedDistance ); + qDebug(" skipped out of view : %lu\n", _skippedOutOfView ); + qDebug(" internal : %lu\n", _internalSkippedOutOfView ); + qDebug(" leaves : %lu\n", _leavesSkippedOutOfView ); + qDebug(" skipped was in view : %lu\n", _skippedWasInView ); + qDebug(" internal : %lu\n", _internalSkippedWasInView ); + qDebug(" leaves : %lu\n", _leavesSkippedWasInView ); + qDebug(" skipped no change : %lu\n", _skippedNoChange ); + qDebug(" internal : %lu\n", _internalSkippedNoChange ); + qDebug(" leaves : %lu\n", _leavesSkippedNoChange ); + qDebug(" skipped occluded : %lu\n", _skippedOccluded ); + qDebug(" internal : %lu\n", _internalSkippedOccluded ); + qDebug(" leaves : %lu\n", _leavesSkippedOccluded ); + + qDebug("\n"); + qDebug(" color sent : %lu\n", _colorSent ); + qDebug(" internal : %lu\n", _internalColorSent ); + qDebug(" leaves : %lu\n", _leavesColorSent ); + qDebug(" Didn't Fit : %lu\n", _didntFit ); + qDebug(" internal : %lu\n", _internalDidntFit ); + qDebug(" leaves : %lu\n", _leavesDidntFit ); + qDebug(" color bits : %lu\n", _colorBitsWritten ); + qDebug(" exists bits : %lu\n", _existsBitsWritten ); + qDebug(" in packet bit : %lu\n", _existsInPacketBitsWritten); + qDebug(" trees removed : %lu\n", _treesRemoved ); +} + +const unsigned greenish = 0x40ff40d0; +const unsigned yellowish = 0xffef40c0; +const unsigned greyish = 0xd0d0d0a0; + +VoxelSceneStats::ItemInfo VoxelSceneStats::_ITEMS[] = { + { "Elapsed" , greenish }, + { "Encode" , yellowish }, + { "Network" , greyish }, + { "Voxels on Server" , greenish }, + { "Voxels Sent" , yellowish }, + { "Colors Sent" , greyish }, + { "Bitmasks Sent" , greenish }, + { "Traversed" , yellowish }, + { "Skipped - Total" , greyish }, + { "Skipped - Distance" , greenish }, + { "Skipped - Out of View", yellowish }, + { "Skipped - Was in View", greyish }, + { "Skipped - No Change" , greenish }, + { "Skipped - Occluded" , yellowish }, + { "Didn't fit in packet" , greyish }, + { "Mode" , greenish }, +}; + +char* VoxelSceneStats::getItemValue(int item) { + const uint64_t USECS_PER_SECOND = 1000 * 1000; + int calcFPS, calcAverageFPS, calculatedKBPS; + switch(item) { + case ITEM_ELAPSED: { + calcFPS = (float)USECS_PER_SECOND / (float)_elapsed; + float elapsedAverage = _elapsedAverage.getAverage(); + calcAverageFPS = (float)USECS_PER_SECOND / (float)elapsedAverage; + + sprintf(_itemValueBuffer, "%llu usecs (%d fps) Average: %.0f usecs (%d fps)", + _elapsed, calcFPS, elapsedAverage, calcAverageFPS); + break; + } + case ITEM_ENCODE: + calcFPS = (float)USECS_PER_SECOND / (float)_totalEncodeTime; + sprintf(_itemValueBuffer, "%llu usecs (%d fps)", _totalEncodeTime, calcFPS); + break; + case ITEM_PACKETS: { + float elapsedSecs = ((float)_elapsed / (float)USECS_PER_SECOND); + calculatedKBPS = elapsedSecs == 0 ? 0 : ((_bytes * 8) / elapsedSecs) / 1000; + sprintf(_itemValueBuffer, "%d packets %lu bytes (%d kbps)", _packets, _bytes, calculatedKBPS); + break; + } + case ITEM_VOXELS_SERVER: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _totalVoxels, _totalInternal, _totalLeaves); + break; + } + case ITEM_VOXELS: { + unsigned long total = _existsInPacketBitsWritten + _colorSent; + float calculatedBPV = total == 0 ? 0 : (_bytes * 8) / total; + float averageBPV = _bitsPerVoxelAverage.getAverage(); + sprintf(_itemValueBuffer, "%lu (%.2f bits/voxel Average: %.2f bits/voxel) %lu internal %lu leaves", + total, calculatedBPV, averageBPV, _existsInPacketBitsWritten, _colorSent); + break; + } + case ITEM_TRAVERSED: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _traversed, _internal, _leaves); + break; + } + case ITEM_SKIPPED: { + unsigned long total = _skippedDistance + _skippedOutOfView + + _skippedWasInView + _skippedNoChange + _skippedOccluded; + + unsigned long internal = _internalSkippedDistance + _internalSkippedOutOfView + + _internalSkippedWasInView + _internalSkippedNoChange + _internalSkippedOccluded; + + unsigned long leaves = _leavesSkippedDistance + _leavesSkippedOutOfView + + _leavesSkippedWasInView + _leavesSkippedNoChange + _leavesSkippedOccluded; + + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + total, internal, leaves); + break; + } + case ITEM_SKIPPED_DISTANCE: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedDistance, _internalSkippedDistance, _leavesSkippedDistance); + break; + } + case ITEM_SKIPPED_OUT_OF_VIEW: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedOutOfView, _internalSkippedOutOfView, _leavesSkippedOutOfView); + break; + } + case ITEM_SKIPPED_WAS_IN_VIEW: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedWasInView, _internalSkippedWasInView, _leavesSkippedWasInView); + break; + } + case ITEM_SKIPPED_NO_CHANGE: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedNoChange, _internalSkippedNoChange, _leavesSkippedNoChange); + break; + } + case ITEM_SKIPPED_OCCLUDED: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _skippedOccluded, _internalSkippedOccluded, _leavesSkippedOccluded); + break; + } + case ITEM_COLORS: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves", + _colorSent, _internalColorSent, _leavesColorSent); + break; + } + case ITEM_DIDNT_FIT: { + sprintf(_itemValueBuffer, "%lu total %lu internal %lu leaves (removed: %lu)", + _didntFit, _internalDidntFit, _leavesDidntFit, _treesRemoved); + break; + } + case ITEM_BITS: { + sprintf(_itemValueBuffer, "colors: %lu, exists: %lu, in packets: %lu", + _colorBitsWritten, _existsBitsWritten, _existsInPacketBitsWritten); + break; + } + case ITEM_MODE: { + sprintf(_itemValueBuffer, "%s - %s", (_isFullScene ? "Full Scene" : "Partial Scene"), + (_isMoving ? "Moving" : "Stationary")); + break; + } + default: + sprintf(_itemValueBuffer, ""); + break; + } + return _itemValueBuffer; +} + diff --git a/libraries/voxels/src/VoxelSceneStats.h b/libraries/voxels/src/VoxelSceneStats.h new file mode 100644 index 0000000000..ded2061a6e --- /dev/null +++ b/libraries/voxels/src/VoxelSceneStats.h @@ -0,0 +1,170 @@ +// +// VoxelSceneStats.h +// hifi +// +// Created by Brad Hefta-Gaub on 7/18/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__VoxelSceneStats__ +#define __hifi__VoxelSceneStats__ + +#include +#include + +class VoxelNode; + +class VoxelSceneStats { +public: + VoxelSceneStats(); + void reset(); + void sceneStarted(bool fullScene, bool moving, VoxelNode* root); + void sceneCompleted(); + + void printDebugDetails(); + void packetSent(int bytes); + + void encodeStarted(); + void encodeStopped(); + + void traversed(const VoxelNode* node); + void skippedDistance(const VoxelNode* node); + void skippedOutOfView(const VoxelNode* node); + void skippedWasInView(const VoxelNode* node); + void skippedNoChange(const VoxelNode* node); + void skippedOccluded(const VoxelNode* node); + void colorSent(const VoxelNode* node); + void didntFit(const VoxelNode* node); + void colorBitsWritten(); + void existsBitsWritten(); + void existsInPacketBitsWritten(); + void childBitsRemoved(bool includesExistsBits, bool includesColors); + + int packIntoMessage(unsigned char* destinationBuffer, int availableBytes); + int unpackFromMessage(unsigned char* sourceBuffer, int availableBytes); + + bool isReadyToSend() const { return _isReadyToSend; } + void markAsSent() { _isReadyToSend = false; } + unsigned char* getStatsMessage() { return &_statsMessage[0]; } + int getStatsMessageLength() const { return _statsMessageLength; } + + enum { + ITEM_ELAPSED, + ITEM_ENCODE, + ITEM_PACKETS, + ITEM_VOXELS_SERVER, + ITEM_VOXELS, + ITEM_COLORS, + ITEM_BITS, + ITEM_TRAVERSED, + ITEM_SKIPPED, + ITEM_SKIPPED_DISTANCE, + ITEM_SKIPPED_OUT_OF_VIEW, + ITEM_SKIPPED_WAS_IN_VIEW, + ITEM_SKIPPED_NO_CHANGE, + ITEM_SKIPPED_OCCLUDED, + ITEM_DIDNT_FIT, + ITEM_MODE, + ITEM_COUNT + }; + + // Meta information about each stats item + struct ItemInfo { + char const* const caption; + unsigned colorRGBA; + }; + + ItemInfo& getItemInfo(int item) { return _ITEMS[item]; }; + char* getItemValue(int item); + +private: + bool _isReadyToSend; + unsigned char _statsMessage[MAX_PACKET_SIZE]; + int _statsMessageLength; + + // scene timing data in usecs + bool _isStarted; + uint64_t _start; + uint64_t _end; + uint64_t _elapsed; + + SimpleMovingAverage _elapsedAverage; + SimpleMovingAverage _bitsPerVoxelAverage; + + uint64_t _totalEncodeTime; + uint64_t _encodeStart; + + // scene voxel related data + unsigned long _totalVoxels; + unsigned long _totalInternal; + unsigned long _totalLeaves; + + unsigned long _traversed; + unsigned long _internal; + unsigned long _leaves; + + unsigned long _skippedDistance; + unsigned long _internalSkippedDistance; + unsigned long _leavesSkippedDistance; + + unsigned long _skippedOutOfView; + unsigned long _internalSkippedOutOfView; + unsigned long _leavesSkippedOutOfView; + + unsigned long _skippedWasInView; + unsigned long _internalSkippedWasInView; + unsigned long _leavesSkippedWasInView; + + unsigned long _skippedNoChange; + unsigned long _internalSkippedNoChange; + unsigned long _leavesSkippedNoChange; + + unsigned long _skippedOccluded; + unsigned long _internalSkippedOccluded; + unsigned long _leavesSkippedOccluded; + + unsigned long _colorSent; + unsigned long _internalColorSent; + unsigned long _leavesColorSent; + + unsigned long _didntFit; + unsigned long _internalDidntFit; + unsigned long _leavesDidntFit; + + unsigned long _colorBitsWritten; + unsigned long _existsBitsWritten; + unsigned long _existsInPacketBitsWritten; + unsigned long _treesRemoved; + + // Accounting Notes: + // + // 1) number of voxels sent can be calculated as _colorSent + _colorBitsWritten. This works because each internal + // node in a packet will have a _colorBitsWritten included for it and each "leaf" in the packet will have a + // _colorSent written for it. Note that these "leaf" nodes in the packets may not be actual leaves in the full + // tree, because LOD may cause us to send an average color for an internal node instead of recursing deeper to + // the leaves. + // + // 2) the stats balance if: (working assumption) + // if _colorSent > 0 + // _traversed = all skipped + _colorSent + _colorBitsWritten + // else + // _traversed = all skipped + _colorSent + _colorBitsWritten + _treesRemoved + // + + // scene network related data + unsigned int _packets; + unsigned long _bytes; + unsigned int _passes; + + // features related items + bool _isMoving; + bool _isFullScene; + + + static ItemInfo _ITEMS[]; + static int const MAX_ITEM_VALUE_LENGTH = 128; + char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; +}; + +#endif /* defined(__hifi__VoxelSceneStats__) */ diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 9cb6a85f93..b26a70f34b 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -673,6 +673,9 @@ void VoxelTree::reaverageVoxelColors(VoxelNode *startNode) { if (hasChildren && !startNode->collapseIdenticalLeaves()) { startNode->setColorFromAverageOfChildren(); } + + // this is also a good time to recalculateSubTreeNodeCount() + startNode->recalculateSubTreeNodeCount(); } } @@ -1037,6 +1040,13 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, availableBytes -= codeLength; // keep track or remaining space int currentEncodeLevel = 0; + + // record some stats, this is the one node that we won't record below in the recursion function, so we need to + // track it here + if (params.stats) { + params.stats->traversed(node); + } + int childBytesWritten = encodeTreeBitstreamRecursion(node, outputBuffer, availableBytes, bag, params, currentEncodeLevel); // if childBytesWritten == 1 then something went wrong... that's not possible @@ -1081,6 +1091,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // If we're too far away for our render level, then just return if (distance >= boundaryDistance) { + if (params.stats) { + params.stats->skippedDistance(node); + } return bytesAtThisLevel; } @@ -1088,6 +1101,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if // we're out of view if (!node->isInView(*params.viewFrustum)) { + if (params.stats) { + params.stats->skippedOutOfView(node); + } return bytesAtThisLevel; } @@ -1110,6 +1126,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // if we're in deltaViewFrustum mode, and this node has changed since it was last sent, then we do // need to send it. if (wasInView && !(params.deltaViewFrustum && node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))) { + if (params.stats) { + params.stats->skippedWasInView(node); + } return bytesAtThisLevel; } @@ -1117,6 +1136,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // then we can also bail early and save bits if (!params.forceSendScene && !params.deltaViewFrustum && !node->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE)) { + if (params.stats) { + params.stats->skippedNoChange(node); + } return bytesAtThisLevel; } @@ -1136,6 +1158,9 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false); delete voxelPolygon; // cleanup if (result == OCCLUDED) { + if (params.stats) { + params.stats->skippedOccluded(node); + } return bytesAtThisLevel; } } else { @@ -1201,6 +1226,12 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp distancesToChildren[i] = 0.0f; currentCount++; } + + // track stats + if (params.stats && childNode) { + params.stats->traversed(childNode); + } + } // for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so @@ -1211,13 +1242,21 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp bool childIsInView = (childNode && (!params.viewFrustum || childNode->isInView(*params.viewFrustum))); - if (childIsInView) { + if (!childIsInView) { + if (params.stats) { + params.stats->skippedOutOfView(childNode); + } + } else { // Before we determine consider this further, let's see if it's in our LOD scope... float distance = distancesToChildren[i]; // params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0; float boundaryDistance = !params.viewFrustum ? 1 : boundaryDistanceForRenderLevel(childNode->getLevel() + params.boundaryLevelAdjust); - if (distance < boundaryDistance) { + if (!(distance < boundaryDistance)) { + if (params.stats) { + params.stats->skippedDistance(childNode); + } + } else { inViewCount++; // track children in view as existing and not a leaf, if they're a leaf, @@ -1261,7 +1300,19 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp } // wants occlusion culling & isLeaf() - bool shouldRender = !params.viewFrustum ? true : childNode->calculateShouldRender(params.viewFrustum, params.boundaryLevelAdjust); + bool shouldRender = !params.viewFrustum + ? true + : childNode->calculateShouldRender(params.viewFrustum, params.boundaryLevelAdjust); + + // track some stats + if (params.stats) { + if (!shouldRender && childNode->isLeaf()) { + params.stats->skippedDistance(childNode); + } + if (childIsOccluded) { + params.stats->skippedOccluded(childNode); + } + } // track children with actual color, only if the child wasn't previously in view! if (shouldRender && !childIsOccluded) { @@ -1288,7 +1339,14 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp inViewWithColorCount++; } else { // otherwise just track stats of the items we discarded - params.childWasInViewDiscarded++; + if (params.stats) { + if (childWasInView) { + params.stats->skippedWasInView(childNode); + } else { + params.stats->skippedNoChange(childNode); + } + } + } } } @@ -1297,14 +1355,23 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp *writeToThisLevelBuffer = childrenColoredBits; writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count + if (params.stats) { + params.stats->colorBitsWritten(); + } // write the color data... if (params.includeColor) { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { if (oneAtBit(childrenColoredBits, i)) { - memcpy(writeToThisLevelBuffer, &node->getChildAtIndex(i)->getColor(), BYTES_PER_COLOR); + VoxelNode* childNode = node->getChildAtIndex(i); + memcpy(writeToThisLevelBuffer, &childNode->getColor(), BYTES_PER_COLOR); writeToThisLevelBuffer += BYTES_PER_COLOR; // move the pointer for color bytesAtThisLevel += BYTES_PER_COLOR; // keep track of byte count for color + + if (params.stats) { + params.stats->colorSent(childNode); + } + } } } @@ -1315,12 +1382,18 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp *writeToThisLevelBuffer = childrenExistInTreeBits; writeToThisLevelBuffer += sizeof(childrenExistInTreeBits); // move the pointer bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count + if (params.stats) { + params.stats->existsBitsWritten(); + } } // write the child exist bits *writeToThisLevelBuffer = childrenExistInPacketBits; writeToThisLevelBuffer += sizeof(childrenExistInPacketBits); // move the pointer bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count + if (params.stats) { + params.stats->existsInPacketBitsWritten(); + } // We only need to keep digging, if there is at least one child that is inView, and not a leaf. keepDiggingDeeper = (inViewNotLeafCount > 0); @@ -1333,6 +1406,11 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp availableBytes -= bytesAtThisLevel; } else { bag.insert(node); + + if (params.stats) { + params.stats->didntFit(node); + } + return 0; } @@ -1393,7 +1471,12 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! // // we can make this act like no bytes out, by just resetting the bytes out in this case - if (params.includeColor && childTreeBytesOut == 2) { + if (params.includeColor && !params.includeExistsBits && childTreeBytesOut == 2) { + childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees + } + // If we've asked for existBits, this is also true, except that the tree will output 3 bytes + // NOTE: does this introduce a problem with detecting deletion?? + if (params.includeColor && params.includeExistsBits && childTreeBytesOut == 3) { childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees } @@ -1408,6 +1491,12 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp childrenExistInPacketBits -= (1 << (7 - originalIndex)); // repair the child exists mask *childExistsPlaceHolder = childrenExistInPacketBits; + + // If this is the last of the child exists bits, then we're actually be rolling out the entire tree + if (params.stats && childrenExistInPacketBits == 0) { + params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor); + } + // Note: no need to move the pointer, cause we already stored this } // end if (childTreeBytesOut == 0) } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index e5db6526e9..199942605e 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -9,12 +9,14 @@ #ifndef __hifi__VoxelTree__ #define __hifi__VoxelTree__ -#include "SimpleMovingAverage.h" +#include +#include + +#include "CoverageMap.h" #include "ViewFrustum.h" #include "VoxelNode.h" #include "VoxelNodeBag.h" -#include "CoverageMap.h" -#include "PointerStack.h" +#include "VoxelSceneStats.h" // Callback function, for recuseTreeWithOperation typedef bool (*RecurseVoxelTreeOperation)(VoxelNode* node, void* extraData); @@ -36,6 +38,7 @@ typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; #define NO_BOUNDARY_ADJUST 0 #define LOW_RES_MOVING_ADJUST 1 #define IGNORE_LAST_SENT 0 +#define IGNORE_SCENE_STATS NULL class EncodeBitstreamParams { public: @@ -48,10 +51,10 @@ public: bool deltaViewFrustum; const ViewFrustum* lastViewFrustum; bool wantOcclusionCulling; - long childWasInViewDiscarded; int boundaryLevelAdjust; uint64_t lastViewFrustumSent; bool forceSendScene; + VoxelSceneStats* stats; CoverageMap* map; EncodeBitstreamParams( @@ -66,7 +69,8 @@ public: CoverageMap* map = IGNORE_COVERAGE_MAP, int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, uint64_t lastViewFrustumSent = IGNORE_LAST_SENT, - bool forceSendScene = true) : + bool forceSendScene = true, + VoxelSceneStats* stats = IGNORE_SCENE_STATS) : maxEncodeLevel (maxEncodeLevel), maxLevelReached (0), viewFrustum (viewFrustum), @@ -76,10 +80,10 @@ public: deltaViewFrustum (deltaViewFrustum), lastViewFrustum (lastViewFrustum), wantOcclusionCulling (wantOcclusionCulling), - childWasInViewDiscarded (0), boundaryLevelAdjust (boundaryLevelAdjust), lastViewFrustumSent (lastViewFrustumSent), forceSendScene (forceSendScene), + stats (stats), map (map) {} }; diff --git a/voxel-server/src/VoxelNodeData.h b/voxel-server/src/VoxelNodeData.h index 96b61db963..746db6da93 100644 --- a/voxel-server/src/VoxelNodeData.h +++ b/voxel-server/src/VoxelNodeData.h @@ -12,9 +12,11 @@ #include #include #include -#include "VoxelNodeBag.h" -#include "VoxelConstants.h" -#include "CoverageMap.h" + +#include +#include +#include +#include class VoxelNodeData : public AvatarData { public: @@ -58,6 +60,9 @@ public: void setLastTimeBagEmpty(uint64_t lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; }; bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }; + + VoxelSceneStats stats; + private: VoxelNodeData(const VoxelNodeData &); VoxelNodeData& operator= (const VoxelNodeData&); diff --git a/voxel-server/src/main.cpp b/voxel-server/src/main.cpp index 8a8048035b..c009eec2a5 100644 --- a/voxel-server/src/main.cpp +++ b/voxel-server/src/main.cpp @@ -60,6 +60,7 @@ bool wantLocalDomain = false; bool wantColorRandomizer = false; bool debugVoxelSending = false; bool shouldShowAnimationDebug = false; +bool displayVoxelStats = false; EnvironmentData environmentData[3]; @@ -111,6 +112,44 @@ void eraseVoxelTreeAndCleanupNodeVisitData() { pthread_mutex_t treeLock; +void handlePacketSend(NodeList* nodeList, + NodeList::iterator& node, + VoxelNodeData* nodeData, + int& trueBytesSent, int& truePacketsSent) { + // If we've got a stats message ready to send, then see if we can piggyback them together + if (nodeData->stats.isReadyToSend()) { + // Send the stats message to the client + unsigned char* statsMessage = nodeData->stats.getStatsMessage(); + int statsMessageLength = nodeData->stats.getStatsMessageLength(); + + // If the size of the stats message and the voxel message will fit in a packet, then piggyback them + if (nodeData->getPacketLength() + statsMessageLength < MAX_PACKET_SIZE) { + + // copy voxel message to back of stats message + memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength()); + statsMessageLength += nodeData->getPacketLength(); + + // actually send it + nodeList->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength); + } else { + // not enough room in the packet, send two packets + nodeList->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength); + nodeList->getNodeSocket()->send(node->getActiveSocket(), + nodeData->getPacket(), nodeData->getPacketLength()); + } + } else { + // just send the voxel packet + nodeList->getNodeSocket()->send(node->getActiveSocket(), + nodeData->getPacket(), nodeData->getPacketLength()); + } + // remember to track our stats + nodeData->stats.packetSent(nodeData->getPacketLength()); + trueBytesSent += nodeData->getPacketLength(); + truePacketsSent++; + nodeData->resetVoxelPacket(); +} + + // Version of voxel distributor that sends the deepest LOD level at once void deepestLevelVoxelDistributor(NodeList* nodeList, NodeList::iterator& node, @@ -141,11 +180,9 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n", debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor())); } - nodeList->getNodeSocket()->send(node->getActiveSocket(), - nodeData->getPacket(), nodeData->getPacketLength()); - trueBytesSent += nodeData->getPacketLength(); - truePacketsSent++; - nodeData->resetVoxelPacket(); + + handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); + } else { if (::debugVoxelSending) { printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n", @@ -200,13 +237,20 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, // only set our last sent time if we weren't resetting due to frustum change uint64_t now = usecTimestampNow(); nodeData->setLastTimeBagEmpty(now); - if (::debugVoxelSending) { - printf("ENTIRE SCENE SENT! nodeData->setLastTimeBagEmpty(now=[%lld])\n", now); - } } - + + nodeData->stats.sceneCompleted(); + + if (::displayVoxelStats) { + nodeData->stats.printDebugDetails(); + } + // This is the start of "resending" the scene. nodeData->nodeBag.insert(serverTree.rootNode); + + // start tracking our stats + bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging(); + nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, ::serverTree.rootNode); } // If we have something in our nodeBag, then turn them into packets and send them out... @@ -239,33 +283,32 @@ void deepestLevelVoxelDistributor(NodeList* nodeList, CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; int boundaryLevelAdjust = viewFrustumChanged && nodeData->getWantLowResMoving() ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST; + + bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && + nodeData->getViewFrustumJustStoppedChanging(); EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, wantOcclusionCulling, coverageMap, boundaryLevelAdjust, nodeData->getLastTimeBagEmpty(), - nodeData->getViewFrustumJustStoppedChanging()); - + isFullScene, &nodeData->stats); + + nodeData->stats.encodeStarted(); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeData->nodeBag, params); + nodeData->stats.encodeStopped(); if (nodeData->getAvailable() >= bytesWritten) { nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } else { - nodeList->getNodeSocket()->send(node->getActiveSocket(), - nodeData->getPacket(), nodeData->getPacketLength()); - trueBytesSent += nodeData->getPacketLength(); - truePacketsSent++; + handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); packetsSentThisInterval++; nodeData->resetVoxelPacket(); nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } } else { if (nodeData->isPacketWaiting()) { - nodeList->getNodeSocket()->send(node->getActiveSocket(), - nodeData->getPacket(), nodeData->getPacketLength()); - trueBytesSent += nodeData->getPacketLength(); - truePacketsSent++; + handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); nodeData->resetVoxelPacket(); } packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left @@ -368,7 +411,9 @@ void *distributeVoxelsToListeners(void *args) { if (usecToSleep > 0) { usleep(usecToSleep); } else { - std::cout << "Last send took too much time, not sleeping!\n"; + if (::debugVoxelSending) { + std::cout << "Last send took too much time, not sleeping!\n"; + } } } @@ -401,6 +446,10 @@ int main(int argc, const char * argv[]) { nodeList->startSilentNodeRemovalThread(); srand((unsigned)time(0)); + + const char* DISPLAY_VOXEL_STATS = "--displayVoxelStats"; + ::displayVoxelStats = cmdOptionExists(argc, argv, DISPLAY_VOXEL_STATS); + printf("displayVoxelStats=%s\n", debug::valueOf(::displayVoxelStats)); const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; ::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING); @@ -437,8 +486,10 @@ int main(int argc, const char * argv[]) { ::serverTree.clearDirtyBit(); // the tree is clean since we just loaded it printf("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); - unsigned long nodeCount = ::serverTree.getVoxelCount(); - printf("Nodes after loading scene %ld nodes\n", nodeCount); + unsigned long nodeCount = ::serverTree.rootNode->getSubTreeNodeCount(); + unsigned long internalNodeCount = ::serverTree.rootNode->getSubTreeInternalNodeCount(); + unsigned long leafNodeCount = ::serverTree.rootNode->getSubTreeLeafNodeCount(); + printf("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount); } // Check to see if the user passed in a command line option for loading an old style local