Merge branch 'master' of https://github.com/highfidelity/hifi into xboxLasers
|
@ -28,6 +28,7 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include <recording/Deck.h>
|
||||
|
@ -67,6 +68,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::set<recording::Recorder>();
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<ScriptEngines>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
|
@ -588,9 +590,10 @@ void Agent::aboutToFinish() {
|
|||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
|
||||
|
||||
ResourceManager::cleanup();
|
||||
|
||||
|
||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||
DependencyManager::destroy<AudioInjectorManager>();
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
emit stopAvatarAudioTimer();
|
||||
_avatarAudioTimerThread.quit();
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
#include <ShutdownEventListener.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <ScriptEngines.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
@ -53,10 +52,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
DependencyManager::set<AccountManager>();
|
||||
|
||||
|
||||
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto scriptEngines = DependencyManager::set<ScriptEngines>();
|
||||
|
||||
// create a NodeList as an unassigned client, must be after addressManager
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
|
||||
|
@ -178,8 +176,6 @@ AssignmentClient::~AssignmentClient() {
|
|||
void AssignmentClient::aboutToQuit() {
|
||||
stopAssignmentClient();
|
||||
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
|
|
@ -636,7 +636,11 @@ QString AudioMixer::percentageForMixStats(int counter) {
|
|||
}
|
||||
|
||||
void AudioMixer::sendStatsPacket() {
|
||||
static QJsonObject statsObject;
|
||||
QJsonObject statsObject;
|
||||
|
||||
if (_numStatFrames == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1;
|
||||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100.0f;
|
||||
|
|
|
@ -49,7 +49,7 @@ AudioMixerClientData::~AudioMixerClientData() {
|
|||
|
||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||
QReadLocker readLocker { &_streamsLock };
|
||||
|
||||
|
||||
auto it = _audioStreams.find(QUuid());
|
||||
if (it != _audioStreams.end()) {
|
||||
return dynamic_cast<AvatarAudioStream*>(it->second.get());
|
||||
|
@ -75,7 +75,7 @@ void AudioMixerClientData::removeHRTFForStream(const QUuid& nodeID, const QUuid&
|
|||
|
||||
int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
||||
PacketType packetType = message.getType();
|
||||
|
||||
|
||||
if (packetType == PacketType::AudioStreamStats) {
|
||||
|
||||
// skip over header, appendFlag, and num stats packed
|
||||
|
@ -218,11 +218,10 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// The append flag is a boolean value that will be packed right after the header. The first packet sent
|
||||
// inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
|
||||
// The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
|
||||
// it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
|
||||
quint8 appendFlag = 0;
|
||||
// The append flag is a boolean value that will be packed right after the header.
|
||||
// This flag allows the client to know when it has received all stats packets, so it can group any downstream effects,
|
||||
// and clear its cache of injector stream stats; it helps to prevent buildup of dead audio stream stats in the client.
|
||||
quint8 appendFlag = AudioStreamStats::START;
|
||||
|
||||
auto streamsCopy = getAudioStreams();
|
||||
|
||||
|
@ -233,14 +232,21 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
while (numStreamStatsRemaining > 0) {
|
||||
auto statsPacket = NLPacket::create(PacketType::AudioStreamStats);
|
||||
|
||||
// pack the append flag in this packet
|
||||
statsPacket->writePrimitive(appendFlag);
|
||||
appendFlag = 1;
|
||||
|
||||
int numStreamStatsRoomFor = (int)(statsPacket->size() - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
|
||||
|
||||
// calculate and pack the number of stream stats to follow
|
||||
// calculate the number of stream stats to follow
|
||||
quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
|
||||
|
||||
// is this the terminal packet?
|
||||
if (numStreamStatsRemaining <= numStreamStatsToPack) {
|
||||
appendFlag |= AudioStreamStats::END;
|
||||
}
|
||||
|
||||
// pack the append flag in this packet
|
||||
statsPacket->writePrimitive(appendFlag);
|
||||
appendFlag = 0;
|
||||
|
||||
// pack the number of stream stats to follow
|
||||
statsPacket->writePrimitive(numStreamStatsToPack);
|
||||
|
||||
// pack the calculated number of stream stats
|
||||
|
|
After Width: | Height: | Size: 788 KiB |
After Width: | Height: | Size: 558 KiB |
BIN
interface/resources/meshes/controller/vive_body.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_button.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_l_grip.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_r_grip.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_sys_button.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_tips.fbm/Blank.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
interface/resources/meshes/controller/vive_tips.fbm/Grip.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
interface/resources/meshes/controller/vive_tips.fbm/Rotate.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
interface/resources/meshes/controller/vive_tips.fbm/Teleport.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
interface/resources/meshes/controller/vive_tips.fbm/Trigger.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
interface/resources/meshes/controller/vive_tips.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_trackpad.fbx
Normal file
BIN
interface/resources/meshes/controller/vive_trigger.fbx
Normal file
9
interface/resources/qml/StatText.qml
Normal file
|
@ -0,0 +1,9 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
|
||||
Text {
|
||||
color: "white";
|
||||
style: Text.Outline;
|
||||
styleColor: "black";
|
||||
font.pixelSize: 12;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.2
|
||||
import '.'
|
||||
|
||||
Item {
|
||||
id: stats
|
||||
|
@ -28,9 +29,7 @@ Item {
|
|||
implicitWidth: row.width
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
readonly property int fontSize: 12
|
||||
readonly property string fontColor: "white"
|
||||
readonly property string bgColor: "#99333333"
|
||||
readonly property string bgColor: "#AA111111"
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
@ -49,64 +48,40 @@ Item {
|
|||
Column {
|
||||
id: generalCol
|
||||
spacing: 4; x: 4; y: 4;
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Servers: " + root.serverCount
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Avatars: " + root.avatarCount
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Frame Rate: " + root.framerate.toFixed(2);
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Render Rate: " + root.renderrate.toFixed(2);
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Present Rate: " + root.presentrate.toFixed(2);
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Present New Rate: " + root.presentnewrate.toFixed(2);
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Present Drop Rate: " + root.presentdroprate.toFixed(2);
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Simrate: " + root.simrate
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Avatar Simrate: " + root.avatarSimrate
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2)
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "Asset Mbps In/Out: " + root.assetMbpsIn.toFixed(2) + "/" + root.assetMbpsOut.toFixed(2)
|
||||
}
|
||||
|
@ -126,29 +101,19 @@ Item {
|
|||
Column {
|
||||
id: pingCol
|
||||
spacing: 4; x: 4; y: 4;
|
||||
Text {
|
||||
color: root.fontColor
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Audio ping: " + root.audioPing
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Avatar ping: " + root.avatarPing
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Entities avg ping: " + root.entitiesPing
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Asset ping: " + root.assetPing
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Messages max ping: " + root.messagePing
|
||||
}
|
||||
|
@ -167,46 +132,32 @@ Item {
|
|||
Column {
|
||||
id: geoCol
|
||||
spacing: 4; x: 4; y: 4;
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Position: " + root.position.x.toFixed(1) + ", " +
|
||||
root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1)
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Speed: " + root.speed.toFixed(1)
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Yaw: " + root.yaw.toFixed(1)
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Avatar Mixer In: " + root.avatarMixerInKbps + " kbps, " +
|
||||
root.avatarMixerInPps + "pps";
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Avatar Mixer Out: " + root.avatarMixerOutKbps + " kbps, " +
|
||||
root.avatarMixerOutPps + "pps";
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Downloads: " + root.downloads + "/" + root.downloadLimit +
|
||||
", Pending: " + root.downloadsPending;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded && root.downloadUrls.length > 0;
|
||||
text: "Download URLs:"
|
||||
}
|
||||
|
@ -217,9 +168,7 @@ Item {
|
|||
visible: root.expanded && root.downloadUrls.length > 0;
|
||||
|
||||
model: root.downloadUrls
|
||||
delegate: Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
delegate: StatText {
|
||||
visible: root.expanded;
|
||||
text: modelData.length > 30
|
||||
? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22)
|
||||
|
@ -240,94 +189,95 @@ Item {
|
|||
Column {
|
||||
id: octreeCol
|
||||
spacing: 4; x: 4; y: 4;
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: "Triangles: " + root.triangles +
|
||||
" / Material Switches: " + root.materialSwitches
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
text: "GPU Textures: " + root.gpuTextures;
|
||||
StatText {
|
||||
text: "GPU Free Memory: " + root.gpuFreeMemory + " MB";
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
text: "GPU Buffers: " + root.gpuBuffers;
|
||||
StatText {
|
||||
text: "GPU Textures: ";
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
text: " Sparse Enabled: " + (0 == root.gpuSparseTextureEnabled ? "false" : "true");
|
||||
}
|
||||
StatText {
|
||||
text: " Count: " + root.gpuTextures;
|
||||
}
|
||||
StatText {
|
||||
text: " Sparse Count: " + root.gpuTexturesSparse;
|
||||
visible: 0 != root.gpuSparseTextureEnabled;
|
||||
}
|
||||
StatText {
|
||||
text: " Virtual Memory: " + root.gpuTextureVirtualMemory + " MB";
|
||||
}
|
||||
StatText {
|
||||
text: " Commited Memory: " + root.gpuTextureMemory + " MB";
|
||||
}
|
||||
StatText {
|
||||
text: " Sparse Memory: " + root.gpuTextureSparseMemory + " MB";
|
||||
visible: 0 != root.gpuSparseTextureEnabled;
|
||||
}
|
||||
StatText {
|
||||
text: "GPU Buffers: "
|
||||
}
|
||||
StatText {
|
||||
text: " Count: " + root.gpuTextures;
|
||||
}
|
||||
StatText {
|
||||
text: "QML Texture Memory: " + root.qmlTextureMemory + " MB";
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Items rendered / considered: " +
|
||||
root.itemRendered + " / " + root.itemConsidered;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: " out of view: " + root.itemOutOfView +
|
||||
" too small: " + root.itemTooSmall;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Shadows rendered / considered: " +
|
||||
root.shadowRendered + " / " + root.shadowConsidered;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: " out of view: " + root.shadowOutOfView +
|
||||
" too small: " + root.shadowTooSmall;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: !root.expanded
|
||||
text: "Octree Elements Server: " + root.serverElements +
|
||||
" Local: " + root.localElements;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "Octree Sending Mode: " + root.sendingMode;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "Octree Packets to Process: " + root.packetStats;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "Octree Elements - ";
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "\tServer: " + root.serverElements +
|
||||
" Internal: " + root.serverInternal +
|
||||
" Leaves: " + root.serverLeaves;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "\tLocal: " + root.localElements +
|
||||
" Internal: " + root.localInternal +
|
||||
" Leaves: " + root.localLeaves;
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
StatText {
|
||||
visible: root.expanded
|
||||
text: "LOD: " + root.lodStatus;
|
||||
}
|
||||
|
@ -341,12 +291,10 @@ Item {
|
|||
width: perfText.width + 8
|
||||
height: perfText.height + 8
|
||||
color: root.bgColor;
|
||||
Text {
|
||||
StatText {
|
||||
x: 4; y: 4
|
||||
id: perfText
|
||||
color: root.fontColor
|
||||
font.family: root.monospaceFont
|
||||
font.pixelSize: 12
|
||||
text: "------------------------------------------ Function " +
|
||||
"--------------------------------------- --msecs- -calls--\n" +
|
||||
root.timingStats;
|
||||
|
|
|
@ -51,9 +51,10 @@ OriginalDesktop.Desktop {
|
|||
Toolbar {
|
||||
id: sysToolbar;
|
||||
objectName: "com.highfidelity.interface.toolbar.system";
|
||||
// Magic: sysToolbar.x and y come from settings, and are bound before the properties specified here are applied.
|
||||
x: sysToolbar.x;
|
||||
y: sysToolbar.y;
|
||||
// These values will be overridden by sysToolbar.x/y if there is a saved position in Settings
|
||||
// On exit, the sysToolbar position is saved to settings
|
||||
x: 30
|
||||
y: 50
|
||||
}
|
||||
property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar
|
||||
map[sysToolbar.objectName] = sysToolbar;
|
||||
|
|
|
@ -583,23 +583,32 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
bool wantsSandboxRunning = shouldRunServer();
|
||||
static bool determinedSandboxState = false;
|
||||
static bool sandboxIsRunning = false;
|
||||
SandboxUtils sandboxUtils;
|
||||
// updateHeartbeat() because we are going to poll shortly...
|
||||
updateHeartbeat();
|
||||
sandboxUtils.ifLocalSandboxRunningElse([&]() {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running.....";
|
||||
determinedSandboxState = true;
|
||||
sandboxIsRunning = true;
|
||||
}, [&, wantsSandboxRunning]() {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running....";
|
||||
determinedSandboxState = true;
|
||||
if (wantsSandboxRunning) {
|
||||
QString contentPath = getRunServerPath();
|
||||
SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME);
|
||||
sandboxIsRunning = true;
|
||||
}
|
||||
determinedSandboxState = true;
|
||||
});
|
||||
|
||||
// SandboxUtils::runLocalSandbox currently has 2 sec delay after spawning sandbox, so 4
|
||||
// sec here is ok I guess. TODO: ping sandbox so we know it is up, perhaps?
|
||||
quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4;
|
||||
auto startWaiting = usecTimestampNow();
|
||||
while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) {
|
||||
QCoreApplication::processEvents();
|
||||
// updateHeartbeat() while polling so we don't scare the deadlock watchdog
|
||||
updateHeartbeat();
|
||||
usleep(USECS_PER_MSEC * 50); // 20hz
|
||||
}
|
||||
|
||||
|
@ -1326,10 +1335,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||
|
||||
if (shouldGoToTutorial) {
|
||||
sandboxUtils.ifLocalSandboxRunningElse([=]() {
|
||||
if(sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
}, [=]() {
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
showHelp();
|
||||
|
@ -1339,7 +1348,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
bool isFirstRun = firstRun.get();
|
||||
|
@ -1351,13 +1360,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (hasHMDAndHandControllers) {
|
||||
sandboxUtils.ifLocalSandboxRunningElse([=]() {
|
||||
if(sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
}, [=]() {
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
|
@ -1729,6 +1738,7 @@ void Application::initializeUi() {
|
|||
// though I can't find it. Hence, "ApplicationInterface"
|
||||
rootContext->setContextProperty("ApplicationInterface", this);
|
||||
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
|
||||
FileScriptingInterface* fileDownload = new FileScriptingInterface(engine);
|
||||
|
@ -3759,12 +3769,6 @@ void Application::updateDialogs(float deltaTime) const {
|
|||
PerformanceWarning warn(showWarnings, "Application::updateDialogs()");
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
|
||||
// Update audio stats dialog, if any
|
||||
AudioStatsDialog* audioStatsDialog = dialogsManager->getAudioStatsDialog();
|
||||
if(audioStatsDialog) {
|
||||
audioStatsDialog->update();
|
||||
}
|
||||
|
||||
// Update bandwidth dialog, if any
|
||||
BandwidthDialog* bandwidthDialog = dialogsManager->getBandwidthDialog();
|
||||
if (bandwidthDialog) {
|
||||
|
@ -5013,6 +5017,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
|
||||
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
|
||||
// Caches
|
||||
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
|
@ -5681,6 +5686,9 @@ void Application::updateDisplayMode() {
|
|||
// Make the switch atomic from the perspective of other threads
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_displayPluginLock);
|
||||
// Tell the desktop to no reposition (which requires plugin info), until we have set the new plugin, below.
|
||||
bool wasRepositionLocked = offscreenUi->getDesktop()->property("repositionLocked").toBool();
|
||||
offscreenUi->getDesktop()->setProperty("repositionLocked", true);
|
||||
|
||||
auto oldDisplayPlugin = _displayPlugin;
|
||||
if (_displayPlugin) {
|
||||
|
@ -5717,6 +5725,7 @@ void Application::updateDisplayMode() {
|
|||
_offscreenContext->makeCurrent();
|
||||
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
offscreenUi->getDesktop()->setProperty("repositionLocked", wasRepositionLocked);
|
||||
}
|
||||
|
||||
emit activeDisplayPluginChanged();
|
||||
|
|
|
@ -59,7 +59,6 @@
|
|||
#include "scripting/ControllerScriptingInterface.h"
|
||||
#include "scripting/DialogsManagerScriptingInterface.h"
|
||||
#include "ui/ApplicationOverlay.h"
|
||||
#include "ui/AudioStatsDialog.h"
|
||||
#include "ui/BandwidthDialog.h"
|
||||
#include "ui/LodToolsDialog.h"
|
||||
#include "ui/LogDialog.h"
|
||||
|
|
|
@ -654,10 +654,6 @@ Menu::Menu() {
|
|||
audioScopeFramesGroup->addAction(fiftyFrames);
|
||||
}
|
||||
|
||||
// Developer > Audio > Audio Network Stats...
|
||||
addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNetworkStats, 0,
|
||||
dialogsManager.data(), SLOT(audioStatsDetails()));
|
||||
|
||||
// Developer > Physics >>>
|
||||
MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics");
|
||||
{
|
||||
|
|
|
@ -37,7 +37,6 @@ namespace MenuOption {
|
|||
const QString AssetMigration = "ATP Asset Migration";
|
||||
const QString AssetServer = "Asset Browser";
|
||||
const QString Attachments = "Attachments...";
|
||||
const QString AudioNetworkStats = "Audio Network Stats";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
const QString AudioScope = "Show Scope";
|
||||
const QString AudioScopeFiftyFrames = "Fifty";
|
||||
|
|
|
@ -57,6 +57,20 @@ bool HMDScriptingInterface::isHandControllerAvailable() {
|
|||
return PluginUtils::isHandControllerAvailable();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::requestShowHandControllers() {
|
||||
_showHandControllersCount++;
|
||||
emit shouldShowHandControllersChanged();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::requestHideHandControllers() {
|
||||
_showHandControllersCount--;
|
||||
emit shouldShowHandControllersChanged();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::shouldShowHandControllers() const {
|
||||
return _showHandControllersCount > 0;
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 hudIntersection;
|
||||
auto instance = DependencyManager::get<HMDScriptingInterface>();
|
||||
|
|
|
@ -41,6 +41,10 @@ public:
|
|||
Q_INVOKABLE bool isHMDAvailable();
|
||||
Q_INVOKABLE bool isHandControllerAvailable();
|
||||
|
||||
Q_INVOKABLE void requestShowHandControllers();
|
||||
Q_INVOKABLE void requestHideHandControllers();
|
||||
Q_INVOKABLE bool shouldShowHandControllers() const;
|
||||
|
||||
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
|
||||
Q_INVOKABLE void disableHandLasers(int hands) const;
|
||||
|
||||
|
@ -65,6 +69,9 @@ public:
|
|||
// rotate the overlay UI sphere so that it is centered about the the current HMD position and orientation
|
||||
Q_INVOKABLE void centerUI();
|
||||
|
||||
signals:
|
||||
bool shouldShowHandControllersChanged();
|
||||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
@ -81,6 +88,7 @@ private:
|
|||
|
||||
bool getHUDLookAtPosition3D(glm::vec3& result) const;
|
||||
glm::mat4 getWorldHMDMatrix() const;
|
||||
std::atomic<int> _showHandControllersCount { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_HMDScriptingInterface_h
|
||||
|
|
|
@ -98,9 +98,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
|||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
if (!_uiTexture) {
|
||||
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D([](uint32_t recycleTexture, void* recycleFence){
|
||||
DependencyManager::get<OffscreenUi>()->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_uiTexture->setSource(__FUNCTION__);
|
||||
}
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
|
|
|
@ -1,296 +0,0 @@
|
|||
//
|
||||
// AudioStatsDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Bridget Went on 7/9/15.
|
||||
// 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 "AudioStatsDialog.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <AudioClient.h>
|
||||
#include <AudioConstants.h>
|
||||
#include <AudioIOStats.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <NodeList.h>
|
||||
#include <Util.h>
|
||||
|
||||
|
||||
|
||||
const unsigned COLOR0 = 0x33cc99ff;
|
||||
const unsigned COLOR1 = 0xffef40c0;
|
||||
const unsigned COLOR2 = 0xd0d0d0a0;
|
||||
const unsigned COLOR3 = 0x01DD7880;
|
||||
|
||||
|
||||
AudioStatsDisplay::AudioStatsDisplay(QFormLayout* form,
|
||||
QString text, unsigned colorRGBA) :
|
||||
_text(text),
|
||||
_colorRGBA(colorRGBA)
|
||||
{
|
||||
_label = new QLabel();
|
||||
_label->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QPalette palette = _label->palette();
|
||||
unsigned rgb = colorRGBA >> 8;
|
||||
rgb = ((rgb & 0xfefefeu) >> 1) + ((rgb & 0xf8f8f8) >> 3);
|
||||
palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb));
|
||||
_label->setPalette(palette);
|
||||
|
||||
form->addRow(_label);
|
||||
}
|
||||
|
||||
void AudioStatsDisplay::paint() {
|
||||
_label->setText(_strBuf);
|
||||
}
|
||||
|
||||
void AudioStatsDisplay::updatedDisplay(QString str) {
|
||||
_strBuf = str;
|
||||
}
|
||||
|
||||
|
||||
AudioStatsDialog::AudioStatsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint) {
|
||||
|
||||
setWindowTitle("Audio Network Statistics");
|
||||
|
||||
// Get statistics from the Audio Client
|
||||
_stats = &DependencyManager::get<AudioClient>()->getStats();
|
||||
|
||||
// Create layout
|
||||
_form = new QFormLayout();
|
||||
_form->setSizeConstraint(QLayout::SetFixedSize);
|
||||
|
||||
// Initialize channels' content (needed to correctly size channels)
|
||||
updateStats();
|
||||
|
||||
// Create channels
|
||||
_audioDisplayChannels = QVector<QVector<AudioStatsDisplay*>>(1);
|
||||
|
||||
_audioMixerID = addChannel(_form, _audioMixerStats, COLOR0);
|
||||
_upstreamClientID = addChannel(_form, _upstreamClientStats, COLOR1);
|
||||
_upstreamMixerID = addChannel(_form, _upstreamMixerStats, COLOR2);
|
||||
_downstreamID = addChannel(_form, _downstreamStats, COLOR3);
|
||||
_upstreamInjectedID = addChannel(_form, _upstreamInjectedStats, COLOR0);
|
||||
|
||||
// Initialize channels
|
||||
updateChannels();
|
||||
|
||||
// Future renders
|
||||
connect(averageUpdateTimer, SIGNAL(timeout()), this, SLOT(renderStats()));
|
||||
averageUpdateTimer->start(200);
|
||||
|
||||
// Initial render
|
||||
QDialog::setLayout(_form);
|
||||
}
|
||||
|
||||
int AudioStatsDialog::addChannel(QFormLayout* form, QVector<QString>& stats, const unsigned color) {
|
||||
|
||||
int channelID = _audioDisplayChannels.size() - 1;
|
||||
|
||||
for (int i = 0; i < stats.size(); i++)
|
||||
// Create new display label
|
||||
_audioDisplayChannels[channelID].push_back(new AudioStatsDisplay(form, stats.at(i), color));
|
||||
|
||||
// Expand vector to fit next channel
|
||||
_audioDisplayChannels.resize(_audioDisplayChannels.size() + 1);
|
||||
|
||||
return channelID;
|
||||
}
|
||||
|
||||
void AudioStatsDialog::renderStats() {
|
||||
updateStats();
|
||||
updateChannels();
|
||||
}
|
||||
|
||||
void AudioStatsDialog::updateChannels() {
|
||||
updateChannel(_audioMixerStats, _audioMixerID);
|
||||
updateChannel(_upstreamClientStats, _upstreamClientID);
|
||||
updateChannel(_upstreamMixerStats, _upstreamMixerID);
|
||||
updateChannel(_downstreamStats, _downstreamID);
|
||||
updateChannel(_upstreamInjectedStats, _upstreamInjectedID);
|
||||
}
|
||||
|
||||
void AudioStatsDialog::updateChannel(QVector<QString>& stats, int channelID) {
|
||||
// Update all stat displays at specified channel
|
||||
for (int i = 0; i < stats.size(); i++)
|
||||
_audioDisplayChannels[channelID].at(i)->updatedDisplay(stats.at(i));
|
||||
}
|
||||
|
||||
void AudioStatsDialog::updateStats() {
|
||||
|
||||
// Clear current stats from all vectors
|
||||
clearAllChannels();
|
||||
|
||||
double audioInputBufferLatency{ 0.0 };
|
||||
double inputRingBufferLatency{ 0.0 };
|
||||
double networkRoundtripLatency{ 0.0 };
|
||||
double mixerRingBufferLatency{ 0.0 };
|
||||
double outputRingBufferLatency{ 0.0 };
|
||||
double audioOutputBufferLatency{ 0.0 };
|
||||
|
||||
if (SharedNodePointer audioMixerNodePointer = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AudioMixer)) {
|
||||
audioInputBufferLatency = (double)_stats->getInputMsRead().getWindowMax();
|
||||
inputRingBufferLatency = (double)_stats->getInputMsUnplayed().getWindowMax();
|
||||
networkRoundtripLatency = (double)audioMixerNodePointer->getPingMs();
|
||||
mixerRingBufferLatency = (double)_stats->getMixerAvatarStreamStats()._unplayedMs;
|
||||
outputRingBufferLatency = (double)_stats->getMixerDownstreamStats()._unplayedMs;
|
||||
audioOutputBufferLatency = (double)_stats->getOutputMsUnplayed().getWindowMax();
|
||||
}
|
||||
|
||||
double totalLatency = audioInputBufferLatency + inputRingBufferLatency + mixerRingBufferLatency
|
||||
+ outputRingBufferLatency + audioOutputBufferLatency + networkRoundtripLatency;
|
||||
|
||||
QString stats;
|
||||
_audioMixerStats.push_back("PIPELINE (averaged over the past 10s)");
|
||||
stats = "Input Read:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(audioInputBufferLatency, 'f', 0)));
|
||||
stats = "Input Ring:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(inputRingBufferLatency, 'f', 0)));
|
||||
stats = "Network (client->mixer):\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(networkRoundtripLatency / 2, 'f', 0)));
|
||||
stats = "Mixer Ring:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(mixerRingBufferLatency, 'f', 0)));
|
||||
stats = "Network (mixer->client):\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(networkRoundtripLatency / 2, 'f', 0)));
|
||||
stats = "Output Ring:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(outputRingBufferLatency, 'f', 0)));
|
||||
stats = "Output Read:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(audioOutputBufferLatency, 'f', 0)));
|
||||
stats = "TOTAL:\t%1 ms";
|
||||
_audioMixerStats.push_back(stats.arg(QString::number(totalLatency, 'f', 0)));
|
||||
|
||||
const MovingMinMaxAvg<quint64>& packetSentTimeGaps = _stats->getPacketTimegaps();
|
||||
|
||||
_upstreamClientStats.push_back("\nUpstream Mic Audio Packets Sent Gaps (by client):");
|
||||
|
||||
stats = "Inter-packet timegaps";
|
||||
_upstreamClientStats.push_back(stats);
|
||||
stats = "overall min:\t%1, max:\t%2, avg:\t%3";
|
||||
stats = stats.arg(formatUsecTime(packetSentTimeGaps.getMin()),
|
||||
formatUsecTime(packetSentTimeGaps.getMax()),
|
||||
formatUsecTime(packetSentTimeGaps.getAverage()));
|
||||
_upstreamClientStats.push_back(stats);
|
||||
|
||||
stats = "last window min:\t%1, max:\t%2, avg:\t%3";
|
||||
stats = stats.arg(formatUsecTime(packetSentTimeGaps.getWindowMin()),
|
||||
formatUsecTime(packetSentTimeGaps.getWindowMax()),
|
||||
formatUsecTime(packetSentTimeGaps.getWindowAverage()));
|
||||
_upstreamClientStats.push_back(stats);
|
||||
|
||||
_upstreamMixerStats.push_back("\nMIXER STREAM");
|
||||
_upstreamMixerStats.push_back("(this client's remote mixer stream performance)");
|
||||
|
||||
renderAudioStreamStats(&_stats->getMixerAvatarStreamStats(), &_upstreamMixerStats);
|
||||
|
||||
_downstreamStats.push_back("\nCLIENT STREAM");
|
||||
|
||||
AudioStreamStats downstreamStats = _stats->getMixerDownstreamStats();
|
||||
|
||||
renderAudioStreamStats(&downstreamStats, &_downstreamStats);
|
||||
|
||||
|
||||
if (_shouldShowInjectedStreams) {
|
||||
|
||||
foreach(const AudioStreamStats& injectedStreamAudioStats, _stats->getMixerInjectedStreamStatsMap()) {
|
||||
stats = "\nINJECTED STREAM (ID: %1)";
|
||||
stats = stats.arg(injectedStreamAudioStats._streamIdentifier.toString());
|
||||
_upstreamInjectedStats.push_back(stats);
|
||||
|
||||
renderAudioStreamStats(&injectedStreamAudioStats, &_upstreamInjectedStats);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioStatsDialog::renderAudioStreamStats(const AudioStreamStats* streamStats, QVector<QString>* audioStreamStats) {
|
||||
|
||||
QString stats = "Packet Loss";
|
||||
audioStreamStats->push_back(stats);
|
||||
stats = "overall:\t%1%\t(%2 lost), window:\t%3%\t(%4 lost)";
|
||||
stats = stats.arg(QString::number((int)(streamStats->_packetStreamStats.getLostRate() * 100.0f)),
|
||||
QString::number((int)(streamStats->_packetStreamStats._lost)),
|
||||
QString::number((int)(streamStats->_packetStreamWindowStats.getLostRate() * 100.0f)),
|
||||
QString::number((int)(streamStats->_packetStreamWindowStats._lost)));
|
||||
audioStreamStats->push_back(stats);
|
||||
|
||||
stats = "Ringbuffer";
|
||||
audioStreamStats->push_back(stats);
|
||||
stats = "available frames (avg):\t%1\t(%2), desired:\t%3";
|
||||
stats = stats.arg(QString::number(streamStats->_framesAvailable),
|
||||
QString::number(streamStats->_framesAvailableAverage),
|
||||
QString::number(streamStats->_desiredJitterBufferFrames));
|
||||
audioStreamStats->push_back(stats);
|
||||
stats = "starves:\t%1, last starve duration:\t%2, drops:\t%3, overflows:\t%4";
|
||||
stats = stats.arg(QString::number(streamStats->_starveCount),
|
||||
QString::number(streamStats->_consecutiveNotMixedCount),
|
||||
QString::number(streamStats->_framesDropped),
|
||||
QString::number(streamStats->_overflowCount));
|
||||
audioStreamStats->push_back(stats);
|
||||
|
||||
stats = "Inter-packet timegaps";
|
||||
audioStreamStats->push_back(stats);
|
||||
|
||||
stats = "overall min:\t%1, max:\t%2, avg:\t%3";
|
||||
stats = stats.arg(formatUsecTime(streamStats->_timeGapMin),
|
||||
formatUsecTime(streamStats->_timeGapMax),
|
||||
formatUsecTime(streamStats->_timeGapAverage));
|
||||
audioStreamStats->push_back(stats);
|
||||
|
||||
|
||||
stats = "last window min:\t%1, max:\t%2, avg:\t%3";
|
||||
stats = stats.arg(formatUsecTime(streamStats->_timeGapWindowMin),
|
||||
formatUsecTime(streamStats->_timeGapWindowMax),
|
||||
formatUsecTime(streamStats->_timeGapWindowAverage));
|
||||
audioStreamStats->push_back(stats);
|
||||
}
|
||||
|
||||
void AudioStatsDialog::clearAllChannels() {
|
||||
_audioMixerStats.clear();
|
||||
_upstreamClientStats.clear();
|
||||
_upstreamMixerStats.clear();
|
||||
_downstreamStats.clear();
|
||||
_upstreamInjectedStats.clear();
|
||||
}
|
||||
|
||||
void AudioStatsDialog::paintEvent(QPaintEvent* event) {
|
||||
|
||||
// Repaint each stat in each channel
|
||||
for (int i = 0; i < _audioDisplayChannels.size(); i++) {
|
||||
for(int j = 0; j < _audioDisplayChannels[i].size(); j++) {
|
||||
_audioDisplayChannels[i].at(j)->paint();
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::paintEvent(event);
|
||||
}
|
||||
|
||||
void AudioStatsDialog::reject() {
|
||||
// Just regularly close upon ESC
|
||||
QDialog::close();
|
||||
}
|
||||
|
||||
void AudioStatsDialog::closeEvent(QCloseEvent* event) {
|
||||
QDialog::closeEvent(event);
|
||||
emit closed();
|
||||
}
|
||||
|
||||
AudioStatsDialog::~AudioStatsDialog() {
|
||||
clearAllChannels();
|
||||
for (int i = 0; i < _audioDisplayChannels.size(); i++) {
|
||||
_audioDisplayChannels[i].clear();
|
||||
for(int j = 0; j < _audioDisplayChannels[i].size(); j++) {
|
||||
delete _audioDisplayChannels[i].at(j);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
//
|
||||
// AudioStatsDialog.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Bridget Went on 7/9/15.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#ifndef __hifi__AudioStatsDialog__
|
||||
#define __hifi__AudioStatsDialog__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QFormLayout>
|
||||
#include <QVector>
|
||||
#include <QTimer>
|
||||
#include <QString>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class AudioIOStats;
|
||||
class AudioStreamStats;
|
||||
|
||||
//display
|
||||
class AudioStatsDisplay : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
public:
|
||||
AudioStatsDisplay(QFormLayout* form, QString text, unsigned colorRGBA);
|
||||
void updatedDisplay(QString str);
|
||||
void paint();
|
||||
|
||||
private:
|
||||
QString _strBuf;
|
||||
QLabel* _label;
|
||||
QString _text;
|
||||
unsigned _colorRGBA;
|
||||
|
||||
};
|
||||
|
||||
//dialog
|
||||
class AudioStatsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioStatsDialog(QWidget* parent);
|
||||
~AudioStatsDialog();
|
||||
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
// audio stats methods for rendering
|
||||
QVector<QString> _audioMixerStats;
|
||||
QVector<QString> _upstreamClientStats;
|
||||
QVector<QString> _upstreamMixerStats;
|
||||
QVector<QString> _downstreamStats;
|
||||
QVector<QString> _upstreamInjectedStats;
|
||||
|
||||
int _audioMixerID;
|
||||
int _upstreamClientID;
|
||||
int _upstreamMixerID;
|
||||
int _downstreamID;
|
||||
int _upstreamInjectedID;
|
||||
|
||||
QVector<QVector<AudioStatsDisplay*>> _audioDisplayChannels;
|
||||
|
||||
void updateStats();
|
||||
int addChannel(QFormLayout* form, QVector<QString>& stats, const unsigned color);
|
||||
void updateChannel(QVector<QString>& stats, const int channelID);
|
||||
void updateChannels();
|
||||
void clearAllChannels();
|
||||
void renderAudioStreamStats(const AudioStreamStats* streamStats, QVector<QString>* audioStreamstats);
|
||||
|
||||
|
||||
const AudioIOStats* _stats;
|
||||
QFormLayout* _form;
|
||||
|
||||
bool _shouldShowInjectedStreams{ false };
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
|
||||
void closed();
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
void reject() override;
|
||||
void renderStats();
|
||||
|
||||
protected:
|
||||
|
||||
// Emits a 'closed' signal when this dialog is closed.
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
QTimer* averageUpdateTimer = new QTimer(this);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif /* defined(__hifi__AudioStatsDialog__) */
|
||||
|
|
@ -103,20 +103,6 @@ void DialogsManager::cachesSizeDialog() {
|
|||
_cachesSizeDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::audioStatsDetails() {
|
||||
if (! _audioStatsDialog) {
|
||||
_audioStatsDialog = new AudioStatsDialog(qApp->getWindow());
|
||||
connect(_audioStatsDialog, SIGNAL(closed()), _audioStatsDialog, SLOT(deleteLater()));
|
||||
|
||||
if (_hmdToolsDialog) {
|
||||
_hmdToolsDialog->watchWindow(_audioStatsDialog->windowHandle());
|
||||
}
|
||||
|
||||
_audioStatsDialog->show();
|
||||
}
|
||||
_audioStatsDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::bandwidthDetails() {
|
||||
if (! _bandwidthDialog) {
|
||||
_bandwidthDialog = new BandwidthDialog(qApp->getWindow());
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
class AnimationsDialog;
|
||||
class AttachmentsDialog;
|
||||
class AudioStatsDialog;
|
||||
class BandwidthDialog;
|
||||
class CachesSizeDialog;
|
||||
class DiskCacheEditor;
|
||||
|
@ -35,7 +34,6 @@ class DialogsManager : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
QPointer<AudioStatsDialog> getAudioStatsDialog() const { return _audioStatsDialog; }
|
||||
QPointer<BandwidthDialog> getBandwidthDialog() const { return _bandwidthDialog; }
|
||||
QPointer<HMDToolsDialog> getHMDToolsDialog() const { return _hmdToolsDialog; }
|
||||
QPointer<LodToolsDialog> getLodToolsDialog() const { return _lodToolsDialog; }
|
||||
|
@ -52,7 +50,6 @@ public slots:
|
|||
void showLoginDialog();
|
||||
void octreeStatsDetails();
|
||||
void cachesSizeDialog();
|
||||
void audioStatsDetails();
|
||||
void bandwidthDetails();
|
||||
void lodTools();
|
||||
void hmdTools(bool showTools);
|
||||
|
@ -78,7 +75,6 @@ private:
|
|||
|
||||
QPointer<AnimationsDialog> _animationsDialog;
|
||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QPointer<AudioStatsDialog> _audioStatsDialog;
|
||||
QPointer<BandwidthDialog> _bandwidthDialog;
|
||||
QPointer<CachesSizeDialog> _cachesSizeDialog;
|
||||
QPointer<DiskCacheEditor> _diskCacheEditor;
|
||||
|
|
|
@ -111,7 +111,6 @@ void Stats::updateStats(bool force) {
|
|||
PerformanceTimer::setActive(shouldDisplayTimingDetail);
|
||||
}
|
||||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
// we need to take one avatar out so we don't include ourselves
|
||||
|
@ -287,6 +286,13 @@ void Stats::updateStats(bool force) {
|
|||
|
||||
STAT_UPDATE(gpuBuffers, (int)gpu::Context::getBufferGPUCount());
|
||||
STAT_UPDATE(gpuTextures, (int)gpu::Context::getTextureGPUCount());
|
||||
STAT_UPDATE(gpuTexturesSparse, (int)gpu::Context::getTextureGPUSparseCount());
|
||||
STAT_UPDATE(qmlTextureMemory, (int)BYTES_TO_MB(OffscreenQmlSurface::getUsedTextureMemory()));
|
||||
STAT_UPDATE(gpuTextureMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUMemoryUsage()));
|
||||
STAT_UPDATE(gpuTextureVirtualMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUVirtualMemoryUsage()));
|
||||
STAT_UPDATE(gpuTextureSparseMemory, (int)BYTES_TO_MB(gpu::Texture::getTextureGPUSparseMemoryUsage()));
|
||||
STAT_UPDATE(gpuSparseTextureEnabled, gpu::Texture::getEnableSparseTextures() ? 1 : 0);
|
||||
STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemory()));
|
||||
|
||||
// Incoming packets
|
||||
QLocale locale(QLocale::English);
|
||||
|
|
|
@ -28,8 +28,6 @@ class Stats : public QQuickItem {
|
|||
Q_PROPERTY(bool expanded READ isExpanded WRITE setExpanded NOTIFY expandedChanged)
|
||||
Q_PROPERTY(bool timingExpanded READ isTimingExpanded NOTIFY timingExpandedChanged)
|
||||
Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT)
|
||||
Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream)
|
||||
Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream)
|
||||
|
||||
STATS_PROPERTY(int, serverCount, 0)
|
||||
// How often the app is creating new gpu::Frames
|
||||
|
@ -91,6 +89,13 @@ class Stats : public QQuickItem {
|
|||
STATS_PROPERTY(int, localLeaves, 0)
|
||||
STATS_PROPERTY(int, gpuBuffers, 0)
|
||||
STATS_PROPERTY(int, gpuTextures, 0)
|
||||
STATS_PROPERTY(int, gpuTexturesSparse, 0)
|
||||
STATS_PROPERTY(int, qmlTextureMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureVirtualMemory, 0)
|
||||
STATS_PROPERTY(int, gpuTextureSparseMemory, 0)
|
||||
STATS_PROPERTY(int, gpuSparseTextureEnabled, 0)
|
||||
STATS_PROPERTY(int, gpuFreeMemory, 0)
|
||||
|
||||
public:
|
||||
static Stats* getInstance();
|
||||
|
@ -102,9 +107,6 @@ public:
|
|||
return _monospaceFont;
|
||||
}
|
||||
|
||||
float getAudioPacketLossUpstream() { return _audioStats->getMixerAvatarStreamStats()._packetStreamStats.getLostRate(); }
|
||||
float getAudioPacketLossDownstream() { return _audioStats->getMixerDownstreamStats()._packetStreamStats.getLostRate(); }
|
||||
|
||||
void updateStats(bool force = false);
|
||||
|
||||
bool isExpanded() { return _expanded; }
|
||||
|
@ -179,8 +181,15 @@ signals:
|
|||
void localInternalChanged();
|
||||
void localLeavesChanged();
|
||||
void timingStatsChanged();
|
||||
void qmlTextureMemoryChanged();
|
||||
void gpuBuffersChanged();
|
||||
void gpuTexturesChanged();
|
||||
void gpuTexturesSparseChanged();
|
||||
void gpuTextureMemoryChanged();
|
||||
void gpuTextureVirtualMemoryChanged();
|
||||
void gpuTextureSparseMemoryChanged();
|
||||
void gpuSparseTextureEnabledChanged();
|
||||
void gpuFreeMemoryChanged();
|
||||
|
||||
private:
|
||||
int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process
|
||||
|
|
|
@ -49,6 +49,8 @@ Web3DOverlay::~Web3DOverlay() {
|
|||
if (_webSurface) {
|
||||
_webSurface->pause();
|
||||
_webSurface->disconnect(_connection);
|
||||
|
||||
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
|
@ -111,9 +113,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D([webSurface](uint32_t recycleTexture, void* recycleFence) {
|
||||
webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
|
|
|
@ -148,7 +148,7 @@ public slots:
|
|||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
||||
void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); }
|
||||
void sendDownstreamAudioStatsPacket() { _stats.publish(); }
|
||||
void handleAudioInput();
|
||||
void handleRecordedAudioInput(const QByteArray& audio);
|
||||
void reset();
|
||||
|
|
|
@ -18,22 +18,24 @@
|
|||
|
||||
#include "AudioIOStats.h"
|
||||
|
||||
// This is called 5x/sec (see AudioStatsDialog), and we want it to log the last 5s
|
||||
static const int INPUT_READS_WINDOW = 25;
|
||||
static const int INPUT_UNPLAYED_WINDOW = 25;
|
||||
static const int OUTPUT_UNPLAYED_WINDOW = 25;
|
||||
// This is called 1x/sec (see AudioClient) and we want it to log the last 5s
|
||||
static const int INPUT_READS_WINDOW = 5;
|
||||
static const int INPUT_UNPLAYED_WINDOW = 5;
|
||||
static const int OUTPUT_UNPLAYED_WINDOW = 5;
|
||||
|
||||
static const int APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS = (int)(30.0f * 1000.0f / AudioConstants::NETWORK_FRAME_MSECS);
|
||||
|
||||
|
||||
AudioIOStats::AudioIOStats(MixedProcessedAudioStream* receivedAudioStream) :
|
||||
_receivedAudioStream(receivedAudioStream),
|
||||
_inputMsRead(0, INPUT_READS_WINDOW),
|
||||
_inputMsUnplayed(0, INPUT_UNPLAYED_WINDOW),
|
||||
_outputMsUnplayed(0, OUTPUT_UNPLAYED_WINDOW),
|
||||
_interface(new AudioStatsInterface(this)),
|
||||
_inputMsRead(1, INPUT_READS_WINDOW),
|
||||
_inputMsUnplayed(1, INPUT_UNPLAYED_WINDOW),
|
||||
_outputMsUnplayed(1, OUTPUT_UNPLAYED_WINDOW),
|
||||
_lastSentPacketTime(0),
|
||||
_packetTimegaps(0, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS)
|
||||
_packetTimegaps(1, APPROXIMATELY_30_SECONDS_OF_AUDIO_PACKETS),
|
||||
_receivedAudioStream(receivedAudioStream)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioIOStats::reset() {
|
||||
|
@ -44,11 +46,13 @@ void AudioIOStats::reset() {
|
|||
_outputMsUnplayed.reset();
|
||||
_packetTimegaps.reset();
|
||||
|
||||
_mixerAvatarStreamStats = AudioStreamStats();
|
||||
_mixerInjectedStreamStatsMap.clear();
|
||||
_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
|
||||
_interface->updateMixerStream(AudioStreamStats());
|
||||
_interface->updateClientStream(AudioStreamStats());
|
||||
_interface->updateInjectorStreams(QHash<QUuid, AudioStreamStats>());
|
||||
}
|
||||
|
||||
void AudioIOStats::sentPacket() {
|
||||
void AudioIOStats::sentPacket() const {
|
||||
// first time this is 0
|
||||
if (_lastSentPacketTime == 0) {
|
||||
_lastSentPacketTime = usecTimestampNow();
|
||||
|
@ -60,37 +64,13 @@ void AudioIOStats::sentPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
const MovingMinMaxAvg<float>& AudioIOStats::getInputMsRead() const {
|
||||
_inputMsRead.currentIntervalComplete();
|
||||
return _inputMsRead;
|
||||
}
|
||||
|
||||
const MovingMinMaxAvg<float>& AudioIOStats::getInputMsUnplayed() const {
|
||||
_inputMsUnplayed.currentIntervalComplete();
|
||||
return _inputMsUnplayed;
|
||||
}
|
||||
|
||||
const MovingMinMaxAvg<float>& AudioIOStats::getOutputMsUnplayed() const {
|
||||
_outputMsUnplayed.currentIntervalComplete();
|
||||
return _outputMsUnplayed;
|
||||
}
|
||||
|
||||
const MovingMinMaxAvg<quint64>& AudioIOStats::getPacketTimegaps() const {
|
||||
_packetTimegaps.currentIntervalComplete();
|
||||
return _packetTimegaps;
|
||||
}
|
||||
|
||||
const AudioStreamStats AudioIOStats::getMixerDownstreamStats() const {
|
||||
return _receivedAudioStream->getAudioStreamStats();
|
||||
}
|
||||
|
||||
void AudioIOStats::processStreamStatsPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
// parse the appendFlag, clear injected audio stream stats if 0
|
||||
quint8 appendFlag;
|
||||
message->readPrimitive(&appendFlag);
|
||||
|
||||
if (!appendFlag) {
|
||||
_mixerInjectedStreamStatsMap.clear();
|
||||
if (appendFlag & AudioStreamStats::START) {
|
||||
_injectorStreams.clear();
|
||||
}
|
||||
|
||||
// parse the number of stream stats structs to follow
|
||||
|
@ -103,14 +83,18 @@ void AudioIOStats::processStreamStatsPacket(QSharedPointer<ReceivedMessage> mess
|
|||
message->readPrimitive(&streamStats);
|
||||
|
||||
if (streamStats._streamType == PositionalAudioStream::Microphone) {
|
||||
_mixerAvatarStreamStats = streamStats;
|
||||
_interface->updateMixerStream(streamStats);
|
||||
} else {
|
||||
_mixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
|
||||
_injectorStreams[streamStats._streamIdentifier] = streamStats;
|
||||
}
|
||||
}
|
||||
|
||||
if (appendFlag & AudioStreamStats::END) {
|
||||
_interface->updateInjectorStreams(_injectorStreams);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioIOStats::sendDownstreamAudioStatsPacket() {
|
||||
void AudioIOStats::publish() {
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
|
||||
// call _receivedAudioStream's per-second callback
|
||||
|
@ -122,10 +106,15 @@ void AudioIOStats::sendDownstreamAudioStatsPacket() {
|
|||
return;
|
||||
}
|
||||
|
||||
quint8 appendFlag = 0;
|
||||
quint8 appendFlag = AudioStreamStats::START | AudioStreamStats::END;
|
||||
quint16 numStreamStatsToPack = 1;
|
||||
AudioStreamStats stats = _receivedAudioStream->getAudioStreamStats();
|
||||
|
||||
// update the interface
|
||||
_interface->updateLocalBuffers(_inputMsRead, _inputMsUnplayed, _outputMsUnplayed, _packetTimegaps);
|
||||
_interface->updateClientStream(stats);
|
||||
|
||||
// prepare a packet to the mixer
|
||||
int statsPacketSize = sizeof(appendFlag) + sizeof(numStreamStatsToPack) + sizeof(stats);
|
||||
auto statsPacket = NLPacket::create(PacketType::AudioStreamStats, statsPacketSize);
|
||||
|
||||
|
@ -137,7 +126,88 @@ void AudioIOStats::sendDownstreamAudioStatsPacket() {
|
|||
|
||||
// pack downstream audio stream stats
|
||||
statsPacket->writePrimitive(stats);
|
||||
|
||||
|
||||
// send packet
|
||||
nodeList->sendPacket(std::move(statsPacket), *audioMixer);
|
||||
}
|
||||
|
||||
AudioStreamStatsInterface::AudioStreamStatsInterface(QObject* parent) :
|
||||
QObject(parent) {}
|
||||
|
||||
void AudioStreamStatsInterface::updateStream(const AudioStreamStats& stats) {
|
||||
lossRate(stats._packetStreamStats.getLostRate());
|
||||
lossCount(stats._packetStreamStats._lost);
|
||||
lossRateWindow(stats._packetStreamWindowStats.getLostRate());
|
||||
lossCountWindow(stats._packetStreamWindowStats._lost);
|
||||
|
||||
framesDesired(stats._desiredJitterBufferFrames);
|
||||
framesAvailable(stats._framesAvailable);
|
||||
framesAvailableAvg(stats._framesAvailableAverage);
|
||||
|
||||
unplayedMsMax(stats._unplayedMs);
|
||||
|
||||
starveCount(stats._starveCount);
|
||||
lastStarveDurationCount(stats._consecutiveNotMixedCount);
|
||||
dropCount(stats._framesDropped);
|
||||
overflowCount(stats._overflowCount);
|
||||
|
||||
timegapMsMax(stats._timeGapMax / USECS_PER_MSEC);
|
||||
timegapMsAvg(stats._timeGapAverage / USECS_PER_MSEC);
|
||||
timegapMsMaxWindow(stats._timeGapWindowMax / USECS_PER_MSEC);
|
||||
timegapMsAvgWindow(stats._timeGapWindowAverage / USECS_PER_MSEC);
|
||||
}
|
||||
|
||||
AudioStatsInterface::AudioStatsInterface(QObject* parent) :
|
||||
QObject(parent),
|
||||
_client(new AudioStreamStatsInterface(this)),
|
||||
_mixer(new AudioStreamStatsInterface(this)),
|
||||
_injectors(new QObject(this)) {}
|
||||
|
||||
|
||||
void AudioStatsInterface::updateLocalBuffers(const MovingMinMaxAvg<float>& inputMsRead,
|
||||
const MovingMinMaxAvg<float>& inputMsUnplayed,
|
||||
const MovingMinMaxAvg<float>& outputMsUnplayed,
|
||||
const MovingMinMaxAvg<quint64>& timegaps) {
|
||||
if (SharedNodePointer audioNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AudioMixer)) {
|
||||
pingMs(audioNode->getPingMs());
|
||||
}
|
||||
|
||||
inputReadMsMax(inputMsRead.getWindowMax());
|
||||
inputUnplayedMsMax(inputMsUnplayed.getWindowMax());
|
||||
outputUnplayedMsMax(outputMsUnplayed.getWindowMax());
|
||||
|
||||
sentTimegapMsMax(timegaps.getMax() / USECS_PER_MSEC);
|
||||
sentTimegapMsAvg(timegaps.getAverage() / USECS_PER_MSEC);
|
||||
sentTimegapMsMaxWindow(timegaps.getWindowMax() / USECS_PER_MSEC);
|
||||
sentTimegapMsAvgWindow(timegaps.getWindowAverage() / USECS_PER_MSEC);
|
||||
}
|
||||
|
||||
void AudioStatsInterface::updateInjectorStreams(const QHash<QUuid, AudioStreamStats>& stats) {
|
||||
// Get existing injectors
|
||||
auto injectorIds = _injectors->dynamicPropertyNames();
|
||||
|
||||
// Go over reported injectors
|
||||
QHash<QUuid, AudioStreamStats>::const_iterator injector = stats.constBegin();
|
||||
while (injector != stats.constEnd()) {
|
||||
const auto id = injector.key().toByteArray();
|
||||
// Mark existing injector (those left will be removed)
|
||||
injectorIds.removeOne(id);
|
||||
auto injectorProperty = _injectors->property(id);
|
||||
// Add new injector
|
||||
if (!injectorProperty.isValid()) {
|
||||
injectorProperty = QVariant::fromValue(new AudioStreamStatsInterface(this));
|
||||
_injectors->setProperty(id, injectorProperty);
|
||||
}
|
||||
// Update property with reported injector
|
||||
injectorProperty.value<AudioStreamStatsInterface*>()->updateStream(injector.value());
|
||||
++injector;
|
||||
}
|
||||
|
||||
// Remove unreported injectors
|
||||
for (auto& id : injectorIds) {
|
||||
_injectors->property(id).value<AudioStreamStatsInterface*>()->deleteLater();
|
||||
_injectors->setProperty(id, QVariant());
|
||||
}
|
||||
|
||||
emit injectorStreamsChanged();
|
||||
}
|
||||
|
|
|
@ -22,44 +22,124 @@
|
|||
|
||||
class MixedProcessedAudioStream;
|
||||
|
||||
#define AUDIO_PROPERTY(TYPE, NAME) \
|
||||
Q_PROPERTY(TYPE NAME READ NAME NOTIFY NAME##Changed) \
|
||||
public: \
|
||||
TYPE NAME() const { return _##NAME; } \
|
||||
void NAME(TYPE value) { \
|
||||
if (_##NAME != value) { \
|
||||
_##NAME = value; \
|
||||
emit NAME##Changed(value); \
|
||||
} \
|
||||
} \
|
||||
Q_SIGNAL void NAME##Changed(TYPE value); \
|
||||
private: \
|
||||
TYPE _##NAME{ (TYPE)0 };
|
||||
|
||||
class AudioStreamStatsInterface : public QObject {
|
||||
Q_OBJECT
|
||||
AUDIO_PROPERTY(float, lossRate)
|
||||
AUDIO_PROPERTY(float, lossCount)
|
||||
AUDIO_PROPERTY(float, lossRateWindow)
|
||||
AUDIO_PROPERTY(float, lossCountWindow)
|
||||
|
||||
AUDIO_PROPERTY(int, framesDesired)
|
||||
AUDIO_PROPERTY(int, framesAvailable)
|
||||
AUDIO_PROPERTY(int, framesAvailableAvg)
|
||||
AUDIO_PROPERTY(float, unplayedMsMax)
|
||||
|
||||
AUDIO_PROPERTY(int, starveCount)
|
||||
AUDIO_PROPERTY(int, lastStarveDurationCount)
|
||||
AUDIO_PROPERTY(int, dropCount)
|
||||
AUDIO_PROPERTY(int, overflowCount)
|
||||
|
||||
AUDIO_PROPERTY(quint64, timegapMsMax)
|
||||
AUDIO_PROPERTY(quint64, timegapMsAvg)
|
||||
AUDIO_PROPERTY(quint64, timegapMsMaxWindow)
|
||||
AUDIO_PROPERTY(quint64, timegapMsAvgWindow)
|
||||
|
||||
public:
|
||||
void updateStream(const AudioStreamStats& stats);
|
||||
|
||||
private:
|
||||
friend class AudioStatsInterface;
|
||||
AudioStreamStatsInterface(QObject* parent);
|
||||
};
|
||||
|
||||
class AudioStatsInterface : public QObject {
|
||||
Q_OBJECT
|
||||
AUDIO_PROPERTY(float, pingMs);
|
||||
|
||||
AUDIO_PROPERTY(float, inputReadMsMax);
|
||||
AUDIO_PROPERTY(float, inputUnplayedMsMax);
|
||||
AUDIO_PROPERTY(float, outputUnplayedMsMax);
|
||||
|
||||
AUDIO_PROPERTY(quint64, sentTimegapMsMax);
|
||||
AUDIO_PROPERTY(quint64, sentTimegapMsAvg);
|
||||
AUDIO_PROPERTY(quint64, sentTimegapMsMaxWindow);
|
||||
AUDIO_PROPERTY(quint64, sentTimegapMsAvgWindow);
|
||||
|
||||
Q_PROPERTY(AudioStreamStatsInterface* mixerStream READ getMixerStream NOTIFY mixerStreamChanged);
|
||||
Q_PROPERTY(AudioStreamStatsInterface* clientStream READ getClientStream NOTIFY clientStreamChanged);
|
||||
Q_PROPERTY(QObject* injectorStreams READ getInjectorStreams NOTIFY injectorStreamsChanged);
|
||||
|
||||
public:
|
||||
AudioStreamStatsInterface* getMixerStream() const { return _mixer; }
|
||||
AudioStreamStatsInterface* getClientStream() const { return _client; }
|
||||
QObject* getInjectorStreams() const { return _injectors; }
|
||||
|
||||
void updateLocalBuffers(const MovingMinMaxAvg<float>& inputMsRead,
|
||||
const MovingMinMaxAvg<float>& inputMsUnplayed,
|
||||
const MovingMinMaxAvg<float>& outputMsUnplayed,
|
||||
const MovingMinMaxAvg<quint64>& timegaps);
|
||||
void updateMixerStream(const AudioStreamStats& stats) { _mixer->updateStream(stats); emit mixerStreamChanged(); }
|
||||
void updateClientStream(const AudioStreamStats& stats) { _client->updateStream(stats); emit clientStreamChanged(); }
|
||||
void updateInjectorStreams(const QHash<QUuid, AudioStreamStats>& stats);
|
||||
|
||||
signals:
|
||||
void mixerStreamChanged();
|
||||
void clientStreamChanged();
|
||||
void injectorStreamsChanged();
|
||||
|
||||
private:
|
||||
friend class AudioIOStats;
|
||||
AudioStatsInterface(QObject* parent);
|
||||
AudioStreamStatsInterface* _client;
|
||||
AudioStreamStatsInterface* _mixer;
|
||||
QObject* _injectors;
|
||||
};
|
||||
|
||||
class AudioIOStats : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioIOStats(MixedProcessedAudioStream* receivedAudioStream);
|
||||
|
||||
void reset();
|
||||
|
||||
void updateInputMsRead(float ms) { _inputMsRead.update(ms); }
|
||||
void updateInputMsUnplayed(float ms) { _inputMsUnplayed.update(ms); }
|
||||
void updateOutputMsUnplayed(float ms) { _outputMsUnplayed.update(ms); }
|
||||
void sentPacket();
|
||||
|
||||
const MovingMinMaxAvg<float>& getInputMsRead() const;
|
||||
const MovingMinMaxAvg<float>& getInputMsUnplayed() const;
|
||||
const MovingMinMaxAvg<float>& getOutputMsUnplayed() const;
|
||||
const MovingMinMaxAvg<quint64>& getPacketTimegaps() const;
|
||||
|
||||
const AudioStreamStats getMixerDownstreamStats() const;
|
||||
const AudioStreamStats& getMixerAvatarStreamStats() const { return _mixerAvatarStreamStats; }
|
||||
const QHash<QUuid, AudioStreamStats>& getMixerInjectedStreamStatsMap() const { return _mixerInjectedStreamStatsMap; }
|
||||
|
||||
void sendDownstreamAudioStatsPacket();
|
||||
void reset();
|
||||
|
||||
AudioStatsInterface* data() const { return _interface; }
|
||||
|
||||
void updateInputMsRead(float ms) const { _inputMsRead.update(ms); }
|
||||
void updateInputMsUnplayed(float ms) const { _inputMsUnplayed.update(ms); }
|
||||
void updateOutputMsUnplayed(float ms) const { _outputMsUnplayed.update(ms); }
|
||||
void sentPacket() const;
|
||||
|
||||
void publish();
|
||||
|
||||
public slots:
|
||||
void processStreamStatsPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
MixedProcessedAudioStream* _receivedAudioStream;
|
||||
AudioStatsInterface* _interface;
|
||||
|
||||
mutable MovingMinMaxAvg<float> _inputMsRead;
|
||||
mutable MovingMinMaxAvg<float> _inputMsUnplayed;
|
||||
mutable MovingMinMaxAvg<float> _outputMsUnplayed;
|
||||
|
||||
quint64 _lastSentPacketTime;
|
||||
mutable quint64 _lastSentPacketTime;
|
||||
mutable MovingMinMaxAvg<quint64> _packetTimegaps;
|
||||
|
||||
AudioStreamStats _mixerAvatarStreamStats;
|
||||
QHash<QUuid, AudioStreamStats> _mixerInjectedStreamStatsMap;
|
||||
|
||||
MixedProcessedAudioStream* _receivedAudioStream;
|
||||
QHash<QUuid, AudioStreamStats> _injectorStreams;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioIOStats_h
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
|
||||
class AudioStreamStats {
|
||||
public:
|
||||
// Intermediate packets should have no flag set
|
||||
// Unique packets should have both flags set
|
||||
enum AppendFlag : quint8 {
|
||||
START = 1,
|
||||
END = 2
|
||||
};
|
||||
|
||||
AudioStreamStats()
|
||||
: _streamType(-1),
|
||||
_streamIdentifier(),
|
||||
|
|
|
@ -471,8 +471,8 @@ float calculateRepeatedFrameFadeFactor(int indexOfRepeat) {
|
|||
const float INITIAL_MSECS_NO_FADE = 20.0f;
|
||||
const float MSECS_FADE_TO_ZERO = 320.0f;
|
||||
|
||||
const float INITIAL_FRAMES_NO_FADE = INITIAL_MSECS_NO_FADE * AudioConstants::NETWORK_FRAME_MSECS;
|
||||
const float FRAMES_FADE_TO_ZERO = MSECS_FADE_TO_ZERO * AudioConstants::NETWORK_FRAME_MSECS;
|
||||
const float INITIAL_FRAMES_NO_FADE = INITIAL_MSECS_NO_FADE / AudioConstants::NETWORK_FRAME_MSECS;
|
||||
const float FRAMES_FADE_TO_ZERO = MSECS_FADE_TO_ZERO / AudioConstants::NETWORK_FRAME_MSECS;
|
||||
|
||||
const float SAMPLE_RANGE = std::numeric_limits<int16_t>::max();
|
||||
|
||||
|
@ -480,8 +480,6 @@ float calculateRepeatedFrameFadeFactor(int indexOfRepeat) {
|
|||
return 1.0f;
|
||||
} else if (indexOfRepeat <= INITIAL_FRAMES_NO_FADE + FRAMES_FADE_TO_ZERO) {
|
||||
return pow(SAMPLE_RANGE, -(indexOfRepeat - INITIAL_FRAMES_NO_FADE) / FRAMES_FADE_TO_ZERO);
|
||||
|
||||
//return 1.0f - ((indexOfRepeat - INITIAL_FRAMES_NO_FADE) / FRAMES_FADE_TO_ZERO);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
|
|
@ -630,6 +630,8 @@ void OpenGLDisplayPlugin::present() {
|
|||
PROFILE_RANGE_EX("internalPresent", 0xff00ffff, (uint64_t)presentCount())
|
||||
internalPresent();
|
||||
}
|
||||
|
||||
gpu::Backend::setFreeGPUMemory(gpu::gl::getFreeDedicatedMemory());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -201,10 +201,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
auto recycler = [webSurface] (uint32_t recycleTexture, void* recycleFence) {
|
||||
webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
};
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D(recycler));
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda()));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
|
|
|
@ -8,9 +8,8 @@
|
|||
#include "OffscreenQmlSurface.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtQml/QtQml>
|
||||
|
@ -37,9 +36,159 @@
|
|||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
#include "TextureRecycler.h"
|
||||
#include "Context.h"
|
||||
|
||||
struct TextureSet {
|
||||
// The number of surfaces with this size
|
||||
size_t count { 0 };
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> returnedTextures;
|
||||
};
|
||||
|
||||
uint64_t uvec2ToUint64(const uvec2& v) {
|
||||
uint64_t result = v.x;
|
||||
result <<= 32;
|
||||
result |= v.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
class OffscreenTextures {
|
||||
public:
|
||||
GLuint getNextTexture(const uvec2& size) {
|
||||
assert(QThread::currentThread() == qApp->thread());
|
||||
|
||||
recycle();
|
||||
|
||||
++_activeTextureCount;
|
||||
auto sizeKey = uvec2ToUint64(size);
|
||||
assert(_textures.count(sizeKey));
|
||||
auto& textureSet = _textures[sizeKey];
|
||||
if (!textureSet.returnedTextures.empty()) {
|
||||
auto textureAndFence = textureSet.returnedTextures.front();
|
||||
textureSet.returnedTextures.pop_front();
|
||||
waitOnFence(static_cast<GLsync>(textureAndFence.second));
|
||||
return textureAndFence.first;
|
||||
}
|
||||
|
||||
return createTexture(size);
|
||||
}
|
||||
|
||||
void releaseSize(const uvec2& size) {
|
||||
assert(QThread::currentThread() == qApp->thread());
|
||||
auto sizeKey = uvec2ToUint64(size);
|
||||
assert(_textures.count(sizeKey));
|
||||
auto& textureSet = _textures[sizeKey];
|
||||
if (0 == --textureSet.count) {
|
||||
for (const auto& textureAndFence : textureSet.returnedTextures) {
|
||||
destroy(textureAndFence);
|
||||
}
|
||||
_textures.erase(sizeKey);
|
||||
}
|
||||
}
|
||||
|
||||
void acquireSize(const uvec2& size) {
|
||||
assert(QThread::currentThread() == qApp->thread());
|
||||
auto sizeKey = uvec2ToUint64(size);
|
||||
auto& textureSet = _textures[sizeKey];
|
||||
++textureSet.count;
|
||||
}
|
||||
|
||||
// May be called on any thread
|
||||
void releaseTexture(const OffscreenQmlSurface::TextureAndFence & textureAndFence) {
|
||||
--_activeTextureCount;
|
||||
Lock lock(_mutex);
|
||||
_returnedTextures.push_back(textureAndFence);
|
||||
}
|
||||
|
||||
void report() {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if ((now - _lastReport) > USECS_PER_SECOND * 5) {
|
||||
_lastReport = now;
|
||||
qCDebug(glLogging) << "Current offscreen texture count " << _allTextureCount;
|
||||
qCDebug(glLogging) << "Current offscreen active texture count " << _activeTextureCount;
|
||||
}
|
||||
}
|
||||
|
||||
size_t getUsedTextureMemory() { return _totalTextureUsage; }
|
||||
private:
|
||||
static void waitOnFence(GLsync fence) {
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
}
|
||||
|
||||
static size_t getMemoryForSize(const uvec2& size) {
|
||||
// Base size + mips
|
||||
return static_cast<size_t>(((size.x * size.y) << 2) * 1.33f);
|
||||
}
|
||||
|
||||
void destroyTexture(GLuint texture) {
|
||||
--_allTextureCount;
|
||||
auto size = _textureSizes[texture];
|
||||
assert(getMemoryForSize(size) <= _totalTextureUsage);
|
||||
_totalTextureUsage -= getMemoryForSize(size);
|
||||
_textureSizes.erase(texture);
|
||||
glDeleteTextures(1, &texture);
|
||||
}
|
||||
|
||||
void destroy(const OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
waitOnFence(static_cast<GLsync>(textureAndFence.second));
|
||||
destroyTexture(textureAndFence.first);
|
||||
}
|
||||
|
||||
GLuint createTexture(const uvec2& size) {
|
||||
// Need a new texture
|
||||
uint32_t newTexture;
|
||||
glGenTextures(1, &newTexture);
|
||||
++_allTextureCount;
|
||||
_textureSizes[newTexture] = size;
|
||||
_totalTextureUsage += getMemoryForSize(size);
|
||||
glBindTexture(GL_TEXTURE_2D, newTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
return newTexture;
|
||||
}
|
||||
|
||||
void recycle() {
|
||||
assert(QThread::currentThread() == qApp->thread());
|
||||
// First handle any global returns
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> returnedTextures;
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
returnedTextures.swap(_returnedTextures);
|
||||
}
|
||||
|
||||
for (auto textureAndFence : returnedTextures) {
|
||||
GLuint texture = textureAndFence.first;
|
||||
uvec2 size = _textureSizes[texture];
|
||||
auto sizeKey = uvec2ToUint64(size);
|
||||
// Textures can be returned after all surfaces of the given size have been destroyed,
|
||||
// in which case we just destroy the texture
|
||||
if (!_textures.count(sizeKey)) {
|
||||
destroy(textureAndFence);
|
||||
continue;
|
||||
}
|
||||
_textures[sizeKey].returnedTextures.push_back(textureAndFence);
|
||||
}
|
||||
}
|
||||
|
||||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
std::atomic<int> _allTextureCount;
|
||||
std::atomic<int> _activeTextureCount;
|
||||
std::unordered_map<uint64_t, TextureSet> _textures;
|
||||
std::unordered_map<GLuint, uvec2> _textureSizes;
|
||||
Mutex _mutex;
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> _returnedTextures;
|
||||
uint64_t _lastReport { 0 };
|
||||
size_t _totalTextureUsage { 0 };
|
||||
} offscreenTextures;
|
||||
|
||||
class UrlHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -79,6 +228,10 @@ private:
|
|||
friend class OffscreenQmlSurface;
|
||||
};
|
||||
|
||||
size_t OffscreenQmlSurface::getUsedTextureMemory() {
|
||||
return offscreenTextures.getUsedTextureMemory();
|
||||
}
|
||||
|
||||
class QmlNetworkAccessManager : public NetworkAccessManager {
|
||||
public:
|
||||
friend class QmlNetworkAccessManagerFactory;
|
||||
|
@ -98,31 +251,10 @@ QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) {
|
|||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||
|
||||
void OffscreenQmlSurface::setupFbo() {
|
||||
_canvas->makeCurrent();
|
||||
_textures.setSize(_size);
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
_depthStencil = 0;
|
||||
}
|
||||
glGenRenderbuffers(1, &_depthStencil);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _size.x, _size.y);
|
||||
|
||||
if (_fbo) {
|
||||
glDeleteFramebuffers(1, &_fbo);
|
||||
_fbo = 0;
|
||||
}
|
||||
glGenFramebuffers(1, &_fbo);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::cleanup() {
|
||||
_canvas->makeCurrent();
|
||||
|
||||
_renderControl->invalidate();
|
||||
delete _renderControl; // and invalidate
|
||||
|
||||
if (_depthStencil) {
|
||||
|
@ -134,7 +266,8 @@ void OffscreenQmlSurface::cleanup() {
|
|||
_fbo = 0;
|
||||
}
|
||||
|
||||
_textures.clear();
|
||||
offscreenTextures.releaseSize(_size);
|
||||
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
|
@ -148,26 +281,7 @@ void OffscreenQmlSurface::render() {
|
|||
_renderControl->sync();
|
||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
||||
|
||||
// Clear out any pending textures to be returned
|
||||
{
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> returnedTextures;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
returnedTextures.swap(_returnedTextures);
|
||||
}
|
||||
if (!returnedTextures.empty()) {
|
||||
for (const auto& textureAndFence : returnedTextures) {
|
||||
GLsync fence = static_cast<GLsync>(textureAndFence.second);
|
||||
if (fence) {
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
}
|
||||
_textures.recycleTexture(textureAndFence.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLuint texture = _textures.getNextTexture();
|
||||
GLuint texture = offscreenTextures.getNextTexture(_size);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
|
@ -177,12 +291,11 @@ void OffscreenQmlSurface::render() {
|
|||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
_textures.recycleTexture(_latestTextureAndFence.first);
|
||||
glDeleteSync(static_cast<GLsync>(_latestTextureAndFence.second));
|
||||
offscreenTextures.releaseTexture(_latestTextureAndFence);
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
}
|
||||
|
||||
|
@ -199,7 +312,6 @@ void OffscreenQmlSurface::render() {
|
|||
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) {
|
||||
textureAndFence = { 0, 0 };
|
||||
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 == _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
|
@ -210,20 +322,18 @@ bool OffscreenQmlSurface::fetchTexture(TextureAndFence& textureAndFence) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& textureAndFence) {
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
_returnedTextures.push_back(textureAndFence);
|
||||
std::function<void(uint32_t, void*)> OffscreenQmlSurface::getDiscardLambda() {
|
||||
return [](uint32_t texture, void* fence) {
|
||||
offscreenTextures.releaseTexture({ texture, static_cast<GLsync>(fence) });
|
||||
};
|
||||
}
|
||||
|
||||
bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) {
|
||||
// If we already have a pending texture, don't render another one
|
||||
// i.e. don't render faster than the consumer context, since it wastes
|
||||
// GPU cycles on producing output that will never be seen
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 != _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
if (0 != _latestTextureAndFence.first) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto minRenderInterval = USECS_PER_SECOND / fps;
|
||||
|
@ -307,7 +417,6 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
}
|
||||
_glData = ::getGLContextData();
|
||||
_renderControl->initialize(_canvas->getContext());
|
||||
setupFbo();
|
||||
|
||||
// When Quick says there is a need to render, we will not render immediately. Instead,
|
||||
// a timer with a small interval is used to get better performance.
|
||||
|
@ -367,9 +476,40 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
}
|
||||
|
||||
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height() << " with pixel ratio " << pixelRatio;
|
||||
|
||||
_canvas->makeCurrent();
|
||||
|
||||
// Release hold on the textures of the old size
|
||||
if (uvec2() != _size) {
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
offscreenTextures.releaseTexture(_latestTextureAndFence);
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
}
|
||||
offscreenTextures.releaseSize(_size);
|
||||
}
|
||||
|
||||
_size = newOffscreenSize;
|
||||
_textures.setSize(_size);
|
||||
setupFbo();
|
||||
|
||||
// Acquire the new texture size
|
||||
if (uvec2() != _size) {
|
||||
offscreenTextures.acquireSize(_size);
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
_depthStencil = 0;
|
||||
}
|
||||
glGenRenderbuffers(1, &_depthStencil);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _size.x, _size.y);
|
||||
if (!_fbo) {
|
||||
glGenFramebuffers(1, &_fbo);
|
||||
}
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
_canvas->doneCurrent();
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||
|
@ -421,7 +561,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
|
||||
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
} else {
|
||||
qWarning() << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
qCWarning(glLogging) << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
}
|
||||
|
||||
QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
|
||||
|
@ -429,7 +569,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
if (_qmlComponent->isError()) {
|
||||
QList<QQmlError> errorList = _qmlComponent->errors();
|
||||
foreach(const QQmlError& error, errorList)
|
||||
qWarning() << error.url() << error.line() << error;
|
||||
qCWarning(glLogging) << error.url() << error.line() << error;
|
||||
if (!_rootItem) {
|
||||
qFatal("Unable to finish loading QML root");
|
||||
}
|
||||
|
@ -474,6 +614,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
}
|
||||
|
||||
void OffscreenQmlSurface::updateQuick() {
|
||||
offscreenTextures.report();
|
||||
// If we're
|
||||
// a) not set up
|
||||
// b) already rendering a frame
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
#define hifi_OffscreenQmlSurface_h
|
||||
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <ThreadHelpers.h>
|
||||
#include "TextureRecycler.h"
|
||||
|
||||
class QWindow;
|
||||
class QMyQuickRenderControl;
|
||||
|
@ -30,6 +30,11 @@ class QQmlContext;
|
|||
class QQmlComponent;
|
||||
class QQuickWindow;
|
||||
class QQuickItem;
|
||||
|
||||
// GPU resources are typically buffered for one copy being used by the renderer,
|
||||
// one copy in flight, and one copy being used by the receiver
|
||||
#define GPU_RESOURCE_BUFFER_SIZE 3
|
||||
|
||||
class OffscreenQmlSurface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
||||
|
@ -82,9 +87,9 @@ public:
|
|||
// when the texture is safe to read.
|
||||
// Returns false if no new texture is available
|
||||
bool fetchTexture(TextureAndFence& textureAndFence);
|
||||
// Release a previously acquired texture, along with a fence which indicates when reads from the
|
||||
// texture have completed.
|
||||
void releaseTexture(const TextureAndFence& textureAndFence);
|
||||
|
||||
static std::function<void(uint32_t, void*)> getDiscardLambda();
|
||||
static size_t getUsedTextureMemory();
|
||||
|
||||
signals:
|
||||
void focusObjectChanged(QObject* newFocus);
|
||||
|
@ -133,14 +138,10 @@ private:
|
|||
uint32_t _fbo { 0 };
|
||||
uint32_t _depthStencil { 0 };
|
||||
uint64_t _lastRenderTime { 0 };
|
||||
uvec2 _size { 1920, 1080 };
|
||||
TextureRecycler _textures { true };
|
||||
uvec2 _size;
|
||||
|
||||
// Texture management
|
||||
std::mutex _textureMutex;
|
||||
TextureAndFence _latestTextureAndFence { 0, 0 };
|
||||
std::list<TextureAndFence> _returnedTextures;
|
||||
|
||||
|
||||
bool _render { false };
|
||||
bool _polish { true };
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-10-05
|
||||
// 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 "TextureRecycler.h"
|
||||
#include "Config.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
|
||||
void TextureRecycler::setSize(const uvec2& size) {
|
||||
if (size == _size) {
|
||||
return;
|
||||
}
|
||||
_size = size;
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
std::set<Map::key_type> toDelete;
|
||||
std::for_each(_allTextures.begin(), _allTextures.end(), [&](Map::const_reference item) {
|
||||
if (!item.second._active && item.second._size != _size) {
|
||||
toDelete.insert(item.first);
|
||||
}
|
||||
});
|
||||
std::for_each(toDelete.begin(), toDelete.end(), [&](Map::key_type key) {
|
||||
_allTextures.erase(key);
|
||||
});
|
||||
}
|
||||
|
||||
void TextureRecycler::clear() {
|
||||
while (!_readyTextures.empty()) {
|
||||
_readyTextures.pop();
|
||||
}
|
||||
_allTextures.clear();
|
||||
}
|
||||
|
||||
void TextureRecycler::addTexture() {
|
||||
uint32_t newTexture;
|
||||
glGenTextures(1, &newTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, newTexture);
|
||||
if (_useMipmaps) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
} else {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
_allTextures.emplace(std::piecewise_construct, std::forward_as_tuple(newTexture), std::forward_as_tuple(newTexture, _size));
|
||||
_readyTextures.push(newTexture);
|
||||
}
|
||||
|
||||
uint32_t TextureRecycler::getNextTexture() {
|
||||
while (_allTextures.size() < _textureCount) {
|
||||
addTexture();
|
||||
}
|
||||
|
||||
if (_readyTextures.empty()) {
|
||||
addTexture();
|
||||
}
|
||||
|
||||
uint32_t result = _readyTextures.front();
|
||||
_readyTextures.pop();
|
||||
auto& item = _allTextures[result];
|
||||
item._active = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void TextureRecycler::recycleTexture(GLuint texture) {
|
||||
Q_ASSERT(_allTextures.count(texture));
|
||||
auto& item = _allTextures[texture];
|
||||
Q_ASSERT(item._active);
|
||||
item._active = false;
|
||||
if (item._size != _size) {
|
||||
// Buh-bye
|
||||
_allTextures.erase(texture);
|
||||
return;
|
||||
}
|
||||
|
||||
_readyTextures.push(item._tex);
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2015-04-04
|
||||
// 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_TextureRecycler_h
|
||||
#define hifi_TextureRecycler_h
|
||||
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
// GPU resources are typically buffered for one copy being used by the renderer,
|
||||
// one copy in flight, and one copy being used by the receiver
|
||||
#define GPU_RESOURCE_BUFFER_SIZE 3
|
||||
|
||||
class TextureRecycler {
|
||||
public:
|
||||
TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {}
|
||||
void setSize(const uvec2& size);
|
||||
void setTextureCount(uint8_t textureCount);
|
||||
void clear();
|
||||
uint32_t getNextTexture();
|
||||
void recycleTexture(uint32_t texture);
|
||||
|
||||
private:
|
||||
void addTexture();
|
||||
|
||||
struct TexInfo {
|
||||
const uint32_t _tex{ 0 };
|
||||
const uvec2 _size;
|
||||
bool _active { false };
|
||||
|
||||
TexInfo() {}
|
||||
TexInfo(uint32_t tex, const uvec2& size) : _tex(tex), _size(size) {}
|
||||
TexInfo(const TexInfo& other) : _tex(other._tex), _size(other._size) {}
|
||||
};
|
||||
|
||||
using Map = std::map<uint32_t, TexInfo>;
|
||||
using Queue = std::queue<uint32_t>;
|
||||
|
||||
Map _allTextures;
|
||||
Queue _readyTextures;
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
bool _useMipmaps;
|
||||
uint8_t _textureCount { GPU_RESOURCE_BUFFER_SIZE };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -139,7 +139,7 @@ float GLTexture::getMemoryPressure() {
|
|||
}
|
||||
|
||||
// Return the consumed texture memory divided by the available texture memory.
|
||||
auto consumedGpuMemory = Context::getTextureGPUMemoryUsage();
|
||||
auto consumedGpuMemory = Context::getTextureGPUSparseMemoryUsage();
|
||||
float memoryPressure = (float)consumedGpuMemory / (float)availableTextureMemory;
|
||||
static Context::Size lastConsumedGpuMemory = 0;
|
||||
if (memoryPressure > 1.0f && lastConsumedGpuMemory != consumedGpuMemory) {
|
||||
|
@ -205,6 +205,8 @@ GLTexture::~GLTexture() {
|
|||
qWarning() << "No recycler available for texture " << _id << " possible leak";
|
||||
}
|
||||
} else if (_id) {
|
||||
// WARNING! Sparse textures do not use this code path. See GL45BackendTexture for
|
||||
// the GL45Texture destructor for doing any required work tracking GPU stats
|
||||
backend->releaseTexture(_id, _size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
static std::shared_ptr<GLTextureTransferHelper> _textureTransferHelper;
|
||||
|
||||
template <typename GLTextureType>
|
||||
static GLTextureType* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) {
|
||||
static GLTexture* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) {
|
||||
const Texture& texture = *texturePointer;
|
||||
|
||||
// Special case external textures
|
||||
|
@ -74,7 +74,7 @@ public:
|
|||
}
|
||||
|
||||
// If the object hasn't been created, or the object definition is out of date, drop and re-create
|
||||
GLTextureType* object = Backend::getGPUObject<GLTextureType>(texture);
|
||||
GLTexture* object = Backend::getGPUObject<GLTextureType>(texture);
|
||||
|
||||
// Create the texture if need be (force re-creation if the storage stamp changes
|
||||
// for easier use of immutable storage)
|
||||
|
@ -84,6 +84,7 @@ public:
|
|||
if (!object->_transferrable) {
|
||||
object->createTexture();
|
||||
object->_contentStamp = texture.getDataStamp();
|
||||
object->updateSize();
|
||||
object->postTransfer();
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +120,7 @@ public:
|
|||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
GLTextureType* object { nullptr };
|
||||
GLTexture* object { nullptr };
|
||||
if (shouldSync) {
|
||||
object = sync<GLTextureType>(backend, texture, shouldSync);
|
||||
} else {
|
||||
|
|
|
@ -252,6 +252,9 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
|
|||
|
||||
if (_transferrable && Texture::getEnableSparseTextures()) {
|
||||
_sparseInfo.maybeMakeSparse();
|
||||
if (_sparseInfo.sparse) {
|
||||
Backend::incrementTextureGPUSparseCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +264,7 @@ GL45Texture::~GL45Texture() {
|
|||
qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str();
|
||||
}
|
||||
if (_sparseInfo.sparse) {
|
||||
Backend::decrementTextureGPUSparseCount();
|
||||
// Remove this texture from the candidate list of derezzable textures
|
||||
{
|
||||
auto mipLevels = usedMipLevels();
|
||||
|
@ -300,6 +304,7 @@ GL45Texture::~GL45Texture() {
|
|||
}
|
||||
|
||||
auto size = _size;
|
||||
const_cast<GLuint&>(_size) = 0;
|
||||
_textureTransferHelper->queueExecution([id, size, destructionFunctions] {
|
||||
for (auto function : destructionFunctions) {
|
||||
function();
|
||||
|
@ -307,6 +312,7 @@ GL45Texture::~GL45Texture() {
|
|||
glDeleteTextures(1, &id);
|
||||
Backend::decrementTextureGPUCount();
|
||||
Backend::updateTextureGPUMemoryUsage(size, 0);
|
||||
Backend::updateTextureGPUSparseMemoryUsage(size, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +344,9 @@ void GL45Texture::updateSize() const {
|
|||
qFatal("Compressed textures not yet supported");
|
||||
}
|
||||
|
||||
if (_transferrable) {
|
||||
if (_transferrable && _sparseInfo.sparse) {
|
||||
auto size = _allocatedPages * _sparseInfo.pageBytes;
|
||||
Backend::updateTextureGPUSparseMemoryUsage(_size, size);
|
||||
setSize(_allocatedPages * _sparseInfo.pageBytes);
|
||||
} else {
|
||||
setSize(_virtualSize);
|
||||
|
|
|
@ -162,14 +162,25 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S
|
|||
}
|
||||
|
||||
// Counters for Buffer and Texture usage in GPU/Context
|
||||
std::atomic<Size> Context::_freeGPUMemory { 0 };
|
||||
std::atomic<uint32_t> Context::_fenceCount { 0 };
|
||||
std::atomic<uint32_t> Context::_bufferGPUCount { 0 };
|
||||
std::atomic<Buffer::Size> Context::_bufferGPUMemoryUsage { 0 };
|
||||
|
||||
std::atomic<uint32_t> Context::_textureGPUCount{ 0 };
|
||||
std::atomic<Texture::Size> Context::_textureGPUMemoryUsage{ 0 };
|
||||
std::atomic<uint32_t> Context::_textureGPUSparseCount { 0 };
|
||||
std::atomic<Texture::Size> Context::_textureGPUMemoryUsage { 0 };
|
||||
std::atomic<Texture::Size> Context::_textureGPUVirtualMemoryUsage{ 0 };
|
||||
std::atomic<uint32_t> Context::_textureGPUTransferCount{ 0 };
|
||||
std::atomic<Texture::Size> Context::_textureGPUSparseMemoryUsage { 0 };
|
||||
std::atomic<uint32_t> Context::_textureGPUTransferCount { 0 };
|
||||
|
||||
void Context::setFreeGPUMemory(Size size) {
|
||||
_freeGPUMemory.store(size);
|
||||
}
|
||||
|
||||
Size Context::getFreeGPUMemory() {
|
||||
return _freeGPUMemory.load();
|
||||
}
|
||||
|
||||
void Context::incrementBufferGPUCount() {
|
||||
static std::atomic<uint32_t> max { 0 };
|
||||
|
@ -217,6 +228,18 @@ void Context::decrementTextureGPUCount() {
|
|||
--_textureGPUCount;
|
||||
}
|
||||
|
||||
void Context::incrementTextureGPUSparseCount() {
|
||||
static std::atomic<uint32_t> max { 0 };
|
||||
auto total = ++_textureGPUSparseCount;
|
||||
if (total > max.load()) {
|
||||
max = total;
|
||||
qCDebug(gpulogging) << "New max GPU textures " << total;
|
||||
}
|
||||
}
|
||||
void Context::decrementTextureGPUSparseCount() {
|
||||
--_textureGPUSparseCount;
|
||||
}
|
||||
|
||||
void Context::updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) {
|
||||
if (prevObjectSize == newObjectSize) {
|
||||
return;
|
||||
|
@ -239,6 +262,17 @@ void Context::updateTextureGPUVirtualMemoryUsage(Size prevObjectSize, Size newOb
|
|||
}
|
||||
}
|
||||
|
||||
void Context::updateTextureGPUSparseMemoryUsage(Size prevObjectSize, Size newObjectSize) {
|
||||
if (prevObjectSize == newObjectSize) {
|
||||
return;
|
||||
}
|
||||
if (newObjectSize > prevObjectSize) {
|
||||
_textureGPUSparseMemoryUsage.fetch_add(newObjectSize - prevObjectSize);
|
||||
} else {
|
||||
_textureGPUSparseMemoryUsage.fetch_sub(prevObjectSize - newObjectSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::incrementTextureGPUTransferCount() {
|
||||
static std::atomic<uint32_t> max { 0 };
|
||||
auto total = ++_textureGPUTransferCount;
|
||||
|
@ -247,6 +281,7 @@ void Context::incrementTextureGPUTransferCount() {
|
|||
qCDebug(gpulogging) << "New max GPU textures transfers" << total;
|
||||
}
|
||||
}
|
||||
|
||||
void Context::decrementTextureGPUTransferCount() {
|
||||
--_textureGPUTransferCount;
|
||||
}
|
||||
|
@ -263,6 +298,10 @@ uint32_t Context::getTextureGPUCount() {
|
|||
return _textureGPUCount.load();
|
||||
}
|
||||
|
||||
uint32_t Context::getTextureGPUSparseCount() {
|
||||
return _textureGPUSparseCount.load();
|
||||
}
|
||||
|
||||
Context::Size Context::getTextureGPUMemoryUsage() {
|
||||
return _textureGPUMemoryUsage.load();
|
||||
}
|
||||
|
@ -271,16 +310,25 @@ Context::Size Context::getTextureGPUVirtualMemoryUsage() {
|
|||
return _textureGPUVirtualMemoryUsage.load();
|
||||
}
|
||||
|
||||
Context::Size Context::getTextureGPUSparseMemoryUsage() {
|
||||
return _textureGPUSparseMemoryUsage.load();
|
||||
}
|
||||
|
||||
uint32_t Context::getTextureGPUTransferCount() {
|
||||
return _textureGPUTransferCount.load();
|
||||
}
|
||||
|
||||
void Backend::setFreeGPUMemory(Size size) { Context::setFreeGPUMemory(size); }
|
||||
Resource::Size Backend::getFreeGPUMemory() { return Context::getFreeGPUMemory(); }
|
||||
void Backend::incrementBufferGPUCount() { Context::incrementBufferGPUCount(); }
|
||||
void Backend::decrementBufferGPUCount() { Context::decrementBufferGPUCount(); }
|
||||
void Backend::updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateBufferGPUMemoryUsage(prevObjectSize, newObjectSize); }
|
||||
void Backend::incrementTextureGPUCount() { Context::incrementTextureGPUCount(); }
|
||||
void Backend::decrementTextureGPUCount() { Context::decrementTextureGPUCount(); }
|
||||
void Backend::incrementTextureGPUSparseCount() { Context::incrementTextureGPUSparseCount(); }
|
||||
void Backend::decrementTextureGPUSparseCount() { Context::decrementTextureGPUSparseCount(); }
|
||||
void Backend::updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUMemoryUsage(prevObjectSize, newObjectSize); }
|
||||
void Backend::updateTextureGPUVirtualMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUVirtualMemoryUsage(prevObjectSize, newObjectSize); }
|
||||
void Backend::updateTextureGPUSparseMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUSparseMemoryUsage(prevObjectSize, newObjectSize); }
|
||||
void Backend::incrementTextureGPUTransferCount() { Context::incrementTextureGPUTransferCount(); }
|
||||
void Backend::decrementTextureGPUTransferCount() { Context::decrementTextureGPUTransferCount(); }
|
||||
|
|
|
@ -89,12 +89,17 @@ public:
|
|||
|
||||
// These should only be accessed by Backend implementation to repport the buffer and texture allocations,
|
||||
// they are NOT public calls
|
||||
static Resource::Size getFreeGPUMemory();
|
||||
static void setFreeGPUMemory(Resource::Size prevObjectSize);
|
||||
static void incrementBufferGPUCount();
|
||||
static void decrementBufferGPUCount();
|
||||
static void updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize);
|
||||
static void incrementTextureGPUCount();
|
||||
static void decrementTextureGPUCount();
|
||||
static void incrementTextureGPUSparseCount();
|
||||
static void decrementTextureGPUSparseCount();
|
||||
static void updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize);
|
||||
static void updateTextureGPUSparseMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize);
|
||||
static void updateTextureGPUVirtualMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize);
|
||||
static void incrementTextureGPUTransferCount();
|
||||
static void decrementTextureGPUTransferCount();
|
||||
|
@ -201,8 +206,11 @@ public:
|
|||
static Size getBufferGPUMemoryUsage();
|
||||
|
||||
static uint32_t getTextureGPUCount();
|
||||
static uint32_t getTextureGPUSparseCount();
|
||||
static Size getFreeGPUMemory();
|
||||
static Size getTextureGPUMemoryUsage();
|
||||
static Size getTextureGPUVirtualMemoryUsage();
|
||||
static Size getTextureGPUSparseMemoryUsage();
|
||||
static uint32_t getTextureGPUTransferCount();
|
||||
|
||||
protected:
|
||||
|
@ -233,21 +241,28 @@ protected:
|
|||
static void incrementFenceCount();
|
||||
static void decrementFenceCount();
|
||||
|
||||
static void setFreeGPUMemory(Size size);
|
||||
static void incrementTextureGPUCount();
|
||||
static void decrementTextureGPUCount();
|
||||
static void incrementTextureGPUSparseCount();
|
||||
static void decrementTextureGPUSparseCount();
|
||||
static void updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize);
|
||||
static void updateTextureGPUSparseMemoryUsage(Size prevObjectSize, Size newObjectSize);
|
||||
static void updateTextureGPUVirtualMemoryUsage(Size prevObjectSize, Size newObjectSize);
|
||||
static void incrementTextureGPUTransferCount();
|
||||
static void decrementTextureGPUTransferCount();
|
||||
|
||||
// Buffer, Texture and Fence Counters
|
||||
static std::atomic<Size> _freeGPUMemory;
|
||||
static std::atomic<uint32_t> _fenceCount;
|
||||
|
||||
static std::atomic<uint32_t> _bufferGPUCount;
|
||||
static std::atomic<Size> _bufferGPUMemoryUsage;
|
||||
|
||||
static std::atomic<uint32_t> _textureGPUCount;
|
||||
static std::atomic<uint32_t> _textureGPUSparseCount;
|
||||
static std::atomic<Size> _textureGPUMemoryUsage;
|
||||
static std::atomic<Size> _textureGPUSparseMemoryUsage;
|
||||
static std::atomic<Size> _textureGPUVirtualMemoryUsage;
|
||||
static std::atomic<uint32_t> _textureGPUTransferCount;
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ namespace gpu {
|
|||
GPUObject* getGPUObject() const { return _gpuObject.get(); }
|
||||
|
||||
friend class Backend;
|
||||
friend class Texture;
|
||||
};
|
||||
|
||||
namespace gl {
|
||||
|
|
|
@ -70,6 +70,14 @@ void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSiz
|
|||
}
|
||||
}
|
||||
|
||||
bool Texture::getEnableSparseTextures() {
|
||||
return _enableSparseTextures.load();
|
||||
}
|
||||
|
||||
bool Texture::getEnableIncrementalTextureTransfers() {
|
||||
return _enableIncrementalTextureTransfers.load();
|
||||
}
|
||||
|
||||
uint32_t Texture::getTextureCPUCount() {
|
||||
return _textureCPUCount.load();
|
||||
}
|
||||
|
@ -82,6 +90,10 @@ uint32_t Texture::getTextureGPUCount() {
|
|||
return Context::getTextureGPUCount();
|
||||
}
|
||||
|
||||
uint32_t Texture::getTextureGPUSparseCount() {
|
||||
return Context::getTextureGPUSparseCount();
|
||||
}
|
||||
|
||||
Texture::Size Texture::getTextureGPUMemoryUsage() {
|
||||
return Context::getTextureGPUMemoryUsage();
|
||||
}
|
||||
|
@ -90,6 +102,10 @@ Texture::Size Texture::getTextureGPUVirtualMemoryUsage() {
|
|||
return Context::getTextureGPUVirtualMemoryUsage();
|
||||
}
|
||||
|
||||
Texture::Size Texture::getTextureGPUSparseMemoryUsage() {
|
||||
return Context::getTextureGPUSparseMemoryUsage();
|
||||
}
|
||||
|
||||
uint32_t Texture::getTextureGPUTransferCount() {
|
||||
return Context::getTextureGPUTransferCount();
|
||||
}
|
||||
|
@ -287,6 +303,22 @@ Texture::Texture():
|
|||
Texture::~Texture()
|
||||
{
|
||||
_textureCPUCount--;
|
||||
if (getUsage().isExternal()) {
|
||||
Texture::ExternalUpdates externalUpdates;
|
||||
{
|
||||
Lock lock(_externalMutex);
|
||||
_externalUpdates.swap(externalUpdates);
|
||||
}
|
||||
for (const auto& update : externalUpdates) {
|
||||
assert(_externalRecycler);
|
||||
_externalRecycler(update.first, update.second);
|
||||
}
|
||||
// Force the GL object to be destroyed here
|
||||
// If we let the normal destructor do it, then it will be
|
||||
// cleared after the _externalRecycler has been destroyed,
|
||||
// resulting in leaked texture memory
|
||||
gpuObject.setGPUObject(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) {
|
||||
|
@ -935,8 +967,20 @@ Vec3u Texture::evalMipDimensions(uint16 level) const {
|
|||
return glm::max(dimensions, Vec3u(1));
|
||||
}
|
||||
|
||||
void Texture::setExternalRecycler(const ExternalRecycler& recycler) {
|
||||
Lock lock(_externalMutex);
|
||||
_externalRecycler = recycler;
|
||||
}
|
||||
|
||||
Texture::ExternalRecycler Texture::getExternalRecycler() const {
|
||||
Lock lock(_externalMutex);
|
||||
Texture::ExternalRecycler result = _externalRecycler;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Texture::setExternalTexture(uint32 externalId, void* externalFence) {
|
||||
Lock lock(_externalMutex);
|
||||
assert(_externalRecycler);
|
||||
_externalUpdates.push_back({ externalId, externalFence });
|
||||
}
|
||||
|
||||
|
|
|
@ -151,14 +151,16 @@ public:
|
|||
static uint32_t getTextureCPUCount();
|
||||
static Size getTextureCPUMemoryUsage();
|
||||
static uint32_t getTextureGPUCount();
|
||||
static uint32_t getTextureGPUSparseCount();
|
||||
static Size getTextureGPUMemoryUsage();
|
||||
static Size getTextureGPUVirtualMemoryUsage();
|
||||
static Size getTextureGPUSparseMemoryUsage();
|
||||
static uint32_t getTextureGPUTransferCount();
|
||||
static Size getAllowedGPUMemoryUsage();
|
||||
static void setAllowedGPUMemoryUsage(Size size);
|
||||
|
||||
static bool getEnableSparseTextures() { return _enableSparseTextures.load(); }
|
||||
static bool getEnableIncrementalTextureTransfers() { return _enableIncrementalTextureTransfers.load(); }
|
||||
static bool getEnableSparseTextures();
|
||||
static bool getEnableIncrementalTextureTransfers();
|
||||
|
||||
static void setEnableSparseTextures(bool enabled);
|
||||
static void setEnableIncrementalTextureTransfers(bool enabled);
|
||||
|
@ -466,8 +468,8 @@ public:
|
|||
void notifyMipFaceGPULoaded(uint16 level, uint8 face = 0) const { return _storage->notifyMipFaceGPULoaded(level, face); }
|
||||
|
||||
void setExternalTexture(uint32 externalId, void* externalFence);
|
||||
void setExternalRecycler(const ExternalRecycler& recycler) { _externalRecycler = recycler; }
|
||||
ExternalRecycler getExternalRecycler() const { return _externalRecycler; }
|
||||
void setExternalRecycler(const ExternalRecycler& recycler);
|
||||
ExternalRecycler getExternalRecycler() const;
|
||||
|
||||
const GPUObjectPointer gpuObject {};
|
||||
|
||||
|
|
|
@ -23,7 +23,13 @@
|
|||
#include "AccountManager.h"
|
||||
|
||||
const QString HIFI_URL_SCHEME = "hifi";
|
||||
|
||||
#if USE_STABLE_GLOBAL_SERVICES
|
||||
const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome";
|
||||
#else
|
||||
const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome";
|
||||
#endif
|
||||
|
||||
const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost";
|
||||
const QString INDEX_PATH = "/";
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
|
|||
= LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX);
|
||||
|
||||
qCDebug(networking) << "Packet of type" << headerType
|
||||
<< "received from unknown node with UUID" << qPrintable(uuidStringWithoutCurlyBraces(sourceID));
|
||||
<< "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,9 +321,8 @@ protected:
|
|||
|
||||
PacketReceiver* _packetReceiver;
|
||||
|
||||
// XXX can BandwidthRecorder be used for this?
|
||||
int _numCollectedPackets;
|
||||
int _numCollectedBytes;
|
||||
std::atomic<int> _numCollectedPackets;
|
||||
std::atomic<int> _numCollectedBytes;
|
||||
|
||||
QElapsedTimer _packetStatTimer;
|
||||
NodePermissions _permissions;
|
||||
|
|
|
@ -127,19 +127,30 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
|||
packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode");
|
||||
}
|
||||
|
||||
qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination) {
|
||||
qint64 NodeList::sendStats(QJsonObject statsObject, HifiSockAddr destination) {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendStats", Qt::QueuedConnection,
|
||||
Q_ARG(QJsonObject, statsObject),
|
||||
Q_ARG(HifiSockAddr, destination));
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto statsPacketList = NLPacketList::create(PacketType::NodeJsonStats, QByteArray(), true, true);
|
||||
|
||||
QJsonDocument jsonDocument(statsObject);
|
||||
statsPacketList->write(jsonDocument.toBinaryData());
|
||||
|
||||
sendPacketList(std::move(statsPacketList), destination);
|
||||
|
||||
// enumerate the resulting strings, breaking them into MTU sized packets
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) {
|
||||
qint64 NodeList::sendStatsToDomainServer(QJsonObject statsObject) {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendStatsToDomainServer", Qt::QueuedConnection,
|
||||
Q_ARG(QJsonObject, statsObject));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sendStats(statsObject, _domainHandler.getSockAddr());
|
||||
}
|
||||
|
||||
|
@ -251,11 +262,16 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
|
|||
}
|
||||
|
||||
void NodeList::sendDomainServerCheckIn() {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isShuttingDown) {
|
||||
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_publicSockAddr.isNull()) {
|
||||
// we don't know our public socket and we need to send it to the domain server
|
||||
qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
|
||||
|
|
|
@ -54,8 +54,8 @@ public:
|
|||
NodeType_t getOwnerType() const { return _ownerType; }
|
||||
void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; }
|
||||
|
||||
qint64 sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination);
|
||||
qint64 sendStatsToDomainServer(const QJsonObject& statsObject);
|
||||
Q_INVOKABLE qint64 sendStats(QJsonObject statsObject, HifiSockAddr destination);
|
||||
Q_INVOKABLE qint64 sendStatsToDomainServer(QJsonObject statsObject);
|
||||
|
||||
int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; }
|
||||
DomainHandler& getDomainHandler() { return _domainHandler; }
|
||||
|
|
|
@ -87,11 +87,10 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy
|
|||
connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, &_statsTimer, &QTimer::stop);
|
||||
}
|
||||
|
||||
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject &statsObject) {
|
||||
void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
float packetsPerSecond, bytesPerSecond;
|
||||
// XXX can BandwidthRecorder be used for this?
|
||||
nodeList->getPacketStats(packetsPerSecond, bytesPerSecond);
|
||||
nodeList->resetPacketStats();
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
|
||||
void setFinished(bool isFinished);
|
||||
virtual void aboutToFinish() { };
|
||||
void addPacketStatsAndSendStatsPacket(QJsonObject& statsObject);
|
||||
void addPacketStatsAndSendStatsPacket(QJsonObject statsObject);
|
||||
|
||||
public slots:
|
||||
/// threaded run of assignment
|
||||
|
|
|
@ -76,7 +76,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::InjectAudio:
|
||||
case PacketType::MicrophoneAudioNoEcho:
|
||||
case PacketType::MicrophoneAudioWithEcho:
|
||||
return static_cast<PacketVersion>(AudioVersion::Exactly10msAudioPackets);
|
||||
case PacketType::AudioStreamStats:
|
||||
return static_cast<PacketVersion>(AudioVersion::TerminatingStreamStats);
|
||||
|
||||
default:
|
||||
return 17;
|
||||
|
|
|
@ -227,7 +227,8 @@ enum class DomainListVersion : PacketVersion {
|
|||
enum class AudioVersion : PacketVersion {
|
||||
HasCompressedAudio = 17,
|
||||
CodecNameInAudioPackets,
|
||||
Exactly10msAudioPackets
|
||||
Exactly10msAudioPackets,
|
||||
TerminatingStreamStats,
|
||||
};
|
||||
|
||||
#endif // hifi_PacketHeaders_h
|
||||
|
|
|
@ -524,7 +524,7 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
|||
}
|
||||
|
||||
void CharacterController::preSimulation() {
|
||||
if (_enabled && _dynamicsWorld) {
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// slam body to where it is supposed to be
|
||||
|
@ -632,9 +632,10 @@ void CharacterController::preSimulation() {
|
|||
|
||||
void CharacterController::postSimulation() {
|
||||
// postSimulation() exists for symmetry and just in case we need to do something here later
|
||||
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_velocityChange = velocity - _preSimulationVelocity;
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_velocityChange = velocity - _preSimulationVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <EntityScriptingInterface.h>
|
||||
#include <MessagesClient.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <PathUtils.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <NodeList.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
@ -1136,6 +1137,10 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
|
|||
return url;
|
||||
}
|
||||
|
||||
QUrl ScriptEngine::resourcesPath() const {
|
||||
return QUrl::fromLocalFile(PathUtils::resourcesPath());
|
||||
}
|
||||
|
||||
void ScriptEngine::print(const QString& message) {
|
||||
emit printedMessage(message);
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ public:
|
|||
Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
Q_INVOKABLE void print(const QString& message);
|
||||
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
|
||||
Q_INVOKABLE QUrl resourcesPath() const;
|
||||
|
||||
// Entity Script Related methods
|
||||
static void loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload);
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
|
||||
#include "LogHandler.h"
|
||||
|
||||
QMutex LogHandler::_mutex;
|
||||
|
||||
LogHandler& LogHandler::getInstance() {
|
||||
static LogHandler staticInstance;
|
||||
return staticInstance;
|
||||
|
@ -63,8 +65,29 @@ const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
|
|||
// the following will produce 11/18 13:55:36.999
|
||||
const QString DATE_STRING_FORMAT_WITH_MILLISECONDS = "MM/dd hh:mm:ss.zzz";
|
||||
|
||||
void LogHandler::setTargetName(const QString& targetName) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_targetName = targetName;
|
||||
}
|
||||
|
||||
void LogHandler::setShouldOutputProcessID(bool shouldOutputProcessID) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_shouldOutputProcessID = shouldOutputProcessID;
|
||||
}
|
||||
|
||||
void LogHandler::setShouldOutputThreadID(bool shouldOutputThreadID) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_shouldOutputThreadID = shouldOutputThreadID;
|
||||
}
|
||||
|
||||
void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_shouldDisplayMilliseconds = shouldDisplayMilliseconds;
|
||||
}
|
||||
|
||||
|
||||
void LogHandler::flushRepeatedMessages() {
|
||||
QMutexLocker locker(&_repeatedMessageLock);
|
||||
QMutexLocker lock(&_mutex);
|
||||
QHash<QString, int>::iterator message = _repeatMessageCountHash.begin();
|
||||
while (message != _repeatMessageCountHash.end()) {
|
||||
|
||||
|
@ -73,7 +96,9 @@ void LogHandler::flushRepeatedMessages() {
|
|||
.arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key()));
|
||||
|
||||
QMessageLogContext emptyContext;
|
||||
lock.unlock();
|
||||
printMessage(LogSuppressed, emptyContext, repeatMessage);
|
||||
lock.relock();
|
||||
}
|
||||
|
||||
_lastRepeatedMessage.remove(message.key());
|
||||
|
@ -82,13 +107,13 @@ void LogHandler::flushRepeatedMessages() {
|
|||
}
|
||||
|
||||
QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
if (message.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (type == LogDebug) {
|
||||
// for debug messages, check if this matches any of our regexes for repeated log messages
|
||||
QMutexLocker locker(&_repeatedMessageLock);
|
||||
foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) {
|
||||
QRegExp repeatRegex(regexString);
|
||||
if (repeatRegex.indexIn(message) != -1) {
|
||||
|
@ -111,7 +136,6 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
|
|||
}
|
||||
}
|
||||
if (type == LogDebug) {
|
||||
QMutexLocker locker(&_onlyOnceMessageLock);
|
||||
// see if this message is one we should only print once
|
||||
foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) {
|
||||
QRegExp onlyOnceRegex(regexString);
|
||||
|
@ -163,11 +187,11 @@ void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext&
|
|||
}
|
||||
|
||||
const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) {
|
||||
QMutexLocker locker(&_repeatedMessageLock);
|
||||
QMutexLocker lock(&_mutex);
|
||||
return *_repeatedMessageRegexes.insert(regexString);
|
||||
}
|
||||
|
||||
const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) {
|
||||
QMutexLocker locker(&_onlyOnceMessageLock);
|
||||
QMutexLocker lock(&_mutex);
|
||||
return *_onlyOnceMessageRegexes.insert(regexString);
|
||||
}
|
||||
|
|
|
@ -38,11 +38,11 @@ public:
|
|||
|
||||
/// sets the target name to output via the verboseMessageHandler, called once before logging begins
|
||||
/// \param targetName the desired target name to output in logs
|
||||
void setTargetName(const QString& targetName) { _targetName = targetName; }
|
||||
void setTargetName(const QString& targetName);
|
||||
|
||||
void setShouldOutputProcessID(bool shouldOutputProcessID) { _shouldOutputProcessID = shouldOutputProcessID; }
|
||||
void setShouldOutputThreadID(bool shouldOutputThreadID) { _shouldOutputThreadID = shouldOutputThreadID; }
|
||||
void setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { _shouldDisplayMilliseconds = shouldDisplayMilliseconds; }
|
||||
void setShouldOutputProcessID(bool shouldOutputProcessID);
|
||||
void setShouldOutputThreadID(bool shouldOutputThreadID);
|
||||
void setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds);
|
||||
|
||||
QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message);
|
||||
|
||||
|
@ -64,11 +64,11 @@ private:
|
|||
QSet<QString> _repeatedMessageRegexes;
|
||||
QHash<QString, int> _repeatMessageCountHash;
|
||||
QHash<QString, QString> _lastRepeatedMessage;
|
||||
QMutex _repeatedMessageLock;
|
||||
|
||||
QSet<QString> _onlyOnceMessageRegexes;
|
||||
QHash<QString, int> _onlyOnceMessageCountHash;
|
||||
QMutex _onlyOnceMessageLock;
|
||||
|
||||
static QMutex _mutex;
|
||||
};
|
||||
|
||||
#endif // hifi_LogHandler_h
|
||||
|
|
|
@ -19,15 +19,29 @@ SimpleMovingAverage::SimpleMovingAverage(int numSamplesToAverage) :
|
|||
_eventDeltaAverage(0.0f),
|
||||
WEIGHTING(1.0f / numSamplesToAverage),
|
||||
ONE_MINUS_WEIGHTING(1 - WEIGHTING) {
|
||||
|
||||
}
|
||||
|
||||
SimpleMovingAverage::SimpleMovingAverage(const SimpleMovingAverage& other) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
SimpleMovingAverage& SimpleMovingAverage::operator=(const SimpleMovingAverage& other) {
|
||||
_numSamples = (int)other._numSamples;
|
||||
_lastEventTimestamp = (uint64_t)other._lastEventTimestamp;
|
||||
_average = (float)other._average;
|
||||
_eventDeltaAverage = (float)other._eventDeltaAverage;
|
||||
WEIGHTING = other.WEIGHTING;
|
||||
ONE_MINUS_WEIGHTING = other.ONE_MINUS_WEIGHTING;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
int SimpleMovingAverage::updateAverage(float sample) {
|
||||
if (_numSamples > 0) {
|
||||
_average = (ONE_MINUS_WEIGHTING * _average) + (WEIGHTING * sample);
|
||||
|
||||
|
||||
float eventDelta = (usecTimestampNow() - _lastEventTimestamp) / 1000000.0f;
|
||||
|
||||
|
||||
if (_numSamples > 1) {
|
||||
_eventDeltaAverage = (ONE_MINUS_WEIGHTING * _eventDeltaAverage) +
|
||||
(WEIGHTING * eventDelta);
|
||||
|
|
|
@ -16,27 +16,31 @@
|
|||
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
|
||||
class SimpleMovingAverage {
|
||||
public:
|
||||
SimpleMovingAverage(int numSamplesToAverage = 100);
|
||||
|
||||
SimpleMovingAverage(const SimpleMovingAverage& other);
|
||||
SimpleMovingAverage& operator=(const SimpleMovingAverage& other);
|
||||
|
||||
int updateAverage(float sample);
|
||||
void reset();
|
||||
|
||||
|
||||
int getSampleCount() const { return _numSamples; };
|
||||
float getAverage() const { return _average; };
|
||||
float getEventDeltaAverage() const; // returned in seconds
|
||||
float getAverageSampleValuePerSecond() const { return _average * (1.0f / getEventDeltaAverage()); }
|
||||
|
||||
|
||||
uint64_t getUsecsSinceLastEvent() const;
|
||||
|
||||
|
||||
private:
|
||||
int _numSamples;
|
||||
uint64_t _lastEventTimestamp;
|
||||
float _average;
|
||||
float _eventDeltaAverage;
|
||||
|
||||
std::atomic<int> _numSamples;
|
||||
std::atomic<uint64_t> _lastEventTimestamp;
|
||||
std::atomic<float> _average;
|
||||
std::atomic<float> _eventDeltaAverage;
|
||||
|
||||
float WEIGHTING;
|
||||
float ONE_MINUS_WEIGHTING;
|
||||
};
|
||||
|
@ -44,10 +48,20 @@ private:
|
|||
|
||||
template <class T, int MAX_NUM_SAMPLES> class MovingAverage {
|
||||
public:
|
||||
MovingAverage<T, MAX_NUM_SAMPLES>() {}
|
||||
MovingAverage<T, MAX_NUM_SAMPLES>(const MovingAverage<T, MAX_NUM_SAMPLES>& other) {
|
||||
*this = other;
|
||||
}
|
||||
MovingAverage<T, MAX_NUM_SAMPLES>& operator=(const MovingAverage<T, MAX_NUM_SAMPLES>& other) {
|
||||
numSamples = (int)other.numSamples;
|
||||
average = (T)other.average;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const float WEIGHTING = 1.0f / (float)MAX_NUM_SAMPLES;
|
||||
const float ONE_MINUS_WEIGHTING = 1.0f - WEIGHTING;
|
||||
int numSamples{ 0 };
|
||||
T average;
|
||||
std::atomic<int> numSamples{ 0 };
|
||||
std::atomic<T> average;
|
||||
|
||||
void clear() {
|
||||
numSamples = 0;
|
||||
|
@ -72,7 +86,7 @@ public:
|
|||
_samples = 0;
|
||||
}
|
||||
|
||||
bool isAverageValid() const {
|
||||
bool isAverageValid() const {
|
||||
std::unique_lock<std::mutex> lock(_lock);
|
||||
return (_samples > 0);
|
||||
}
|
||||
|
@ -87,7 +101,7 @@ public:
|
|||
_samples++;
|
||||
}
|
||||
|
||||
T getAverage() const {
|
||||
T getAverage() const {
|
||||
std::unique_lock<std::mutex> lock(_lock);
|
||||
return _average;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ var DEFAULT_SCRIPTS = [
|
|||
"system/mod.js",
|
||||
"system/selectAudioDevice.js",
|
||||
"system/notifications.js",
|
||||
"system/controllers/controllerDisplayManager.js",
|
||||
"system/controllers/handControllerGrab.js",
|
||||
"system/controllers/handControllerPointer.js",
|
||||
"system/controllers/squeezeHands.js",
|
||||
|
@ -125,4 +126,4 @@ Script.scriptEnding.connect(function() {
|
|||
removeMenuItem();
|
||||
});
|
||||
|
||||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
|
|
|
@ -40,6 +40,8 @@ function onFinishedPlaying() {
|
|||
messageSend({key: 'finishedSound'});
|
||||
}
|
||||
|
||||
var attachment;
|
||||
|
||||
var MILLISECONDS_IN_SECOND = 1000;
|
||||
function startAgent(parameters) { // Can also be used to update.
|
||||
print('crowd-agent starting params', JSON.stringify(parameters), JSON.stringify(Agent));
|
||||
|
@ -61,12 +63,22 @@ function startAgent(parameters) { // Can also be used to update.
|
|||
});
|
||||
}
|
||||
if (parameters.animationData) {
|
||||
data = parameters.animationData;
|
||||
var data = parameters.animationData;
|
||||
Avatar.startAnimation(data.url, data.fps || 30, 1.0, (data.loopFlag === undefined) ? true : data.loopFlag, false, data.startFrame || 0, data.endFrame);
|
||||
}
|
||||
if (parameters.attachment) {
|
||||
attachment = parameters.attachment;
|
||||
Avatar.attach(attachment.modelURL, attachment.jointName, attachment.translation, attachment.rotation, attachment.scale, attachment.isSoft);
|
||||
} else {
|
||||
attachment = undefined;
|
||||
}
|
||||
print('crowd-agent avatars started');
|
||||
}
|
||||
function stopAgent(parameters) {
|
||||
if (attachment) {
|
||||
Avatar.detachOne(attachment.modelURL, attachment.jointName);
|
||||
attachment = undefined;
|
||||
}
|
||||
Agent.isAvatar = false;
|
||||
print('crowd-agent stopped', JSON.stringify(parameters), JSON.stringify(Agent));
|
||||
}
|
||||
|
|
106
scripts/developer/tests/webSpawnTool.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
// webSpawnTool.js
|
||||
//
|
||||
// Stress tests the rendering of web surfaces over time
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
ENTITY_SPAWNER = function (properties) {
|
||||
properties = properties || {};
|
||||
var RADIUS = properties.radius || 5.0; // Spawn within this radius (square)
|
||||
var TEST_ENTITY_NAME = properties.entityName || "WebEntitySpawnTest";
|
||||
var NUM_ENTITIES = properties.count || 10000; // number of entities to spawn
|
||||
var ENTITY_SPAWN_LIMIT = properties.spawnLimit || 1;
|
||||
var ENTITY_SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 2;
|
||||
var ENTITY_LIFETIME = properties.lifetime || 10; // Entity timeout (when/if we crash, we need the entities to delete themselves)
|
||||
|
||||
function makeEntity(properties) {
|
||||
var entity = Entities.addEntity(properties);
|
||||
return {
|
||||
destroy: function () {
|
||||
Entities.deleteEntity(entity)
|
||||
},
|
||||
getAge: function () {
|
||||
return Entities.getEntityProperties(entity).age;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function randomPositionXZ(center, radius) {
|
||||
return {
|
||||
x: center.x + (Math.random() * radius * 2.0) - radius,
|
||||
y: center.y,
|
||||
z: center.z + (Math.random() * radius * 2.0) - radius
|
||||
};
|
||||
}
|
||||
|
||||
var entities = [];
|
||||
var entitiesToCreate = 0;
|
||||
var entitiesSpawned = 0;
|
||||
var spawnTimer = 0.0;
|
||||
var keepAliveTimer = 0.0;
|
||||
|
||||
function clear () {
|
||||
var ids = Entities.findEntities(MyAvatar.position, 50);
|
||||
var that = this;
|
||||
ids.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id);
|
||||
if (properties.name == TEST_ENTITY_NAME) {
|
||||
Entities.deleteEntity(id);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
function createEntities () {
|
||||
entitiesToCreate = NUM_ENTITIES;
|
||||
Script.update.connect(spawnEntities);
|
||||
}
|
||||
|
||||
function spawnEntities (dt) {
|
||||
if (entitiesToCreate <= 0) {
|
||||
Script.update.disconnect(spawnEntities);
|
||||
print("Finished spawning entities");
|
||||
}
|
||||
else if ((spawnTimer -= dt) < 0.0){
|
||||
spawnTimer = ENTITY_SPAWN_INTERVAL;
|
||||
|
||||
var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT);
|
||||
print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")");
|
||||
|
||||
entitiesToCreate -= n;
|
||||
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: RADIUS * -1.5 }));
|
||||
for (; n > 0; --n) {
|
||||
entities.push(makeEntity({
|
||||
type: "Web",
|
||||
//sourceUrl: "https://www.reddit.com/r/random/",
|
||||
sourceUrl: "https://en.wikipedia.org/wiki/Special:Random",
|
||||
name: TEST_ENTITY_NAME,
|
||||
position: randomPositionXZ(center, RADIUS),
|
||||
rotation: MyAvatar.orientation,
|
||||
dimensions: { x: .8 + Math.random() * 0.8, y: 0.45 + Math.random() * 0.45, z: 0.01 },
|
||||
lifetime: ENTITY_LIFETIME
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function despawnEntities () {
|
||||
print("despawning entities");
|
||||
entities.forEach(function (entity) {
|
||||
entity.destroy();
|
||||
});
|
||||
entities = [];
|
||||
}
|
||||
|
||||
function init () {
|
||||
Script.update.disconnect(init);
|
||||
clear();
|
||||
createEntities();
|
||||
Script.scriptEnding.connect(despawnEntities);
|
||||
}
|
||||
Script.update.connect(init);
|
||||
};
|
||||
|
||||
ENTITY_SPAWNER();
|
32
scripts/developer/utilities/audio/Jitter.qml
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Jitter.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ColumnLayout {
|
||||
id: jitter
|
||||
property var max
|
||||
property var avg
|
||||
property bool showGraphs: false
|
||||
|
||||
MovingValue {
|
||||
label: "Jitter"
|
||||
color: "red"
|
||||
source: max - avg
|
||||
showGraphs: jitter.showGraphs
|
||||
}
|
||||
Value {
|
||||
label: "Average"
|
||||
source: avg
|
||||
}
|
||||
}
|
||||
|
51
scripts/developer/utilities/audio/MovingValue.qml
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// MovingValue.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
import "../lib/plotperf"
|
||||
|
||||
RowLayout {
|
||||
id: value
|
||||
property string label
|
||||
property var source
|
||||
property string unit: "ms"
|
||||
property bool showGraphs: false
|
||||
property color color: "darkslategrey"
|
||||
property int decimals: 0
|
||||
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: 100
|
||||
color: value.color
|
||||
text: value.label
|
||||
}
|
||||
Label {
|
||||
visible: !value.showGraphs
|
||||
Layout.preferredWidth: 50
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: value.color
|
||||
text: value.source.toFixed(decimals) + ' ' + unit
|
||||
}
|
||||
PlotPerf {
|
||||
visible: value.showGraphs
|
||||
Layout.fillWidth: true
|
||||
height: 70
|
||||
|
||||
valueUnit: value.unit
|
||||
valueNumDigits: 0
|
||||
backgroundOpacity: 0.2
|
||||
|
||||
plots: [{ binding: "source", color: value.color }]
|
||||
}
|
||||
}
|
||||
|
56
scripts/developer/utilities/audio/Section.qml
Normal file
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Section.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Rectangle {
|
||||
id: section
|
||||
property string label: "Section"
|
||||
property string description: "Description"
|
||||
property alias control : loader.sourceComponent
|
||||
|
||||
width: parent.width
|
||||
height: content.height + border.width * 2 + content.spacing * 2
|
||||
border.color: "black"
|
||||
border.width: 5
|
||||
radius: border.width * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
x: section.radius; y: section.radius
|
||||
spacing: section.border.width
|
||||
width: section.width - 2 * x
|
||||
|
||||
// label
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: hoverArea.containsMouse ? section.description : section.label
|
||||
font.bold: true
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
Item { }
|
||||
|
||||
// control
|
||||
Loader {
|
||||
id: loader
|
||||
Layout.preferredWidth: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
69
scripts/developer/utilities/audio/Stream.qml
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// Stream.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
property var stream
|
||||
property bool showGraphs: false
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: "Ring Buffer"
|
||||
font.italic: true
|
||||
}
|
||||
MovingValue {
|
||||
label: "Minimum Depth"
|
||||
color: "limegreen"
|
||||
source: stream.framesDesired
|
||||
unit: "frames"
|
||||
showGraphs: root.showGraphs
|
||||
}
|
||||
MovingValue {
|
||||
label: "Buffer Depth"
|
||||
color: "darkblue"
|
||||
source: stream.unplayedMsMax
|
||||
showGraphs: root.showGraphs
|
||||
}
|
||||
Value {
|
||||
label: "Available (avg)"
|
||||
source: stream.framesAvailable + " (" + stream.framesAvailableAvg + ") frames"
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: "Jitter"
|
||||
font.italic: true
|
||||
}
|
||||
Jitter {
|
||||
max: stream.timegapMsMaxWindow
|
||||
avg: stream.timegapMsAvgWindow
|
||||
showGraphs: root.showGraphs
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: "Packet Loss"
|
||||
font.italic: true
|
||||
}
|
||||
Value {
|
||||
label: "Window"
|
||||
source: stream.lossRateWindow.toFixed(2) + "% (" + stream.lossCountWindow + " lost)"
|
||||
}
|
||||
Value {
|
||||
label: "Overall"
|
||||
color: "dimgrey"
|
||||
source: stream.lossRate.toFixed(2) + "% (" + stream.lossCount + " lost)"
|
||||
}
|
||||
}
|
||||
|
36
scripts/developer/utilities/audio/Value.qml
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Value.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
RowLayout {
|
||||
id: value
|
||||
property string label
|
||||
property var source
|
||||
property color color: "darkslategrey"
|
||||
|
||||
width: parent.width
|
||||
property int dataPixelWidth: 150
|
||||
|
||||
Label {
|
||||
Layout.preferredWidth: dataPixelWidth
|
||||
color: value.color
|
||||
text: value.label
|
||||
}
|
||||
Label {
|
||||
Layout.preferredWidth: 0
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: value.color
|
||||
text: value.source
|
||||
}
|
||||
}
|
||||
|
25
scripts/developer/utilities/audio/stats.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// stats.js
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Zach Pomerantz, created on 9/22/2016.
|
||||
// Copyright 2016 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
|
||||
//
|
||||
|
||||
var INITIAL_WIDTH = 400;
|
||||
var INITIAL_OFFSET = 50;
|
||||
|
||||
// Set up the qml ui
|
||||
var qml = Script.resolvePath('stats.qml');
|
||||
var window = new OverlayWindow({
|
||||
title: 'Audio Interface Statistics',
|
||||
source: qml,
|
||||
width: 500, height: 520 // stats.qml may be too large for some screens
|
||||
});
|
||||
window.setPosition(INITIAL_OFFSET, INITIAL_OFFSET);
|
||||
|
||||
window.closed.connect(function() { Script.stop(); });
|
||||
|
95
scripts/developer/utilities/audio/stats.qml
Normal file
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// stats.qml
|
||||
// scripts/developer/utilities/audio
|
||||
//
|
||||
// Created by Zach Pomerantz on 9/22/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Column {
|
||||
id: stats
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
property bool showGraphs: toggleGraphs.checked
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
height: 30
|
||||
|
||||
Button {
|
||||
id: toggleGraphs
|
||||
property bool checked: false
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
text: checked ? "Hide graphs" : "Show graphs"
|
||||
onClicked: function() { checked = !checked; }
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
width: parent.width
|
||||
height: parent.height - 30
|
||||
|
||||
Column {
|
||||
width: parent.width / 2
|
||||
height: parent.height
|
||||
|
||||
Section {
|
||||
label: "Latency"
|
||||
description: "Audio pipeline latency, broken out and summed"
|
||||
control: ColumnLayout {
|
||||
MovingValue { label: "Input Read"; source: AudioStats.inputReadMsMax; showGraphs: stats.showGraphs }
|
||||
MovingValue { label: "Input Ring"; source: AudioStats.inputUnplayedMsMax; showGraphs: stats.showGraphs }
|
||||
MovingValue { label: "Network (up)"; source: AudioStats.pingMs / 2; showGraphs: stats.showGraphs; decimals: 1 }
|
||||
MovingValue { label: "Mixer Ring"; source: AudioStats.mixerStream.unplayedMsMax; showGraphs: stats.showGraphs }
|
||||
MovingValue { label: "Network (down)"; source: AudioStats.pingMs / 2; showGraphs: stats.showGraphs; decimals: 1 }
|
||||
MovingValue { label: "Output Ring"; source: AudioStats.clientStream.unplayedMsMax; showGraphs: stats.showGraphs }
|
||||
MovingValue { label: "Output Read"; source: AudioStats.outputUnplayedMsMax; showGraphs: stats.showGraphs }
|
||||
MovingValue { label: "TOTAL"; color: "black"; showGraphs: stats.showGraphs
|
||||
source: AudioStats.inputReadMsMax +
|
||||
AudioStats.inputUnplayedMsMax +
|
||||
AudioStats.outputUnplayedMsMax +
|
||||
AudioStats.mixerStream.unplayedMsMax +
|
||||
AudioStats.clientStream.unplayedMsMax +
|
||||
AudioStats.pingMs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
label: "Upstream Jitter"
|
||||
description: "Timegaps in packets sent to the mixer"
|
||||
control: Jitter {
|
||||
max: AudioStats.sentTimegapMsMaxWindow
|
||||
avg: AudioStats.sentTimegapMsAvgWindow
|
||||
showGraphs: stats.showGraphs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width / 2
|
||||
height: parent.height
|
||||
|
||||
Section {
|
||||
label: "Mixer (upstream)"
|
||||
description: "This client's remote audio stream, as seen by the server's mixer"
|
||||
control: Stream { stream: AudioStats.mixerStream; showGraphs: stats.showGraphs }
|
||||
}
|
||||
|
||||
Section {
|
||||
label: "Client (downstream)"
|
||||
description: "This client's received audio stream, between the network and the OS"
|
||||
control: Stream { stream: AudioStats.clientStream; showGraphs: stats.showGraphs }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,14 +16,14 @@ Item {
|
|||
width: parent.width
|
||||
height: 100
|
||||
|
||||
property int hitboxExtension : 20
|
||||
|
||||
// The title of the graph
|
||||
property string title
|
||||
|
||||
// The object used as the default source object for the prop plots
|
||||
property var object
|
||||
|
||||
property var backgroundOpacity: 0.6
|
||||
|
||||
// Plots is an array of plot descriptor
|
||||
// a default plot descriptor expects the following object:
|
||||
// prop: [ {
|
||||
|
@ -55,9 +55,17 @@ Item {
|
|||
function createValues() {
|
||||
for (var i =0; i < plots.length; i++) {
|
||||
var plot = plots[i];
|
||||
var object = plot["object"] || root.object;
|
||||
var value = plot["prop"];
|
||||
var isBinding = plot["binding"];
|
||||
if (isBinding) {
|
||||
object = root.parent;
|
||||
value = isBinding;
|
||||
}
|
||||
_values.push( {
|
||||
object: (plot["object"] !== undefined ? plot["object"] : root.object),
|
||||
value: plot["prop"],
|
||||
object: object,
|
||||
value: value,
|
||||
fromBinding: isBinding,
|
||||
valueMax: 1,
|
||||
numSamplesConstantMax: 0,
|
||||
valueHistory: new Array(),
|
||||
|
@ -179,7 +187,7 @@ Item {
|
|||
ctx.fillText(text, 0, lineHeight);
|
||||
}
|
||||
function displayBackground(ctx) {
|
||||
ctx.fillStyle = Qt.rgba(0, 0, 0, 0.6);
|
||||
ctx.fillStyle = Qt.rgba(0, 0, 0, root.backgroundOpacity);
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.strokeStyle= "grey";
|
||||
|
@ -210,15 +218,9 @@ Item {
|
|||
|
||||
MouseArea {
|
||||
id: hitbox
|
||||
anchors.fill:mycanvas
|
||||
|
||||
anchors.topMargin: -hitboxExtension
|
||||
anchors.bottomMargin: -hitboxExtension
|
||||
anchors.leftMargin: -hitboxExtension
|
||||
anchors.rightMargin: -hitboxExtension
|
||||
anchors.fill: mycanvas
|
||||
|
||||
onClicked: {
|
||||
print("PerfPlot clicked!")
|
||||
resetMax();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ var AWAY_INTRO = {
|
|||
var isEnabled = true;
|
||||
var wasMuted; // unknonwn?
|
||||
var isAway = false; // we start in the un-away state
|
||||
var wasOverlaysVisible = Menu.isOptionChecked("Overlays");
|
||||
var eventMappingName = "io.highfidelity.away"; // goActive on hand controller button events, too.
|
||||
var eventMapping = Controller.newMapping(eventMappingName);
|
||||
var avatarPosition = MyAvatar.position;
|
||||
|
@ -178,11 +177,7 @@ function goAway(fromStartup) {
|
|||
playAwayAnimation(); // animation is still seen by others
|
||||
showOverlay();
|
||||
|
||||
// remember the View > Overlays state...
|
||||
wasOverlaysVisible = Menu.isOptionChecked("Overlays");
|
||||
|
||||
// show overlays so that people can see the "Away" message
|
||||
Menu.setIsOptionChecked("Overlays", true);
|
||||
HMD.requestShowHandControllers();
|
||||
|
||||
// tell the Reticle, we want to stop capturing the mouse until we come back
|
||||
Reticle.allowMouseCapture = false;
|
||||
|
@ -222,6 +217,8 @@ function goActive() {
|
|||
MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting.
|
||||
stopAwayAnimation();
|
||||
|
||||
HMD.requestHideHandControllers();
|
||||
|
||||
// update the UI sphere to be centered about the current HMD orientation.
|
||||
HMD.centerUI();
|
||||
|
||||
|
@ -233,9 +230,6 @@ function goActive() {
|
|||
|
||||
hideOverlay();
|
||||
|
||||
// restore overlays state to what it was when we went "away"
|
||||
Menu.setIsOptionChecked("Overlays", wasOverlaysVisible);
|
||||
|
||||
// tell the Reticle, we are ready to capture the mouse again and it should be visible
|
||||
Reticle.allowMouseCapture = true;
|
||||
Reticle.visible = true;
|
||||
|
|
|
@ -1,26 +1,45 @@
|
|||
var VISIBLE_BY_DEFAULT = false;
|
||||
//
|
||||
// controllerDisplay.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* globals createControllerDisplay:true deleteControllerDisplay:true */
|
||||
|
||||
var PARENT_ID = "{00000000-0000-0000-0000-000000000001}";
|
||||
|
||||
var DEBUG = false;
|
||||
function debug() {
|
||||
if (DEBUG) {
|
||||
print.apply(self, arguments);
|
||||
function clamp(value, min, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
} else if (value > max) {
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function resolveHardware(path) {
|
||||
var parts = path.split(".");
|
||||
function resolveInner(base, path, i) {
|
||||
if (i >= path.length) {
|
||||
return base;
|
||||
}
|
||||
return resolveInner(base[path[i]], path, ++i);
|
||||
}
|
||||
return resolveInner(Controller.Hardware, parts, 0);
|
||||
}
|
||||
|
||||
createControllerDisplay = function(config) {
|
||||
var controllerDisplay = {
|
||||
overlays: [],
|
||||
partOverlays: {
|
||||
},
|
||||
parts: {
|
||||
},
|
||||
partOverlays: {},
|
||||
parts: {},
|
||||
mappingName: "mapping-display",
|
||||
|
||||
setVisible: function(visible) {
|
||||
print("CONTROLLER_DISPLAY::Setting visible", this.overlays.length);
|
||||
for (var i = 0; i < this.overlays.length; ++i) {
|
||||
print("i", i, this.overlays[i]);
|
||||
Overlays.editOverlay(this.overlays[i], {
|
||||
visible: visible
|
||||
});
|
||||
|
@ -30,7 +49,6 @@ createControllerDisplay = function(config) {
|
|||
setPartVisible: function(partName, visible) {
|
||||
return;
|
||||
if (partName in this.partOverlays) {
|
||||
debug("Setting part visible", partName, visible);
|
||||
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
|
||||
Overlays.editOverlay(this.partOverlays[partName][i], {
|
||||
//visible: visible
|
||||
|
@ -41,7 +59,6 @@ createControllerDisplay = function(config) {
|
|||
|
||||
setLayerForPart: function(partName, layerName) {
|
||||
if (partName in this.parts) {
|
||||
debug("Setting layer...", partName, layerName);
|
||||
var part = this.parts[partName];
|
||||
if (part.textureLayers && layerName in part.textureLayers) {
|
||||
var layer = part.textureLayers[layerName];
|
||||
|
@ -50,6 +67,9 @@ createControllerDisplay = function(config) {
|
|||
textures[part.textureName] = layer.defaultTextureURL;
|
||||
}
|
||||
for (var i = 0; i < this.partOverlays[partName].length; ++i) {
|
||||
|
||||
// AJT: REMOVE
|
||||
print("AJT: Overlays.editOverlays(" + partName + ", " + i + ", { textures: " + JSON.stringify(textures) + " })");
|
||||
Overlays.editOverlay(this.partOverlays[partName][i], {
|
||||
textures: textures
|
||||
});
|
||||
|
@ -64,8 +84,7 @@ createControllerDisplay = function(config) {
|
|||
var position = controller.position;
|
||||
|
||||
if (controller.naturalPosition) {
|
||||
position = Vec3.sum(Vec3.multiplyQbyV(
|
||||
controller.rotation, controller.naturalPosition), position);
|
||||
position = Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position);
|
||||
}
|
||||
|
||||
var overlayID = Overlays.addOverlay("model", {
|
||||
|
@ -75,26 +94,17 @@ createControllerDisplay = function(config) {
|
|||
localPosition: position,
|
||||
parentID: PARENT_ID,
|
||||
parentJointIndex: controller.jointIndex,
|
||||
ignoreRayIntersection: true,
|
||||
ignoreRayIntersection: true
|
||||
});
|
||||
|
||||
controllerDisplay.overlays.push(overlayID);
|
||||
overlayID = null;
|
||||
|
||||
function clamp(value, min, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
} else if (value > max) {
|
||||
return max
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (controller.parts) {
|
||||
for (var partName in controller.parts) {
|
||||
var part = controller.parts[partName];
|
||||
var partPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition));
|
||||
var innerRotation = controller.rotation
|
||||
var innerRotation = controller.rotation;
|
||||
|
||||
controllerDisplay.parts[partName] = controller.parts[partName];
|
||||
|
||||
|
@ -104,7 +114,7 @@ createControllerDisplay = function(config) {
|
|||
localRotation: innerRotation,
|
||||
parentID: PARENT_ID,
|
||||
parentJointIndex: controller.jointIndex,
|
||||
ignoreRayIntersection: true,
|
||||
ignoreRayIntersection: true
|
||||
};
|
||||
|
||||
if (part.defaultTextureLayer) {
|
||||
|
@ -113,10 +123,9 @@ createControllerDisplay = function(config) {
|
|||
properties['textures'] = textures;
|
||||
}
|
||||
|
||||
var overlayID = Overlays.addOverlay("model", properties);
|
||||
overlayID = Overlays.addOverlay("model", properties);
|
||||
|
||||
if (part.type == "rotational") {
|
||||
var range = part.maxValue - part.minValue;
|
||||
if (part.type === "rotational") {
|
||||
mapping.from([part.input]).peek().to(function(controller, overlayID, part) {
|
||||
return function(value) {
|
||||
value = clamp(value, part.minValue, part.maxValue);
|
||||
|
@ -127,7 +136,7 @@ createControllerDisplay = function(config) {
|
|||
|
||||
var offset = { x: 0, y: 0, z: 0 };
|
||||
if (part.origin) {
|
||||
var offset = Vec3.multiplyQbyV(rotation, part.origin);
|
||||
offset = Vec3.multiplyQbyV(rotation, part.origin);
|
||||
offset = Vec3.subtract(offset, part.origin);
|
||||
}
|
||||
|
||||
|
@ -138,20 +147,9 @@ createControllerDisplay = function(config) {
|
|||
localPosition: partPosition,
|
||||
localRotation: Quat.multiply(controller.rotation, rotation)
|
||||
});
|
||||
}
|
||||
};
|
||||
}(controller, overlayID, part));
|
||||
} else if (part.type == "touchpad") {
|
||||
function resolveHardware(path) {
|
||||
var parts = path.split(".");
|
||||
function resolveInner(base, path, i) {
|
||||
if (i >= path.length) {
|
||||
return base;
|
||||
}
|
||||
return resolveInner(base[path[i]], path, ++i);
|
||||
}
|
||||
return resolveInner(Controller.Hardware, parts, 0);
|
||||
}
|
||||
|
||||
} else if (part.type === "touchpad") {
|
||||
var visibleInput = resolveHardware(part.visibleInput);
|
||||
var xinput = resolveHardware(part.xInput);
|
||||
var yinput = resolveHardware(part.yInput);
|
||||
|
@ -165,7 +163,8 @@ createControllerDisplay = function(config) {
|
|||
});
|
||||
mapping.from([yinput]).peek().invert().to(function(value) {
|
||||
});
|
||||
} else if (part.type == "static") {
|
||||
} else if (part.type === "static") {
|
||||
// do nothing
|
||||
} else {
|
||||
print("TYPE NOT SUPPORTED: ", part.type);
|
||||
}
|
||||
|
@ -180,16 +179,11 @@ createControllerDisplay = function(config) {
|
|||
}
|
||||
Controller.enableMapping(controllerDisplay.mappingName);
|
||||
return controllerDisplay;
|
||||
}
|
||||
|
||||
ControllerDisplay = function() {
|
||||
};
|
||||
|
||||
deleteControllerDisplay = function(controllerDisplay) {
|
||||
print("Deleting controller display");
|
||||
for (var i = 0; i < controllerDisplay.overlays.length; ++i) {
|
||||
Overlays.deleteOverlay(controllerDisplay.overlays[i]);
|
||||
}
|
||||
Controller.disableMapping(controllerDisplay.mappingName);
|
||||
}
|
||||
|
||||
};
|
|
@ -1,49 +1,24 @@
|
|||
if (!Function.prototype.bind) {
|
||||
Function.prototype.bind = function(oThis) {
|
||||
if (typeof this !== 'function') {
|
||||
// closest thing possible to the ECMAScript 5
|
||||
// internal IsCallable function
|
||||
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
||||
}
|
||||
//
|
||||
// controllerDisplayManager.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
var aArgs = Array.prototype.slice.call(arguments, 1),
|
||||
fToBind = this,
|
||||
fNOP = function() {},
|
||||
fBound = function() {
|
||||
return fToBind.apply(this instanceof fNOP
|
||||
? this
|
||||
: oThis,
|
||||
aArgs.concat(Array.prototype.slice.call(arguments)));
|
||||
};
|
||||
/* globals ControllerDisplayManager:true createControllerDisplay deleteControllerDisplay
|
||||
VIVE_CONTROLLER_CONFIGURATION_LEFT VIVE_CONTROLLER_CONFIGURATION_RIGHT */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
if (this.prototype) {
|
||||
// Function.prototype doesn't have a prototype property
|
||||
fNOP.prototype = this.prototype;
|
||||
}
|
||||
fBound.prototype = new fNOP();
|
||||
|
||||
return fBound;
|
||||
};
|
||||
}
|
||||
|
||||
Script.setTimeout(function() { print('timeout') }, 100);
|
||||
(function () {
|
||||
|
||||
Script.include("controllerDisplay.js");
|
||||
Script.include("viveControllerConfiguration.js");
|
||||
|
||||
function debug() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift("CONTROLLER DEBUG:");
|
||||
print.apply(this, args);
|
||||
}
|
||||
|
||||
var zeroPosition = { x: 0, y: 0, z: 0 };
|
||||
var zeroRotation = { x: 0, y: 0, z: 0, w: 1 };
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Management of controller display //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Management of controller display
|
||||
//
|
||||
ControllerDisplayManager = function() {
|
||||
var self = this;
|
||||
var controllerLeft = null;
|
||||
|
@ -51,30 +26,24 @@ ControllerDisplayManager = function() {
|
|||
var controllerCheckerIntervalID = null;
|
||||
|
||||
this.setLeftVisible = function(visible) {
|
||||
print("settings controller display to visible");
|
||||
if (controllerLeft) {
|
||||
print("doign it...", visible);
|
||||
controllerLeft.setVisible(visible);
|
||||
}
|
||||
};
|
||||
|
||||
this.setRightVisible = function(visible) {
|
||||
print("settings controller display to visible");
|
||||
if (controllerRight) {
|
||||
print("doign it...", visible);
|
||||
controllerRight.setVisible(visible);
|
||||
}
|
||||
};
|
||||
|
||||
function updateControllers() {
|
||||
if (HMD.active) {
|
||||
if (HMD.active && HMD.shouldShowHandControllers()) {
|
||||
if ("Vive" in Controller.Hardware) {
|
||||
if (!controllerLeft) {
|
||||
debug("Found vive left!");
|
||||
controllerLeft = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_LEFT);
|
||||
}
|
||||
if (!controllerRight) {
|
||||
debug("Found vive right!");
|
||||
controllerRight = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_RIGHT);
|
||||
}
|
||||
// We've found the controllers, we no longer need to look for active controllers
|
||||
|
@ -83,17 +52,14 @@ ControllerDisplayManager = function() {
|
|||
controllerCheckerIntervalID = null;
|
||||
}
|
||||
} else {
|
||||
debug("HMD active, but no controllers found");
|
||||
self.deleteControllerDisplays();
|
||||
if (controllerCheckerIntervalID == null) {
|
||||
if (!controllerCheckerIntervalID) {
|
||||
controllerCheckerIntervalID = Script.setInterval(updateControllers, 1000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug("HMD inactive");
|
||||
// We aren't in HMD mode, we no longer need to look for active controllers
|
||||
if (controllerCheckerIntervalID) {
|
||||
debug("Clearing controller checker interval");
|
||||
Script.clearInterval(controllerCheckerIntervalID);
|
||||
controllerCheckerIntervalID = null;
|
||||
}
|
||||
|
@ -103,39 +69,34 @@ ControllerDisplayManager = function() {
|
|||
|
||||
Messages.subscribe('Controller-Display');
|
||||
var handleMessages = function(channel, message, sender) {
|
||||
var i, data, name, visible;
|
||||
if (!controllerLeft && !controllerRight) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender === MyAvatar.sessionUUID) {
|
||||
if (channel === 'Controller-Display') {
|
||||
var data = JSON.parse(message);
|
||||
var name = data.name;
|
||||
var visible = data.visible;
|
||||
//c.setDisplayAnnotation(name, visible);
|
||||
data = JSON.parse(message);
|
||||
name = data.name;
|
||||
visible = data.visible;
|
||||
if (controllerLeft) {
|
||||
if (name in controllerLeft.annotations) {
|
||||
debug("hiding");
|
||||
for (var i = 0; i < controllerLeft.annotations[name].length; ++i) {
|
||||
debug("hiding", i);
|
||||
for (i = 0; i < controllerLeft.annotations[name].length; ++i) {
|
||||
Overlays.editOverlay(controllerLeft.annotations[name][i], { visible: visible });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (controllerRight) {
|
||||
if (name in controllerRight.annotations) {
|
||||
debug("hiding");
|
||||
for (var i = 0; i < controllerRight.annotations[name].length; ++i) {
|
||||
debug("hiding", i);
|
||||
for (i = 0; i < controllerRight.annotations[name].length; ++i) {
|
||||
Overlays.editOverlay(controllerRight.annotations[name][i], { visible: visible });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (channel === 'Controller-Display-Parts') {
|
||||
debug('here part');
|
||||
var data = JSON.parse(message);
|
||||
for (var name in data) {
|
||||
var visible = data[name];
|
||||
data = JSON.parse(message);
|
||||
for (name in data) {
|
||||
visible = data[name];
|
||||
if (controllerLeft) {
|
||||
controllerLeft.setPartVisible(name, visible);
|
||||
}
|
||||
|
@ -144,8 +105,8 @@ ControllerDisplayManager = function() {
|
|||
}
|
||||
}
|
||||
} else if (channel === 'Controller-Set-Part-Layer') {
|
||||
var data = JSON.parse(message);
|
||||
for (var name in data) {
|
||||
data = JSON.parse(message);
|
||||
for (name in data) {
|
||||
var layer = data[name];
|
||||
if (controllerLeft) {
|
||||
controllerLeft.setLayerForPart(name, layer);
|
||||
|
@ -154,20 +115,17 @@ ControllerDisplayManager = function() {
|
|||
controllerRight.setLayerForPart(name, layer);
|
||||
}
|
||||
}
|
||||
} else if (channel == 'Hifi-Object-Manipulation') {// && sender == MyAvatar.sessionUUID) {
|
||||
//print("got manip");
|
||||
var data = JSON.parse(message);
|
||||
//print("post data", data);
|
||||
var visible = data.action != 'equip';
|
||||
//print("Calling...");
|
||||
if (data.joint == "LeftHand") {
|
||||
} else if (channel === 'Hifi-Object-Manipulation') {
|
||||
data = JSON.parse(message);
|
||||
visible = data.action !== 'equip';
|
||||
if (data.joint === "LeftHand") {
|
||||
self.setLeftVisible(visible);
|
||||
} else if (data.joint == "RightHand") {
|
||||
} else if (data.joint === "RightHand") {
|
||||
self.setRightVisible(visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Messages.messageReceived.connect(handleMessages);
|
||||
|
||||
|
@ -183,12 +141,24 @@ ControllerDisplayManager = function() {
|
|||
};
|
||||
|
||||
this.destroy = function() {
|
||||
print("Destroying controller display");
|
||||
Messages.messageReceived.disconnect(handleMessages);
|
||||
|
||||
HMD.displayModeChanged.disconnect(updateControllers);
|
||||
HMD.shouldShowHandControllersChanged.disconnect(updateControllers);
|
||||
|
||||
self.deleteControllerDisplays();
|
||||
};
|
||||
|
||||
HMD.displayModeChanged.connect(updateControllers);
|
||||
HMD.shouldShowHandControllersChanged.connect(updateControllers);
|
||||
|
||||
updateControllers();
|
||||
}
|
||||
};
|
||||
|
||||
var controllerDisplayManager = new ControllerDisplayManager();
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
controllerDisplayManager.destroy();
|
||||
});
|
||||
|
||||
}());
|
|
@ -11,7 +11,10 @@
|
|||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings, Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications */
|
||||
|
||||
/* global setEntityCustomData, getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
|
||||
Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications */
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
//
|
||||
// viveControllerConfiguration.js
|
||||
//
|
||||
// Created by Anthony J. Thibault on 10/20/16
|
||||
// Originally created by Ryan Huffman on 9/21/2016
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* globals VIVE_CONTROLLER_CONFIGURATION_LEFT:true VIVE_CONTROLLER_CONFIGURATION_RIGHT:true */
|
||||
/* eslint camelcase: ["error", { "properties": "never" }] */
|
||||
|
||||
var LEFT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND");
|
||||
var RIGHT_JOINT_INDEX = MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND");
|
||||
|
||||
|
@ -44,8 +57,11 @@ var viveNaturalPosition = {
|
|||
z: 0.06380049744620919
|
||||
};
|
||||
|
||||
var viveModelURL = "atp:/controller/vive_body.fbx";
|
||||
var viveTipsModelURL = "atp:/controller/vive_tips.fbx"
|
||||
var BASE_URL = Script.resourcesPath();
|
||||
var TIP_TEXTURE_BASE_URL = BASE_URL + "meshes/controller/vive_tips.fbm/";
|
||||
|
||||
var viveModelURL = BASE_URL + "meshes/controller/vive_body.fbx";
|
||||
var viveTipsModelURL = BASE_URL + "meshes/controller/vive_tips.fbx";
|
||||
|
||||
VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
||||
name: "Vive",
|
||||
|
@ -70,20 +86,20 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Blank.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Trigger.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Rotate.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Grip.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Grip.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Teleport.png",
|
||||
},
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Teleport.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -91,7 +107,7 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
// and swaps in textures based on the thumb position.
|
||||
touchpad: {
|
||||
type: "touchpad",
|
||||
modelURL: "atp:/controller/vive_trackpad.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trackpad.fbx",
|
||||
visibleInput: "Vive.RSTouch",
|
||||
xInput: "Vive.LX",
|
||||
yInput: "Vive.LY",
|
||||
|
@ -105,64 +121,63 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
|
|||
disable_defaultTextureLayer: "blank",
|
||||
disable_textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: "atp:/controller/vive_trigger.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trigger.fbx",
|
||||
input: Controller.Standard.LT,
|
||||
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
|
||||
origin: { x: 0, y: -0.015, z: -0.00 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 20,
|
||||
maxAngle: 20
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226}
|
||||
},
|
||||
|
||||
r_grip: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226}
|
||||
},
|
||||
|
||||
sys_button: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311}
|
||||
},
|
||||
|
||||
button: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_button.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
|
||||
},
|
||||
button2: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_button.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
||||
name: "Vive Right",
|
||||
controllers: [
|
||||
|
@ -192,20 +207,20 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
defaultTextureLayer: "blank",
|
||||
textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Blank.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Blank.png"
|
||||
},
|
||||
trigger: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Trigger.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Trigger.png"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Rotate.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Rotate.png"
|
||||
},
|
||||
grip: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Grip.png",
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Grip.png"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: viveTipsModelURL + "/Controller-Tips.fbm/Teleport.png",
|
||||
},
|
||||
defaultTextureURL: TIP_TEXTURE_BASE_URL + "/Teleport.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -213,7 +228,7 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
// and swaps in textures based on the thumb position.
|
||||
touchpad: {
|
||||
type: "touchpad",
|
||||
modelURL: "atp:/controller/vive_trackpad.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trackpad.fbx",
|
||||
visibleInput: "Vive.RSTouch",
|
||||
xInput: "Vive.RX",
|
||||
yInput: "Vive.RY",
|
||||
|
@ -227,58 +242,58 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = {
|
|||
disable_defaultTextureLayer: "blank",
|
||||
disable_textureLayers: {
|
||||
blank: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-blank.jpg"
|
||||
},
|
||||
teleport: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-teleport-active-LG.jpg"
|
||||
},
|
||||
arrows: {
|
||||
defaultTextureURL: "atp:/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows-active.jpg",
|
||||
defaultTextureURL: BASE_URL + "meshes/controller/vive_trackpad.fbx/Touchpad.fbm/touchpad-look-arrows-active.jpg"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
trigger: {
|
||||
type: "rotational",
|
||||
modelURL: "atp:/controller/vive_trigger.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_trigger.fbx",
|
||||
input: Controller.Standard.RT,
|
||||
naturalPosition: {"x":0.000004500150680541992,"y":-0.027690507471561432,"z":0.04830199480056763},
|
||||
origin: { x: 0, y: -0.015, z: -0.00 },
|
||||
minValue: 0.0,
|
||||
maxValue: 1.0,
|
||||
axis: { x: -1, y: 0, z: 0 },
|
||||
maxAngle: 25,
|
||||
maxAngle: 25
|
||||
},
|
||||
|
||||
l_grip: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_l_grip.fbx",
|
||||
naturalPosition: {"x":-0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226}
|
||||
},
|
||||
|
||||
r_grip: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_r_grip.fbx",
|
||||
naturalPosition: {"x":0.01720449887216091,"y":-0.014324013143777847,"z":0.08714400231838226}
|
||||
},
|
||||
|
||||
sys_button: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311},
|
||||
modelURL: BASE_URL + "meshes/controller/vive_sys_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.0020399854984134436,"z":0.08825899660587311}
|
||||
},
|
||||
|
||||
button: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_button.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
|
||||
},
|
||||
button2: {
|
||||
type: "static",
|
||||
modelURL: "atp:/controller/vive_button.fbx",
|
||||
modelURL: BASE_URL + "meshes/controller/vive_button.fbx",
|
||||
naturalPosition: {"x":0,"y":0.005480996798723936,"z":0.019918499514460564}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
|
@ -16,6 +16,8 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
|||
SPACE_LOCAL = "local";
|
||||
SPACE_WORLD = "world";
|
||||
|
||||
Script.include("./controllers.js");
|
||||
|
||||
function objectTranslationPlanePoint(position, dimensions) {
|
||||
var newPosition = { x: position.x, y: position.y, z: position.z };
|
||||
newPosition.y -= dimensions.y / 2.0;
|
||||
|
@ -1046,12 +1048,11 @@ SelectionDisplay = (function() {
|
|||
that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand));
|
||||
that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand));
|
||||
function controllerComputePickRay() {
|
||||
var controllerPose = Controller.getPoseValue(activeHand);
|
||||
var controllerPose = getControllerWorldLocation(activeHand, true);
|
||||
if (controllerPose.valid && that.triggered) {
|
||||
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
|
||||
MyAvatar.position);
|
||||
var controllerPosition = controllerPose.translation;
|
||||
// This gets point direction right, but if you want general quaternion it would be more complicated:
|
||||
var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation));
|
||||
var controllerDirection = Quat.getUp(controllerPose.rotation);
|
||||
return {origin: controllerPosition, direction: controllerDirection};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
//
|
||||
|
||||
Script.include("entityData.js");
|
||||
Script.include("viveHandsv2.js");
|
||||
Script.include("lighter/createButaneLighter.js");
|
||||
Script.include("tutorialEntityIDs.js");
|
||||
|
||||
|
@ -235,7 +234,7 @@ var stepDisableControllers = function(name) {
|
|||
}
|
||||
stepDisableControllers.prototype = {
|
||||
start: function(onFinish) {
|
||||
controllerDisplayManager = new ControllerDisplayManager();
|
||||
HMD.requestShowHandControllers();
|
||||
disableEverything();
|
||||
|
||||
onFinish();
|
||||
|
@ -276,10 +275,7 @@ function reenableEverything() {
|
|||
setControllerPartLayer('touchpad', 'blank');
|
||||
setControllerPartLayer('tips', 'blank');
|
||||
MyAvatar.shouldRenderLocally = true;
|
||||
if (controllerDisplayManager) {
|
||||
controllerDisplayManager.destroy();
|
||||
controllerDisplayManager = null;
|
||||
}
|
||||
HMD.requestHideHandControllers();
|
||||
setAwayEnabled(true);
|
||||
}
|
||||
|
||||
|
|