merge upstream/master into andrew/bispinor

This commit is contained in:
Andrew Meadows 2014-07-02 11:23:42 -07:00
commit f5debdb930
91 changed files with 3329 additions and 1077 deletions
BUILD.md
assignment-client/src
cmake/modules
interface
libraries
tests
tools
bitstream2json
json2bitstream

View file

@ -63,7 +63,7 @@ If `libgnutls28-dev` 3.2.12 or higher is available via your package manager, it
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple.
brew tap highfidelity/homebrew-formulas
brew install cmake glm zlib gnutls
brew install cmake glm gnutls
brew install highfidelity/formulas/qt5
brew link qt5 --force
brew install highfidelity/formulas/qxmpp

View file

@ -147,6 +147,15 @@ void Agent::readPendingDatagrams() {
}
} else if (datagramPacketType == PacketTypeMixedAudio) {
QUuid senderUUID = uuidFromPacketHeader(receivedPacket);
// parse sequence number for this packet
int numBytesPacketHeader = numBytesForPacketHeader(receivedPacket);
const char* sequenceAt = receivedPacket.constData() + numBytesPacketHeader;
quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
_incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
// parse the data and grab the average loudness
_receivedAudioBuffer.parseData(receivedPacket);

View file

@ -71,6 +71,8 @@ private:
ModelTreeHeadlessViewer _modelViewer;
MixedAudioRingBuffer _receivedAudioBuffer;
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
AvatarHashMap _avatarHashMap;
};

View file

@ -78,7 +78,8 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
_sumListeners(0),
_sumMixes(0),
_sourceUnattenuatedZone(NULL),
_listenerUnattenuatedZone(NULL)
_listenerUnattenuatedZone(NULL),
_lastSendAudioStreamStatsTime(usecTimestampNow())
{
}
@ -451,7 +452,7 @@ void AudioMixer::sendStatsPacket() {
AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
if (clientData) {
QString property = "jitterStats." + node->getUUID().toString();
QString value = clientData->getJitterBufferStats();
QString value = clientData->getAudioStreamStatsString();
statsObject2[qPrintable(property)] = value;
somethingToSend = true;
sizeOfStats += property.size() + value.size();
@ -562,7 +563,7 @@ void AudioMixer::run() {
QElapsedTimer timer;
timer.start();
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO
char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + sizeof(quint16)
+ numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
@ -631,20 +632,50 @@ void AudioMixer::run() {
++framesSinceCutoffEvent;
}
const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
bool sendAudioStreamStats = false;
quint64 now = usecTimestampNow();
if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) {
_lastSendAudioStreamStatsTime = now;
sendAudioStreamStats = true;
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
prepareMixForListeningNode(node.data());
// pack header
int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
char* dataAt = clientMixBuffer + numBytesPacketHeader;
memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node);
// pack sequence number
quint16 sequence = nodeData->getOutgoingSequenceNumber();
memcpy(dataAt, &sequence, sizeof(quint16));
dataAt += sizeof(quint16);
// pack mixed audio samples
memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO;
// send mixed audio packet
nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node);
nodeData->incrementOutgoingMixedAudioSequenceNumber();
// send an audio stream stats packet if it's time
if (sendAudioStreamStats) {
nodeData->sendAudioStreamStatsPackets(node);
}
++_sumListeners;
}
}
// push forward the next output pointers for any audio buffers we used
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
if (node->getLinkedData()) {

View file

@ -58,6 +58,8 @@ private:
AABox* _sourceUnattenuatedZone;
AABox* _listenerUnattenuatedZone;
static bool _useDynamicJitterBuffers;
quint64 _lastSendAudioStreamStatsTime;
};
#endif // hifi_AudioMixer_h

View file

@ -20,7 +20,9 @@
#include "AudioMixerClientData.h"
AudioMixerClientData::AudioMixerClientData() :
_ringBuffers()
_ringBuffers(),
_outgoingMixedAudioSequenceNumber(0),
_incomingAvatarAudioSequenceNumberStats()
{
}
@ -44,16 +46,24 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
}
int AudioMixerClientData::parseData(const QByteArray& packet) {
// parse sequence number for this packet
int numBytesPacketHeader = numBytesForPacketHeader(packet);
const char* sequenceAt = packet.constData() + numBytesPacketHeader;
quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
PacketType packetType = packetTypeForPacket(packet);
if (packetType == PacketTypeMicrophoneAudioWithEcho
|| packetType == PacketTypeMicrophoneAudioNoEcho
|| packetType == PacketTypeSilentAudioFrame) {
_incomingAvatarAudioSequenceNumberStats.sequenceNumberReceived(sequence);
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
// read the first byte after the header to see if this is a stereo or mono buffer
quint8 channelFlag = packet.at(numBytesForPacketHeader(packet));
quint8 channelFlag = packet.at(numBytesForPacketHeader(packet) + sizeof(quint16));
bool isStereo = channelFlag == 1;
if (avatarRingBuffer && avatarRingBuffer->isStereo() != isStereo) {
@ -76,7 +86,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
// this is injected audio
// grab the stream identifier for this injected audio
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID));
_incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence);
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
@ -133,6 +145,9 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
} else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
&& audioBuffer->hasStarted() && audioBuffer->isStarved()) {
// this is an empty audio buffer that has starved, safe to delete
// also delete its sequence number stats
QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier();
_incomingInjectedAudioSequenceNumberStatsMap.remove(streamIdentifier);
delete audioBuffer;
i = _ringBuffers.erase(i);
continue;
@ -141,42 +156,123 @@ void AudioMixerClientData::pushBuffersAfterFrameSend() {
}
}
QString AudioMixerClientData::getJitterBufferStats() const {
AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const {
AudioStreamStats streamStats;
SequenceNumberStats streamSequenceNumberStats;
streamStats._streamType = ringBuffer->getType();
if (streamStats._streamType == PositionalAudioRingBuffer::Injector) {
streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier();
streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier);
} else {
streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats;
}
streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived();
streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable();
streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly();
streamStats._packetsLate = streamSequenceNumberStats.getNumLate();
streamStats._packetsLost = streamSequenceNumberStats.getNumLost();
streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered();
streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate();
return streamStats;
}
void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const {
char packet[MAX_PACKET_SIZE];
NodeList* nodeList = NodeList::getInstance();
// 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;
// pack header
int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
char* headerEndAt = packet + numBytesPacketHeader;
// calculate how many stream stat structs we can fit in each packet
const int numStreamStatsRoomFor = (MAX_PACKET_SIZE - numBytesPacketHeader - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
// pack and send stream stats packets until all ring buffers' stats are sent
int numStreamStatsRemaining = _ringBuffers.size();
QList<PositionalAudioRingBuffer*>::ConstIterator ringBuffersIterator = _ringBuffers.constBegin();
while (numStreamStatsRemaining > 0) {
char* dataAt = headerEndAt;
// pack the append flag
memcpy(dataAt, &appendFlag, sizeof(quint8));
appendFlag = 1;
dataAt += sizeof(quint8);
// calculate and pack the number of stream stats to follow
quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
dataAt += sizeof(quint16);
// pack the calculated number of stream stats
for (int i = 0; i < numStreamStatsToPack; i++) {
AudioStreamStats streamStats = getAudioStreamStatsOfStream(*ringBuffersIterator);
memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
ringBuffersIterator++;
}
numStreamStatsRemaining -= numStreamStatsToPack;
// send the current packet
nodeList->writeDatagram(packet, dataAt - packet, destinationNode);
}
}
QString AudioMixerClientData::getAudioStreamStatsString() const {
QString result;
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
if (avatarRingBuffer) {
int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
int resetCount = avatarRingBuffer->getResetCount();
int overflowCount = avatarRingBuffer->getOverflowCount();
int samplesAvailable = avatarRingBuffer->samplesAvailable();
int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
result += "mic.desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " resets:" + QString::number(resetCount);
AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer);
result += "mic.desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " overflows:" + QString::number(overflowCount)
+ " early:" + QString::number(streamStats._packetsEarly)
+ " late:" + QString::number(streamStats._packetsLate)
+ " lost:" + QString::number(streamStats._packetsLost);
} else {
result = "mic unknown";
}
for (int i = 0; i < _ringBuffers.size(); i++) {
if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
int resetCount = _ringBuffers[i]->getResetCount();
int overflowCount = _ringBuffers[i]->getOverflowCount();
int samplesAvailable = _ringBuffers[i]->samplesAvailable();
int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
result += "| injected["+QString::number(i)+"].desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " resets:" + QString::number(resetCount);
AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]);
result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer)
+ " calculated:" + QString::number(calculatedJitterBuffer)
+ " current:" + QString::number(currentJitterBuffer)
+ " available:" + QString::number(framesAvailable)
+ " samples:" + QString::number(samplesAvailable)
+ " overflows:" + QString::number(overflowCount)
+ " early:" + QString::number(streamStats._packetsEarly)
+ " late:" + QString::number(streamStats._packetsLate)
+ " lost:" + QString::number(streamStats._packetsLost);
}
}
return result;
}

View file

@ -17,6 +17,8 @@
#include <PositionalAudioRingBuffer.h>
#include "AvatarAudioRingBuffer.h"
#include "AudioStreamStats.h"
#include "SequenceNumberStats.h"
class AudioMixerClientData : public NodeData {
public:
@ -30,10 +32,20 @@ public:
void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
void pushBuffersAfterFrameSend();
QString getJitterBufferStats() const;
AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const;
QString getAudioStreamStatsString() const;
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const;
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
private:
QList<PositionalAudioRingBuffer*> _ringBuffers;
quint16 _outgoingMixedAudioSequenceNumber;
SequenceNumberStats _incomingAvatarAudioSequenceNumberStats;
QHash<QUuid, SequenceNumberStats> _incomingInjectedAudioSequenceNumberStatsMap;
};
#endif // hifi_AudioMixerClientData_h

View file

@ -69,7 +69,7 @@ void MetavoxelServer::readPendingDatagrams() {
void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
QMutexLocker locker(&node->getMutex());
node->setLinkedData(new MetavoxelSession(this, NodeList::getInstance()->nodeWithUUID(node->getUUID())));
node->setLinkedData(new MetavoxelSession(node, this));
}
}
@ -77,7 +77,7 @@ void MetavoxelServer::sendDeltas() {
// send deltas for all sessions
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::Agent) {
static_cast<MetavoxelSession*>(node->getLinkedData())->sendDelta();
static_cast<MetavoxelSession*>(node->getLinkedData())->update();
}
}
@ -89,59 +89,34 @@ void MetavoxelServer::sendDeltas() {
_sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
}
MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node) :
_server(server),
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)),
_node(node) {
MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) :
Endpoint(node, new PacketRecord(), NULL),
_server(server) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
SLOT(handleMessage(const QVariant&)));
// insert the baseline send record
SendRecord record = { 0 };
_sendRecords.append(record);
}
MetavoxelSession::~MetavoxelSession() {
}
int MetavoxelSession::parseData(const QByteArray& packet) {
// process through sequencer
_sequencer.receivedDatagram(packet);
return packet.size();
}
void MetavoxelSession::sendDelta() {
void MetavoxelSession::update() {
// wait until we have a valid lod
if (!_lod.isValid()) {
return;
if (_lod.isValid()) {
Endpoint::update();
}
Bitstream& out = _sequencer.startPacket();
}
void MetavoxelSession::writeUpdateMessage(Bitstream& out) {
out << QVariant::fromValue(MetavoxelDeltaMessage());
_server->getData().writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod);
_sequencer.endPacket();
// record the send
SendRecord record = { _sequencer.getOutgoingPacketNumber(), _server->getData(), _lod };
_sendRecords.append(record);
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
_server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
}
void MetavoxelSession::sendData(const QByteArray& data) {
NodeList::getInstance()->writeDatagram(data, _node);
}
void MetavoxelSession::readPacket(Bitstream& in) {
QVariant message;
in >> message;
void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
handleMessage(message);
}
void MetavoxelSession::clearSendRecordsBefore(int index) {
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
return new PacketRecord(_lod, _server->getData());
}
void MetavoxelSession::handleMessage(const QVariant& message) {

View file

@ -17,8 +17,7 @@
#include <ThreadedAssignment.h>
#include <DatagramSequencer.h>
#include <MetavoxelData.h>
#include <Endpoint.h>
class MetavoxelEditMessage;
class MetavoxelSession;
@ -53,46 +52,31 @@ private:
};
/// Contains the state of a single client session.
class MetavoxelSession : public NodeData {
class MetavoxelSession : public Endpoint {
Q_OBJECT
public:
MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node);
virtual ~MetavoxelSession();
MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server);
virtual int parseData(const QByteArray& packet);
virtual void update();
void sendDelta();
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
private slots:
void sendData(const QByteArray& data);
void readPacket(Bitstream& in);
void clearSendRecordsBefore(int index);
void handleMessage(const QVariant& message);
private:
class SendRecord {
public:
int packetNumber;
MetavoxelData data;
MetavoxelLOD lod;
};
MetavoxelServer* _server;
DatagramSequencer _sequencer;
SharedNodePointer _node;
MetavoxelLOD _lod;
QList<SendRecord> _sendRecords;
};
#endif // hifi_MetavoxelServer_h

View file

@ -28,7 +28,8 @@ OctreeInboundPacketProcessor::OctreeInboundPacketProcessor(OctreeServer* myServe
_totalLockWaitTime(0),
_totalElementsInPacket(0),
_totalPackets(0),
_lastNackTime(usecTimestampNow())
_lastNackTime(usecTimestampNow()),
_shuttingDown(false)
{
}
@ -72,6 +73,10 @@ void OctreeInboundPacketProcessor::midProcess() {
}
void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
if (_shuttingDown) {
qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet";
return;
}
bool debugProcessPacket = _myServer->wantsVerboseDebug();
@ -182,8 +187,13 @@ void OctreeInboundPacketProcessor::trackInboundPacket(const QUuid& nodeUUID, uns
}
int OctreeInboundPacketProcessor::sendNackPackets() {
int packetsSent = 0;
if (_shuttingDown) {
qDebug() << "OctreeInboundPacketProcessor::sendNackPackets() while shutting down... ignore";
return packetsSent;
}
char packet[MAX_PACKET_SIZE];
NodeToSenderStatsMapIterator i = _singleSenderStats.begin();
@ -206,7 +216,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
}
const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getMissingSequenceNumbers();
const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getIncomingEditSequenceNumberStats().getMissingSet();
// construct nack packet(s) for this node
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
@ -241,6 +251,8 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
// send it
NodeList::getInstance()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
packetsSent++;
qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
}
i++;
}
@ -254,8 +266,7 @@ SingleSenderStats::SingleSenderStats()
_totalLockWaitTime(0),
_totalElementsInPacket(0),
_totalPackets(0),
_incomingLastSequence(0),
_missingSequenceNumbers()
_incomingEditSequenceNumberStats()
{
}
@ -263,74 +274,8 @@ SingleSenderStats::SingleSenderStats()
void SingleSenderStats::trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
int editsInPacket, quint64 processTime, quint64 lockWaitTime) {
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
const int MAX_MISSING_SEQUENCE_SIZE = 100;
unsigned short int expectedSequence = _totalPackets == 0 ? incomingSequence : _incomingLastSequence + (unsigned short int)1;
if (incomingSequence == expectedSequence) { // on time
_incomingLastSequence = incomingSequence;
} else { // out of order
int incoming = (int)incomingSequence;
int expected = (int)expectedSequence;
// check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
int absGap = std::abs(incoming - expected);
if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
// rollover likely occurred between incoming and expected.
// correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
if (incoming > expected) {
incoming -= UINT16_RANGE;
} else {
expected -= UINT16_RANGE;
}
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
// ignore packet if gap is unreasonable
qDebug() << "ignoring unreasonable packet... sequence:" << incomingSequence
<< "_incomingLastSequence:" << _incomingLastSequence;
return;
}
// now that rollover has been corrected for (if it occurred), incoming and expected can be
// compared to each other directly, though one of them might be negative
if (incoming > expected) { // early
// add all sequence numbers that were skipped to the missing sequence numbers list
for (int missingSequence = expected; missingSequence < incoming; missingSequence++) {
_missingSequenceNumbers.insert(missingSequence < 0 ? missingSequence + UINT16_RANGE : missingSequence);
}
_incomingLastSequence = incomingSequence;
} else { // late
// remove this from missing sequence number if it's in there
_missingSequenceNumbers.remove(incomingSequence);
// do not update _incomingLastSequence; it shouldn't become smaller
}
}
// prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
// will be removed.
if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE_SIZE) {
// some older sequence numbers may be from before a rollover point; this must be handled.
// some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
// before the most recent rollover.
int cutoff = (int)_incomingLastSequence - MAX_REASONABLE_SEQUENCE_GAP;
if (cutoff >= 0) {
foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
unsigned short int nonRolloverCutoff = (unsigned short int)cutoff;
if (missingSequence > _incomingLastSequence || missingSequence <= nonRolloverCutoff) {
_missingSequenceNumbers.remove(missingSequence);
}
}
} else {
unsigned short int rolloverCutoff = (unsigned short int)(cutoff + UINT16_RANGE);
foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
if (missingSequence > _incomingLastSequence && missingSequence <= rolloverCutoff) {
_missingSequenceNumbers.remove(missingSequence);
}
}
}
}
// track sequence number
_incomingEditSequenceNumberStats.sequenceNumberReceived(incomingSequence);
// update other stats
_totalTransitTime += transitTime;

View file

@ -14,9 +14,10 @@
#ifndef hifi_OctreeInboundPacketProcessor_h
#define hifi_OctreeInboundPacketProcessor_h
#include <map>
#include <ReceivedPacketProcessor.h>
#include "SequenceNumberStats.h"
class OctreeServer;
class SingleSenderStats {
@ -32,7 +33,8 @@ public:
{ return _totalElementsInPacket == 0 ? 0 : _totalProcessTime / _totalElementsInPacket; }
quint64 getAverageLockWaitTimePerElement() const
{ return _totalElementsInPacket == 0 ? 0 : _totalLockWaitTime / _totalElementsInPacket; }
const QSet<unsigned short int>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
const SequenceNumberStats& getIncomingEditSequenceNumberStats() const { return _incomingEditSequenceNumberStats; }
void trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
int editsInPacket, quint64 processTime, quint64 lockWaitTime);
@ -42,9 +44,7 @@ public:
quint64 _totalLockWaitTime;
quint64 _totalElementsInPacket;
quint64 _totalPackets;
unsigned short int _incomingLastSequence;
QSet<unsigned short int> _missingSequenceNumbers;
SequenceNumberStats _incomingEditSequenceNumberStats;
};
typedef QHash<QUuid, SingleSenderStats> NodeToSenderStatsMap;
@ -73,6 +73,8 @@ public:
NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; }
void shuttingDown() { _shuttingDown = true;}
protected:
virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
@ -100,5 +102,6 @@ private:
NodeToSenderStatsMap _singleSenderStats;
quint64 _lastNackTime;
bool _shuttingDown;
};
#endif // hifi_OctreeInboundPacketProcessor_h

View file

@ -1097,6 +1097,8 @@ void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
void OctreeServer::aboutToFinish() {
qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
_octreeInboundPacketProcessor->shuttingDown();
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
forceNodeShutdown(node);

View file

@ -42,14 +42,11 @@ else (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS)
if (UDEV_LIBRARY AND XINERAMA_LIBRARY AND OVR_LIBRARY)
set(LIBOVR_LIBRARIES "${OVR_LIBRARY};${UDEV_LIBRARY};${XINERAMA_LIBRARY}" CACHE INTERNAL "Oculus libraries")
endif (UDEV_LIBRARY AND XINERAMA_LIBRARY AND OVR_LIBRARY)
elseif (WIN32)
if (CMAKE_BUILD_TYPE MATCHES DEBUG)
set(WINDOWS_LIBOVR_NAME "libovrd.lib")
else()
set(WINDOWS_LIBOVR_NAME "libovr.lib")
endif()
elseif (WIN32)
find_library(LIBOVR_RELEASE_LIBRARIES "Lib/Win32/libovr.lib" HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_DEBUG_LIBRARIES "Lib/Win32/libovrd.lib" HINTS ${LIBOVR_SEARCH_DIRS})
find_library(LIBOVR_LIBRARIES "Lib/Win32/${WINDOWS_LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
set(LIBOVR_LIBRARIES "${LIBOVR_RELEASE_LIBRARIES} ${LIBOVR_DEBUG_LIBRARIES}")
endif ()
if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES)

View file

@ -183,7 +183,11 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}")
endif ()
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
if (WIN32)
target_link_libraries(${TARGET_NAME} optimized "${LIBOVR_RELEASE_LIBRARIES}" debug "${LIBOVR_DEBUG_LIBRARIES}")
else()
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
endif()
endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
# and with PrioVR library

View file

@ -2,18 +2,12 @@
Instructions for adding the Oculus library (LibOVR) to Interface
Stephen Birarda, March 6, 2014
You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.2.5.
You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.3.2.
1. Copy the Oculus SDK folders from the LibOVR directory (Lib, Include, Src) into the interface/externals/oculus folder.
This readme.txt should be there as well.
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'oculus' that contains the three folders mentioned above.
NOTE: On OS X there is a linker error with version 0.2.5c of the Oculus SDK.
It must be re-built (from the included LibOVR_With_Samples.xcodeproj) with RRTI support.
In XCode Build Settings for the ovr target, set "Enable C++ Runtime Types" to yes.
Then, Archive and use the organizer to save a copy of the built products.
In the exported directory you will have a new libovr.a to copy into the oculus directory from above.
2. Clear your build directory, run cmake and build, and you should be all set.

View file

@ -4,12 +4,11 @@
// oculus.frag
// fragment shader
//
// Created by Andrzej Kapolka on 11/26/13.
// Copyright 2013 High Fidelity, Inc.
// Created by Ben Arnold on 6/24/14.
// Copyright 2014 High Fidelity, Inc.
//
// this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample
// code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license
// (http://www.apache.org/licenses/LICENSE-2.0)
// this shader is an adaptation (HLSL -> GLSL) of the one in the
// Oculus_SDK_Overview.pdf for the 3.2 SDK.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -17,23 +16,16 @@
uniform sampler2D texture;
uniform vec2 lensCenter;
uniform vec2 screenCenter;
uniform vec2 scale;
uniform vec2 scaleIn;
uniform vec4 hmdWarpParam;
vec2 hmdWarp(vec2 in01) {
vec2 theta = (in01 - lensCenter) * scaleIn;
float rSq = theta.x * theta.x + theta.y * theta.y;
vec2 theta1 = theta * (hmdWarpParam.x + hmdWarpParam.y * rSq +
hmdWarpParam.z * rSq * rSq + hmdWarpParam.w * rSq * rSq * rSq);
return lensCenter + scale * theta1;
}
varying float vFade;
varying vec2 oTexCoord0;
varying vec2 oTexCoord1;
varying vec2 oTexCoord2;
void main(void) {
vec2 tc = hmdWarp(gl_TexCoord[0].st);
vec2 below = step(screenCenter.st + vec2(-0.25, -0.5), tc.st);
vec2 above = vec2(1.0, 1.0) - step(screenCenter.st + vec2(0.25, 0.5), tc.st);
gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texture2D(texture, tc), above.s * above.t * below.s * below.t);
// 3 samples for fixing chromatic aberrations
float r = texture2D(texture, oTexCoord0.xy).r;
float g = texture2D(texture, oTexCoord1.xy).g;
float b = texture2D(texture, oTexCoord2.xy).b;
gl_FragColor = vec4(r * vFade, g * vFade, b * vFade, 1.0);
}

View file

@ -0,0 +1,63 @@
#version 120
//
// oculus.vert
// vertex shader
//
// Created by Ben Arnold on 6/24/14.
// Copyright 2014 High Fidelity, Inc.
//
// this shader is an adaptation (HLSL -> GLSL) of the one in the
// Oculus_SDK_Overview.pdf for the 3.2 SDK.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
uniform vec2 EyeToSourceUVScale;
uniform vec2 EyeToSourceUVOffset;
uniform mat4 EyeRotationStart;
uniform mat4 EyeRotationEnd;
attribute vec2 position;
attribute vec4 color;
attribute vec2 texCoord0;
attribute vec2 texCoord1;
attribute vec2 texCoord2;
varying float vFade;
varying vec2 oTexCoord0;
varying vec2 oTexCoord1;
varying vec2 oTexCoord2;
vec2 TimewarpTexCoord(vec2 texCoord, mat4 rotMat)
{
// Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic
// aberration and distortion). These are now "real world" vectors in direction (x,y,1)
// relative to the eye of the HMD. Apply the 3x3 timewarp rotation to these vectors.
vec3 transformed = vec3( rotMat * vec4(texCoord.xy, 1, 1) );
// Project them back onto the Z=1 plane of the rendered images.
vec2 flattened = (transformed.xy / transformed.z);
// Scale them into ([0,0.5],[0,1]) or ([0.5,0],[0,1]) UV lookup space (depending on eye)
return (EyeToSourceUVScale * flattened + EyeToSourceUVOffset);
}
void main()
{
float timewarpMixFactor = color.a;
mat4 mixedEyeRot = EyeRotationStart * (1.0 - timewarpMixFactor) + EyeRotationEnd * (timewarpMixFactor);
oTexCoord0 = TimewarpTexCoord(texCoord0, mixedEyeRot);
oTexCoord1 = TimewarpTexCoord(texCoord1, mixedEyeRot);
oTexCoord2 = TimewarpTexCoord(texCoord2, mixedEyeRot);
//Flip y texture coordinates
oTexCoord0.y = 1.0 - oTexCoord0.y;
oTexCoord1.y = 1.0 - oTexCoord1.y;
oTexCoord2.y = 1.0 - oTexCoord2.y;
gl_Position = vec4(position.xy, 0.5, 1.0);
vFade = color.r; // For vignette fade
}

View file

@ -60,6 +60,7 @@
#include <ParticlesScriptingInterface.h>
#include <PerfStat.h>
#include <ResourceCache.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include <OctreeSceneStats.h>
#include <LocalVoxelsList.h>
@ -268,6 +269,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
@ -396,7 +398,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
}
Application::~Application() {
int DELAY_TIME = 1000;
UserActivityLogger::getInstance().close(DELAY_TIME);
qInstallMessageHandler(NULL);
// make sure we don't call the idle timer any more
@ -565,6 +569,16 @@ void Application::paintGL() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::paintGL()");
const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect);
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
// Otherwise, it must rebuild the FBOs
if (OculusManager::isConnected()) {
_textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize());
} else {
_textureCache.setFrameBufferSize(_glWidget->size());
}
glEnable(GL_LINE_SMOOTH);
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
@ -573,28 +587,16 @@ void Application::paintGL() {
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
//Note, the camera distance is set in Camera::setMode() so we dont have to do it here.
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_myCamera.setTightness(0.0f);
glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition();
float headHeight = eyePosition.y - _myAvatar->getPosition().y;
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
}
if (OculusManager::isConnected()) {
// Oculus in third person causes nausea, so only allow it if option is checked in dev menu
if (!Menu::getInstance()->isOptionChecked(MenuOption::AllowOculusCameraModeChange) || _myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setDistance(0.0f);
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
}
_myCamera.setUpShift(0.0f);
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
}
// Update camera position
@ -629,16 +631,32 @@ void Application::paintGL() {
updateShadowMap();
}
//If we aren't using the glow shader, we have to clear the color and depth buffer
if (!glowEnabled) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
if (OculusManager::isConnected()) {
OculusManager::display(whichCamera);
//When in mirror mode, use camera rotation. Otherwise, use body rotation
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
} else {
OculusManager::display(_myAvatar->getWorldAlignedOrientation(), whichCamera.getPosition(), whichCamera);
}
} else if (TV3DManager::isConnected()) {
_glowEffect.prepare();
if (glowEnabled) {
_glowEffect.prepare();
}
TV3DManager::display(whichCamera);
_glowEffect.render();
if (glowEnabled) {
_glowEffect.render();
}
} else {
_glowEffect.prepare();
if (glowEnabled) {
_glowEffect.prepare();
}
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
@ -646,7 +664,9 @@ void Application::paintGL() {
displaySide(whichCamera);
glPopMatrix();
_glowEffect.render();
if (glowEnabled) {
_glowEffect.render();
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
renderRearViewMirror(_mirrorViewRect);
@ -2166,7 +2186,8 @@ int Application::sendNackPackets() {
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
// make copy of missing sequence numbers from stats
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = stats.getMissingSequenceNumbers();
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers =
stats.getIncomingOctreeSequenceNumberStats().getMissingSet();
_octreeSceneStatsLock.unlock();
@ -3136,9 +3157,7 @@ void Application::resetSensors() {
_faceshift.reset();
_visage.reset();
if (OculusManager::isConnected()) {
OculusManager::reset();
}
OculusManager::reset();
_prioVR.reset();
@ -3290,6 +3309,10 @@ void Application::nodeKilled(SharedNodePointer node) {
_particleEditSender.nodeKilled(node);
_modelEditSender.nodeKilled(node);
if (node->getType() == NodeType::AudioMixer) {
QMetaObject::invokeMethod(&_audio, "resetIncomingMixedAudioSequenceNumberStats");
}
if (node->getType() == NodeType::VoxelServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
@ -3558,6 +3581,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
@ -3613,6 +3637,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
// when the application is about to quit, stop our script engine so it unwinds properly
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
NodeList* nodeList = NodeList::getInstance();
connect(nodeList, &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
scriptEngine->moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
@ -3628,7 +3655,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
}
void Application::scriptFinished(const QString& scriptName) {
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptName);
const QString& scriptURLString = QUrl(scriptName).toString();
QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptURLString);
if (it != _scriptEnginesHash.end()) {
_scriptEnginesHash.erase(it);
_runningScriptsWidget->scriptStopped(scriptName);

View file

@ -102,7 +102,9 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
_scopeInput(0),
_scopeOutputLeft(0),
_scopeOutputRight(0)
_scopeOutputRight(0),
_audioMixerAvatarStreamStats(),
_outgoingAvatarAudioSequenceNumber(0)
{
// clear the array of locally injected samples
memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
@ -118,6 +120,9 @@ void Audio::init(QGLWidget *parent) {
void Audio::reset() {
_ringBuffer.reset();
_outgoingAvatarAudioSequenceNumber = 0;
_audioMixerInjectedStreamStatsMap.clear();
_incomingMixedAudioSequenceNumberStats.reset();
}
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
@ -421,13 +426,13 @@ void Audio::handleAudioInput() {
static char audioDataPacket[MAX_PACKET_SIZE];
static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
static int leadingBytes = numBytesPacketHeader + sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
static int16_t* networkAudioSamples = (int16_t*) (audioDataPacket + leadingBytes);
float inputToNetworkInputRatio = calculateDeviceToNetworkInputRatio(_numInputCallbackBytes);
unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio;
int inputSamplesRequired = (int)((float)NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio);
QByteArray inputByteArray = _inputDevice->readAll();
@ -653,6 +658,10 @@ void Audio::handleAudioInput() {
char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType);
// pack sequence number
memcpy(currentPacketPtr, &_outgoingAvatarAudioSequenceNumber, sizeof(quint16));
currentPacketPtr += sizeof(quint16);
// set the mono/stereo byte
*currentPacketPtr++ = isStereo;
@ -665,6 +674,7 @@ void Audio::handleAudioInput() {
currentPacketPtr += sizeof(headOrientation);
nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
_outgoingAvatarAudioSequenceNumber++;
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
.updateValue(numAudioBytes + leadingBytes);
@ -707,6 +717,36 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
}
void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
int numBytesPacketHeader = numBytesForPacketHeader(packet);
const char* dataAt = packet.constData() + numBytesPacketHeader;
// parse the appendFlag, clear injected audio stream stats if 0
quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
dataAt += sizeof(quint8);
if (!appendFlag) {
_audioMixerInjectedStreamStatsMap.clear();
}
// parse the number of stream stats structs to follow
quint16 numStreamStats = *(reinterpret_cast<const quint16*>(dataAt));
dataAt += sizeof(quint16);
// parse the stream stats
AudioStreamStats streamStats;
for (quint16 i = 0; i < numStreamStats; i++) {
memcpy(&streamStats, dataAt, sizeof(AudioStreamStats));
dataAt += sizeof(AudioStreamStats);
if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) {
_audioMixerAvatarStreamStats = streamStats;
} else {
_audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
}
}
}
// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
// data we know that we will have 2x samples for each stereo time sample at the format's sample rate
void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
@ -806,6 +846,16 @@ void Audio::toggleStereoInput() {
}
void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
QUuid senderUUID = uuidFromPacketHeader(audioByteArray);
// parse sequence number for this packet
int numBytesPacketHeader = numBytesForPacketHeader(audioByteArray);
const char* sequenceAt = audioByteArray.constData() + numBytesPacketHeader;
quint16 sequence = *((quint16*)sequenceAt);
_incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
// parse audio data
_ringBuffer.parseData(audioByteArray);
float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
@ -828,7 +878,8 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
QByteArray outputBuffer;
outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
int numSamplesNeededToStartPlayback = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2);
int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2),
_ringBuffer.getSampleCapacity());
if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) {
// We are still waiting for enough samples to begin playback
@ -845,6 +896,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
buffer.resize(numNetworkOutputSamples * sizeof(int16_t));
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
// Accumulate direct transmission of audio from sender to receiver
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);

View file

@ -16,6 +16,7 @@
#include <vector>
#include "InterfaceConfig.h"
#include "AudioStreamStats.h"
#include <QAudio>
#include <QAudioInput>
@ -72,13 +73,17 @@ public:
bool getProcessSpatialAudio() const { return _processSpatialAudio; }
const SequenceNumberStats& getIncomingMixedAudioSequenceNumberStats() const { return _incomingMixedAudioSequenceNumberStats; }
public slots:
void start();
void stop();
void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
void parseAudioStreamStatsPacket(const QByteArray& packet);
void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
void handleAudioInput();
void reset();
void resetIncomingMixedAudioSequenceNumberStats() { _incomingMixedAudioSequenceNumberStats.reset(); }
void toggleMute();
void toggleAudioNoiseReduction();
void toggleToneInjection();
@ -102,6 +107,9 @@ public slots:
float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
signals:
bool muteToggled();
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
@ -233,6 +241,11 @@ private:
QByteArray* _scopeOutputLeft;
QByteArray* _scopeOutputRight;
AudioStreamStats _audioMixerAvatarStreamStats;
QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamStatsMap;
quint16 _outgoingAvatarAudioSequenceNumber;
SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
};

View file

@ -51,7 +51,10 @@ void DatagramProcessor::processDatagrams() {
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
Q_ARG(QByteArray, incomingPacket));
break;
case PacketTypeAudioStreamStats:
QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection,
Q_ARG(QByteArray, incomingPacket));
break;
case PacketTypeParticleAddResponse:
// this will keep creatorTokenIDs to IDs mapped correctly
Particle::handleAddParticleResponse(incomingPacket);

View file

@ -12,6 +12,7 @@
#include "Application.h"
#include "GLCanvas.h"
#include "devices/OculusManager.h"
#include <QMimeData>
#include <QUrl>
#include <QMainWindow>
@ -41,8 +42,17 @@ void GLCanvas::initializeGL() {
void GLCanvas::paintGL() {
if (!_throttleRendering && !Application::getInstance()->getWindow()->isMinimized()) {
//Need accurate frame timing for the oculus rift
if (OculusManager::isConnected()) {
OculusManager::beginFrameTiming();
}
Application::getInstance()->paintGL();
swapBuffers();
if (OculusManager::isConnected()) {
OculusManager::endFrameTiming();
}
}
}
@ -102,8 +112,17 @@ void GLCanvas::activeChanged(Qt::ApplicationState state) {
void GLCanvas::throttleRender() {
_frameTimer.start(_idleRenderInterval);
if (!Application::getInstance()->getWindow()->isMinimized()) {
//Need accurate frame timing for the oculus rift
if (OculusManager::isConnected()) {
OculusManager::beginFrameTiming();
}
Application::getInstance()->paintGL();
swapBuffers();
if (OculusManager::isConnected()) {
OculusManager::endFrameTiming();
}
}
}

View file

@ -332,6 +332,8 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true);
addActionToQMenuAndActionHash(renderOptionsMenu,
MenuOption::GlowMode,
0,
@ -400,7 +402,6 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options");
addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false);
addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true);
QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options");

View file

@ -295,7 +295,6 @@ private:
namespace MenuOption {
const QString AboutApp = "About Interface";
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
const QString AllowOculusCameraModeChange = "Allow Oculus Camera Mode Change (Nausea)";
const QString AlternateIK = "Alternate IK";
const QString AmbientOcclusion = "Ambient Occlusion";
const QString Animations = "Animations...";
@ -352,6 +351,7 @@ namespace MenuOption {
const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
const QString EchoLocalAudio = "Echo Local Audio";
const QString EchoServerAudio = "Echo Server Audio";
const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
const QString Enable3DTVMode = "Enable 3DTV Mode";
const QString EnableVRMode = "Enable VR Mode";
const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing";

View file

@ -35,6 +35,8 @@ MetavoxelSystem::MetavoxelSystem() :
}
void MetavoxelSystem::init() {
MetavoxelClientManager::init();
if (!_program.isLinked()) {
_program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert");
_program.link();
@ -43,62 +45,19 @@ void MetavoxelSystem::init() {
}
_buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
_buffer.create();
connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
}
SharedObjectPointer MetavoxelSystem::findFirstRaySpannerIntersection(
const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance) {
SharedObjectPointer closestSpanner;
float closestDistance = FLT_MAX;
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
float clientDistance;
SharedObjectPointer clientSpanner = client->getData().findFirstRaySpannerIntersection(
origin, direction, attribute, clientDistance);
if (clientSpanner && clientDistance < closestDistance) {
closestSpanner = clientSpanner;
closestDistance = clientDistance;
}
}
}
}
if (closestSpanner) {
distance = closestDistance;
}
return closestSpanner;
}
void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
client->applyEdit(edit, reliable);
}
}
}
MetavoxelLOD MetavoxelSystem::getLOD() const {
const float FIXED_LOD_THRESHOLD = 0.01f;
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
}
void MetavoxelSystem::simulate(float deltaTime) {
// simulate the clients
// update the clients
_points.clear();
_simulateVisitor.setDeltaTime(deltaTime);
_simulateVisitor.setOrder(-Application::getInstance()->getViewFrustum()->getDirection());
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
client->simulate(deltaTime);
client->guide(_simulateVisitor);
}
}
}
update();
_buffer.bind();
int bytes = _points.size() * sizeof(Point);
@ -153,7 +112,7 @@ void MetavoxelSystem::render() {
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
MetavoxelSystemClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
if (client) {
client->guide(_renderVisitor);
}
@ -161,11 +120,13 @@ void MetavoxelSystem::render() {
}
}
void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
node->setLinkedData(new MetavoxelClient(NodeList::getInstance()->nodeWithUUID(node->getUUID())));
}
MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) {
return new MetavoxelSystemClient(node, this);
}
void MetavoxelSystem::updateClient(MetavoxelClient* client) {
MetavoxelClientManager::updateClient(client);
client->guide(_simulateVisitor);
}
MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
@ -235,115 +196,22 @@ bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& cl
return true;
}
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
_node(node),
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
// insert the baseline send record
SendRecord sendRecord = { 0 };
_sendRecords.append(sendRecord);
// insert the baseline receive record
ReceiveRecord receiveRecord = { 0, _data };
_receiveRecords.append(receiveRecord);
MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system) :
MetavoxelClient(node, system) {
}
MetavoxelClient::~MetavoxelClient() {
// close the session
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(CloseSessionMessage());
_sequencer.endPacket();
}
static MetavoxelLOD getLOD() {
const float FIXED_LOD_THRESHOLD = 0.01f;
return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
}
void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
visitor.setLOD(getLOD());
_data.guide(visitor);
}
void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
if (reliable) {
_sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit));
} else {
// apply immediately to local tree
edit.apply(_data, _sequencer.getWeakSharedObjectHash());
// start sending it out
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
}
}
void MetavoxelClient::simulate(float deltaTime) {
Bitstream& out = _sequencer.startPacket();
ClientStateMessage state = { getLOD() };
out << QVariant::fromValue(state);
_sequencer.endPacket();
// record the send
SendRecord record = { _sequencer.getOutgoingPacketNumber(), state.lod };
_sendRecords.append(record);
}
int MetavoxelClient::parseData(const QByteArray& packet) {
int MetavoxelSystemClient::parseData(const QByteArray& packet) {
// process through sequencer
QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::METAVOXELS).updateValue(packet.size());
return packet.size();
}
void MetavoxelClient::sendData(const QByteArray& data) {
void MetavoxelSystemClient::sendDatagram(const QByteArray& data) {
NodeList::getInstance()->writeDatagram(data, _node);
Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::METAVOXELS).updateValue(data.size());
}
void MetavoxelClient::readPacket(Bitstream& in) {
QVariant message;
in >> message;
handleMessage(message, in);
// record the receipt
ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data, _sendRecords.first().lod };
_receiveRecords.append(record);
// reapply local edits
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
if (message.data.userType() == MetavoxelEditMessage::Type) {
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
}
}
}
void MetavoxelClient::clearSendRecordsBefore(int index) {
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
}
void MetavoxelClient::clearReceiveRecordsBefore(int index) {
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
}
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == MetavoxelDeltaMessage::Type) {
_data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod);
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
GLdouble coefficients[] = { x, y, z, w };
glClipPlane(plane, coefficients);

View file

@ -18,37 +18,31 @@
#include <glm/glm.hpp>
#include <NodeList.h>
#include <DatagramSequencer.h>
#include <MetavoxelData.h>
#include <MetavoxelMessages.h>
#include <MetavoxelClientManager.h>
#include "renderer/ProgramObject.h"
class Model;
/// Renders a metavoxel tree.
class MetavoxelSystem : public QObject {
class MetavoxelSystem : public MetavoxelClientManager {
Q_OBJECT
public:
MetavoxelSystem();
void init();
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
const AttributePointer& attribute, float& distance);
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
virtual void init();
virtual MetavoxelLOD getLOD() const;
void simulate(float deltaTime);
void render();
private slots:
void maybeAttachClient(const SharedNodePointer& node);
protected:
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
virtual void updateClient(MetavoxelClient* client);
private:
@ -89,59 +83,18 @@ private:
};
/// A client session associated with a single server.
class MetavoxelClient : public NodeData {
class MetavoxelSystemClient : public MetavoxelClient {
Q_OBJECT
public:
MetavoxelClient(const SharedNodePointer& node);
virtual ~MetavoxelClient();
MetavoxelData& getData() { return _data; }
void guide(MetavoxelVisitor& visitor);
void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
void simulate(float deltaTime);
MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system);
virtual int parseData(const QByteArray& packet);
private slots:
protected:
void sendData(const QByteArray& data);
void readPacket(Bitstream& in);
void clearSendRecordsBefore(int index);
void clearReceiveRecordsBefore(int index);
private:
void handleMessage(const QVariant& message, Bitstream& in);
class SendRecord {
public:
int packetNumber;
MetavoxelLOD lod;
};
class ReceiveRecord {
public:
int packetNumber;
MetavoxelData data;
MetavoxelLOD lod;
};
SharedNodePointer _node;
DatagramSequencer _sequencer;
MetavoxelData _data;
QList<SendRecord> _sendRecords;
QList<ReceiveRecord> _receiveRecords;
virtual void sendDatagram(const QByteArray& data);
};
/// Base class for spanner renderers; provides clipping.

View file

@ -856,7 +856,9 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
renderAttachments(renderMode);
// Render head so long as the camera isn't inside it
if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
const Camera *camera = Application::getInstance()->getCamera();
const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance();
if (shouldRenderHead(cameraPos, renderMode)) {
getHead()->render(1.0f, modelRenderMode);
if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
renderHair();
@ -920,9 +922,16 @@ void MyAvatar::updateOrientation(float deltaTime) {
glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll());
head->setAngularVelocity(angularVelocity);
head->setBaseYaw(yaw);
head->setBasePitch(pitch);
head->setBaseRoll(roll);
//Invert yaw and roll when in mirror mode
if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) {
head->setBaseYaw(-yaw);
head->setBasePitch(pitch);
head->setBaseRoll(-roll);
} else {
head->setBaseYaw(yaw);
head->setBasePitch(pitch);
head->setBaseRoll(roll);
}
}

View file

@ -39,7 +39,6 @@ void SkeletonModel::setJointStates(QVector<JointState> states) {
}
clearShapes();
clearRagdollConstraintsAndPoints();
if (_enableShapes) {
buildShapes();
}
@ -505,8 +504,7 @@ void SkeletonModel::renderRagdoll() {
// virtual
void SkeletonModel::initRagdollPoints() {
assert(_ragdollPoints.size() == 0);
assert(_ragdollConstraints.size() == 0);
clearRagdollConstraintsAndPoints();
// one point for each joint
int numJoints = _jointStates.size();

View file

@ -3,6 +3,7 @@
// interface/src/devices
//
// Created by Stephen Birarda on 5/9/13.
// Refactored by Ben Arnold on 6/30/2014
// Copyright 2012 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -11,67 +12,211 @@
#include "InterfaceConfig.h"
#include "OculusManager.h"
#include <QOpenGLFramebufferObject>
#include <glm/glm.hpp>
#include <UserActivityLogger.h>
#include "Application.h"
#include "OculusManager.h"
#ifdef HAVE_LIBOVR
using namespace OVR;
ProgramObject OculusManager::_program;
int OculusManager::_textureLocation;
int OculusManager::_lensCenterLocation;
int OculusManager::_screenCenterLocation;
int OculusManager::_scaleLocation;
int OculusManager::_scaleInLocation;
int OculusManager::_hmdWarpParamLocation;
int OculusManager::_eyeToSourceUVScaleLocation;
int OculusManager::_eyeToSourceUVOffsetLocation;
int OculusManager::_eyeRotationStartLocation;
int OculusManager::_eyeRotationEndLocation;
int OculusManager::_positionAttributeLocation;
int OculusManager::_colorAttributeLocation;
int OculusManager::_texCoord0AttributeLocation;
int OculusManager::_texCoord1AttributeLocation;
int OculusManager::_texCoord2AttributeLocation;
bool OculusManager::_isConnected = false;
#ifdef HAVE_LIBOVR
using namespace OVR;
using namespace OVR::Util::Render;
ovrHmd OculusManager::_ovrHmd;
ovrHmdDesc OculusManager::_ovrHmdDesc;
ovrFovPort OculusManager::_eyeFov[ovrEye_Count];
ovrEyeRenderDesc OculusManager::_eyeRenderDesc[ovrEye_Count];
ovrSizei OculusManager::_renderTargetSize;
ovrVector2f OculusManager::_UVScaleOffset[ovrEye_Count][2];
GLuint OculusManager::_vertices[ovrEye_Count] = { 0, 0 };
GLuint OculusManager::_indices[ovrEye_Count] = { 0, 0 };
GLsizei OculusManager::_meshSize[ovrEye_Count] = { 0, 0 };
ovrFrameTiming OculusManager::_hmdFrameTiming;
ovrRecti OculusManager::_eyeRenderViewport[ovrEye_Count];
unsigned int OculusManager::_frameIndex = 0;
bool OculusManager::_frameTimingActive = false;
bool OculusManager::_programInitialized = false;
Camera* OculusManager::_camera = NULL;
Ptr<DeviceManager> OculusManager::_deviceManager;
Ptr<HMDDevice> OculusManager::_hmdDevice;
Ptr<SensorDevice> OculusManager::_sensorDevice;
SensorFusion* OculusManager::_sensorFusion;
StereoConfig OculusManager::_stereoConfig;
#endif
void OculusManager::connect() {
#ifdef HAVE_LIBOVR
System::Init();
_deviceManager = *DeviceManager::Create();
_hmdDevice = *_deviceManager->EnumerateDevices<HMDDevice>().CreateDevice();
ovr_Initialize();
if (_hmdDevice) {
_ovrHmd = ovrHmd_Create(0);
if (_ovrHmd) {
if (!_isConnected) {
UserActivityLogger::getInstance().connectedDevice("hmd", "oculus");
}
_isConnected = true;
_sensorDevice = *_hmdDevice->GetSensor();
_sensorFusion = new SensorFusion;
_sensorFusion->AttachToSensor(_sensorDevice);
_sensorFusion->SetPredictionEnabled(true);
ovrHmd_GetDesc(_ovrHmd, &_ovrHmdDesc);
_eyeFov[0] = _ovrHmdDesc.DefaultEyeFov[0];
_eyeFov[1] = _ovrHmdDesc.DefaultEyeFov[1];
//Get texture size
ovrSizei recommendedTex0Size = ovrHmd_GetFovTextureSize(_ovrHmd, ovrEye_Left,
_eyeFov[0], 1.0f);
ovrSizei recommendedTex1Size = ovrHmd_GetFovTextureSize(_ovrHmd, ovrEye_Right,
_eyeFov[1], 1.0f);
_renderTargetSize.w = recommendedTex0Size.w + recommendedTex1Size.w;
_renderTargetSize.h = recommendedTex0Size.h;
if (_renderTargetSize.h < recommendedTex1Size.h) {
_renderTargetSize.h = recommendedTex1Size.h;
}
_eyeRenderDesc[0] = ovrHmd_GetRenderDesc(_ovrHmd, ovrEye_Left, _eyeFov[0]);
_eyeRenderDesc[1] = ovrHmd_GetRenderDesc(_ovrHmd, ovrEye_Right, _eyeFov[1]);
ovrHmd_SetEnabledCaps(_ovrHmd, ovrHmdCap_LowPersistence | ovrHmdCap_LatencyTest);
ovrHmd_StartSensor(_ovrHmd, ovrSensorCap_Orientation | ovrSensorCap_YawCorrection |
ovrSensorCap_Position,
ovrSensorCap_Orientation);
if (!_camera) {
_camera = new Camera;
}
if (!_programInitialized) {
// Shader program
_programInitialized = true;
_program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/oculus.vert");
_program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/oculus.frag");
_program.link();
// Uniforms
_textureLocation = _program.uniformLocation("texture");
_eyeToSourceUVScaleLocation = _program.uniformLocation("EyeToSourceUVScale");
_eyeToSourceUVOffsetLocation = _program.uniformLocation("EyeToSourceUVOffset");
_eyeRotationStartLocation = _program.uniformLocation("EyeRotationStart");
_eyeRotationEndLocation = _program.uniformLocation("EyeRotationEnd");
// Attributes
_positionAttributeLocation = _program.attributeLocation("position");
_colorAttributeLocation = _program.attributeLocation("color");
_texCoord0AttributeLocation = _program.attributeLocation("texCoord0");
_texCoord1AttributeLocation = _program.attributeLocation("texCoord1");
_texCoord2AttributeLocation = _program.attributeLocation("texCoord2");
}
//Generate the distortion VBOs
generateDistortionMesh();
HMDInfo info;
_hmdDevice->GetDeviceInfo(&info);
_stereoConfig.SetHMDInfo(info);
_program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/oculus.frag");
_program.link();
_textureLocation = _program.uniformLocation("texture");
_lensCenterLocation = _program.uniformLocation("lensCenter");
_screenCenterLocation = _program.uniformLocation("screenCenter");
_scaleLocation = _program.uniformLocation("scale");
_scaleInLocation = _program.uniformLocation("scaleIn");
_hmdWarpParamLocation = _program.uniformLocation("hmdWarpParam");
} else {
_deviceManager.Clear();
System::Destroy();
_isConnected = false;
ovrHmd_Destroy(_ovrHmd);
ovr_Shutdown();
}
#endif
}
//Disconnects and deallocates the OR
void OculusManager::disconnect() {
#ifdef HAVE_LIBOVR
if (_isConnected) {
_isConnected = false;
ovrHmd_Destroy(_ovrHmd);
ovr_Shutdown();
//Free the distortion mesh data
for (int i = 0; i < ovrEye_Count; i++) {
if (_vertices[i] != 0) {
glDeleteBuffers(1, &(_vertices[i]));
_vertices[i] = 0;
}
if (_indices[i] != 0) {
glDeleteBuffers(1, &(_indices[i]));
_indices[i] = 0;
}
}
}
#endif
}
#ifdef HAVE_LIBOVR
void OculusManager::generateDistortionMesh() {
//Check if we already have the distortion mesh
if (_vertices[0] != 0) {
printf("WARNING: Tried to generate Oculus distortion mesh twice without freeing the VBOs.");
return;
}
//Viewport for the render target for each eye
_eyeRenderViewport[0].Pos = Vector2i(0, 0);
_eyeRenderViewport[0].Size = Sizei(_renderTargetSize.w / 2, _renderTargetSize.h);
_eyeRenderViewport[1].Pos = Vector2i((_renderTargetSize.w + 1) / 2, 0);
_eyeRenderViewport[1].Size = _eyeRenderViewport[0].Size;
for (int eyeNum = 0; eyeNum < ovrEye_Count; eyeNum++) {
// Allocate and generate distortion mesh vertices
ovrDistortionMesh meshData;
ovrHmd_CreateDistortionMesh(_ovrHmd, _eyeRenderDesc[eyeNum].Eye, _eyeRenderDesc[eyeNum].Fov, _ovrHmdDesc.DistortionCaps, &meshData);
ovrHmd_GetRenderScaleAndOffset(_eyeRenderDesc[eyeNum].Fov, _renderTargetSize, _eyeRenderViewport[eyeNum],
_UVScaleOffset[eyeNum]);
// Parse the vertex data and create a render ready vertex buffer
DistortionVertex* pVBVerts = (DistortionVertex*)OVR_ALLOC(sizeof(DistortionVertex) * meshData.VertexCount);
_meshSize[eyeNum] = meshData.IndexCount;
// Convert the oculus vertex data to the DistortionVertex format.
DistortionVertex* v = pVBVerts;
ovrDistortionVertex* ov = meshData.pVertexData;
for (unsigned int vertNum = 0; vertNum < meshData.VertexCount; vertNum++) {
v->pos.x = ov->Pos.x;
v->pos.y = ov->Pos.y;
v->texR.x = ov->TexR.x;
v->texR.y = ov->TexR.y;
v->texG.x = ov->TexG.x;
v->texG.y = ov->TexG.y;
v->texB.x = ov->TexB.x;
v->texB.y = ov->TexB.y;
v->color.r = v->color.g = v->color.b = (GLubyte)(ov->VignetteFactor * 255.99f);
v->color.a = (GLubyte)(ov->TimeWarpFactor * 255.99f);
v++;
ov++;
}
//vertices
glGenBuffers(1, &(_vertices[eyeNum]));
glBindBuffer(GL_ARRAY_BUFFER, _vertices[eyeNum]);
glBufferData(GL_ARRAY_BUFFER, sizeof(DistortionVertex) * meshData.VertexCount, pVBVerts, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//indices
glGenBuffers(1, &(_indices[eyeNum]));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indices[eyeNum]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * meshData.IndexCount, meshData.pIndexData, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//Now that we have the VBOs we can get rid of the mesh data
OVR_FREE(pVBVerts);
ovrHmd_DestroyDistortionMesh(&meshData);
}
}
#endif
bool OculusManager::isConnected() {
#ifdef HAVE_LIBOVR
return _isConnected && Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode);
@ -80,137 +225,237 @@ bool OculusManager::isConnected() {
#endif
}
//Begins the frame timing for oculus prediction purposes
void OculusManager::beginFrameTiming() {
#ifdef HAVE_LIBOVR
if (_frameTimingActive) {
printf("WARNING: Called OculusManager::beginFrameTiming() twice in a row, need to call OculusManager::endFrameTiming().");
}
_hmdFrameTiming = ovrHmd_BeginFrameTiming(_ovrHmd, _frameIndex);
_frameTimingActive = true;
#endif
}
//Ends frame timing
void OculusManager::endFrameTiming() {
#ifdef HAVE_LIBOVR
ovrHmd_EndFrameTiming(_ovrHmd);
_frameIndex++;
_frameTimingActive = false;
#endif
}
//Sets the camera FoV and aspect ratio
void OculusManager::configureCamera(Camera& camera, int screenWidth, int screenHeight) {
#ifdef HAVE_LIBOVR
_stereoConfig.SetFullViewport(Viewport(0, 0, screenWidth, screenHeight));
camera.setAspectRatio(_stereoConfig.GetAspect());
camera.setFieldOfView(_stereoConfig.GetYFOVDegrees());
camera.setAspectRatio(_renderTargetSize.w / _renderTargetSize.h);
camera.setFieldOfView(atan(_eyeFov[0].UpTan) * DEGREES_PER_RADIAN * 2.0f);
#endif
}
void OculusManager::display(Camera& whichCamera) {
//Displays everything for the oculus, frame timing must be active
void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera) {
#ifdef HAVE_LIBOVR
//beginFrameTiming must be called before display
if (!_frameTimingActive) {
printf("WARNING: Called OculusManager::display() without calling OculusManager::beginFrameTiming() first.");
return;
}
ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
// We only need to render the overlays to a texture once, then we just render the texture as a quad
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
applicationOverlay.renderOverlay(true);
const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays);
Application::getInstance()->getGlowEffect()->prepare();
// render the left eye view to the left side of the screen
const StereoEyeParams& leftEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Left);
//Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it
if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) {
Application::getInstance()->getGlowEffect()->prepare();
} else {
Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->bind();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
ovrPosef eyeRenderPose[ovrEye_Count];
_camera->setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay
_camera->setDistance(0.0f);
_camera->setUpShift(0.0f);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glTranslatef(_stereoConfig.GetProjectionCenterOffset(), 0, 0);
gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(),
whichCamera.getNearClip(), whichCamera.getFarClip());
glViewport(leftEyeParams.VP.x, leftEyeParams.VP.y, leftEyeParams.VP.w, leftEyeParams.VP.h);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glTranslatef(_stereoConfig.GetIPD() * 0.5f, 0, 0);
Application::getInstance()->displaySide(whichCamera);
glm::quat orientation;
//Render each eye into an fbo
for (int eyeIndex = 0; eyeIndex < ovrEye_Count; eyeIndex++) {
if (displayOverlays) {
applicationOverlay.displayOverlayTextureOculus(whichCamera);
}
// and the right eye to the right side
const StereoEyeParams& rightEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Right);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glTranslatef(-_stereoConfig.GetProjectionCenterOffset(), 0, 0);
gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(),
whichCamera.getNearClip(), whichCamera.getFarClip());
glViewport(rightEyeParams.VP.x, rightEyeParams.VP.y, rightEyeParams.VP.w, rightEyeParams.VP.h);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(_stereoConfig.GetIPD() * -0.5f, 0, 0);
Application::getInstance()->displaySide(whichCamera);
ovrEyeType eye = _ovrHmdDesc.EyeRenderOrder[eyeIndex];
if (displayOverlays) {
applicationOverlay.displayOverlayTextureOculus(whichCamera);
//Set the camera rotation for this eye
eyeRenderPose[eye] = ovrHmd_GetEyePose(_ovrHmd, eye);
orientation.x = eyeRenderPose[eye].Orientation.x;
orientation.y = eyeRenderPose[eye].Orientation.y;
orientation.z = eyeRenderPose[eye].Orientation.z;
orientation.w = eyeRenderPose[eye].Orientation.w;
_camera->setTargetRotation(bodyOrientation * orientation);
_camera->setTargetPosition(position);
_camera->update(1.0f / Application::getInstance()->getFps());
Matrix4f proj = ovrMatrix4f_Projection(_eyeRenderDesc[eye].Fov, whichCamera.getNearClip(), whichCamera.getFarClip(), true);
proj.Transpose();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glLoadMatrixf((GLfloat *)proj.M);
glViewport(_eyeRenderViewport[eye].Pos.x, _eyeRenderViewport[eye].Pos.y,
_eyeRenderViewport[eye].Size.w, _eyeRenderViewport[eye].Size.h);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(_eyeRenderDesc[eye].ViewAdjust.x, _eyeRenderDesc[eye].ViewAdjust.y, _eyeRenderDesc[eye].ViewAdjust.z);
Application::getInstance()->displaySide(*_camera);
if (displayOverlays) {
applicationOverlay.displayOverlayTextureOculus(*_camera);
}
}
//Wait till time-warp to reduce latency
ovr_WaitTillTime(_hmdFrameTiming.TimewarpPointSeconds);
glPopMatrix();
// restore our normal viewport
const Viewport& fullViewport = _stereoConfig.GetFullViewport();
glViewport(fullViewport.x, fullViewport.y, fullViewport.w, fullViewport.h);
QOpenGLFramebufferObject* fbo = Application::getInstance()->getGlowEffect()->render(true);
glBindTexture(GL_TEXTURE_2D, fbo->texture());
//Full texture viewport for glow effect
glViewport(0, 0, _renderTargetSize.w, _renderTargetSize.h);
//Bind the output texture from the glow shader. If glow effect is disabled, we just grab the texture
if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) {
QOpenGLFramebufferObject* fbo = Application::getInstance()->getGlowEffect()->render(true);
glBindTexture(GL_TEXTURE_2D, fbo->texture());
} else {
Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->release();
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->texture());
}
// restore our normal viewport
glViewport(0, 0, Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height());
glMatrixMode(GL_PROJECTION);
glPopMatrix();
//Renders the distorted mesh onto the screen
renderDistortionMesh(eyeRenderPose);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
}
#ifdef HAVE_LIBOVR
void OculusManager::renderDistortionMesh(ovrPosef eyeRenderPose[ovrEye_Count]) {
glLoadIdentity();
gluOrtho2D(fullViewport.x, fullViewport.x + fullViewport.w, fullViewport.y, fullViewport.y + fullViewport.h);
gluOrtho2D(0, Application::getInstance()->getGLWidget()->width(), 0, Application::getInstance()->getGLWidget()->height());
glDisable(GL_DEPTH_TEST);
// for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp
float scaleFactor = 1.0 / _stereoConfig.GetDistortionScale();
float aspectRatio = _stereoConfig.GetAspect();
glDisable(GL_BLEND);
_program.bind();
_program.setUniformValue(_textureLocation, 0);
const DistortionConfig& distortionConfig = _stereoConfig.GetDistortionConfig();
_program.setUniformValue(_lensCenterLocation, (0.5 + distortionConfig.XCenterOffset * 0.5) * 0.5, 0.5);
_program.setUniformValue(_screenCenterLocation, 0.25, 0.5);
_program.setUniformValue(_scaleLocation, 0.25 * scaleFactor, 0.5 * scaleFactor * aspectRatio);
_program.setUniformValue(_scaleInLocation, 4, 2 / aspectRatio);
_program.setUniformValue(_hmdWarpParamLocation, distortionConfig.K[0], distortionConfig.K[1],
distortionConfig.K[2], distortionConfig.K[3]);
glColor3f(1, 0, 1);
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(0, 0);
glTexCoord2f(0.5, 0);
glVertex2f(leftEyeParams.VP.w, 0);
glTexCoord2f(0.5, 1);
glVertex2f(leftEyeParams.VP.w, leftEyeParams.VP.h);
glTexCoord2f(0, 1);
glVertex2f(0, leftEyeParams.VP.h);
glEnd();
_program.setUniformValue(_lensCenterLocation, 0.5 + (0.5 - distortionConfig.XCenterOffset * 0.5) * 0.5, 0.5);
_program.setUniformValue(_screenCenterLocation, 0.75, 0.5);
glBegin(GL_QUADS);
glTexCoord2f(0.5, 0);
glVertex2f(leftEyeParams.VP.w, 0);
glTexCoord2f(1, 0);
glVertex2f(fullViewport.w, 0);
glTexCoord2f(1, 1);
glVertex2f(fullViewport.w, leftEyeParams.VP.h);
glTexCoord2f(0.5, 1);
glVertex2f(leftEyeParams.VP.w, leftEyeParams.VP.h);
glEnd();
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, 0);
_program.enableAttributeArray(_positionAttributeLocation);
_program.enableAttributeArray(_colorAttributeLocation);
_program.enableAttributeArray(_texCoord0AttributeLocation);
_program.enableAttributeArray(_texCoord1AttributeLocation);
_program.enableAttributeArray(_texCoord2AttributeLocation);
//Render the distortion meshes for each eye
for (int eyeNum = 0; eyeNum < ovrEye_Count; eyeNum++) {
GLfloat uvScale[2] = { _UVScaleOffset[eyeNum][0].x, _UVScaleOffset[eyeNum][0].y };
_program.setUniformValueArray(_eyeToSourceUVScaleLocation, uvScale, 1, 2);
GLfloat uvOffset[2] = { _UVScaleOffset[eyeNum][1].x, _UVScaleOffset[eyeNum][1].y };
_program.setUniformValueArray(_eyeToSourceUVOffsetLocation, uvOffset, 1, 2);
ovrMatrix4f timeWarpMatrices[2];
Matrix4f transposeMatrices[2];
//Grabs the timewarp matrices to be used in the shader
ovrHmd_GetEyeTimewarpMatrices(_ovrHmd, (ovrEyeType)eyeNum, eyeRenderPose[eyeNum], timeWarpMatrices);
transposeMatrices[0] = Matrix4f(timeWarpMatrices[0]);
transposeMatrices[1] = Matrix4f(timeWarpMatrices[1]);
//Have to transpose the matrices before using them
transposeMatrices[0].Transpose();
transposeMatrices[1].Transpose();
glUniformMatrix4fv(_eyeRotationStartLocation, 1, GL_FALSE, (GLfloat *)transposeMatrices[0].M);
glUniformMatrix4fv(_eyeRotationEndLocation, 1, GL_FALSE, (GLfloat *)transposeMatrices[1].M);
glBindBuffer(GL_ARRAY_BUFFER, _vertices[eyeNum]);
//Set vertex attribute pointers
glVertexAttribPointer(_positionAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)0);
glVertexAttribPointer(_texCoord0AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)8);
glVertexAttribPointer(_texCoord1AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)16);
glVertexAttribPointer(_texCoord2AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)24);
glVertexAttribPointer(_colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(DistortionVertex), (void *)32);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indices[eyeNum]);
glDrawElements(GL_TRIANGLES, _meshSize[eyeNum], GL_UNSIGNED_SHORT, 0);
}
_program.disableAttributeArray(_positionAttributeLocation);
_program.disableAttributeArray(_colorAttributeLocation);
_program.disableAttributeArray(_texCoord0AttributeLocation);
_program.disableAttributeArray(_texCoord1AttributeLocation);
_program.disableAttributeArray(_texCoord2AttributeLocation);
glEnable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
_program.release();
glPopMatrix();
#endif
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#endif
//Tries to reconnect to the sensors
void OculusManager::reset() {
#ifdef HAVE_LIBOVR
_sensorFusion->Reset();
disconnect();
connect();
#endif
}
//Gets the current predicted angles from the oculus sensors
void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) {
#ifdef HAVE_LIBOVR
_sensorFusion->GetPredictedOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
ovrSensorState ss = ovrHmd_GetSensorState(_ovrHmd, _hmdFrameTiming.ScanoutMidpointSeconds);
if (ss.StatusFlags & (ovrStatus_OrientationTracked | ovrStatus_PositionTracked)) {
ovrPosef pose = ss.Predicted.Pose;
Quatf orientation = Quatf(pose.Orientation);
orientation.GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
}
#endif
}
//Used to set the size of the glow framebuffers
QSize OculusManager::getRenderTargetSize() {
#ifdef HAVE_LIBOVR
QSize rv;
rv.setWidth(_renderTargetSize.w);
rv.setHeight(_renderTargetSize.h);
return rv;
#else
return QSize(100, 100);
#endif
}

View file

@ -3,6 +3,7 @@
// interface/src/devices
//
// Created by Stephen Birarda on 5/9/13.
// Refactored by Ben Arnold on 6/30/2014
// Copyright 2012 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@ -12,10 +13,9 @@
#ifndef hifi_OculusManager_h
#define hifi_OculusManager_h
#include <iostream>
#ifdef HAVE_LIBOVR
#include <OVR.h>
#include "../src/Util/Util_Render_Stereo.h"
#endif
#include "renderer/ProgramObject.h"
@ -28,38 +28,69 @@ class Camera;
class OculusManager {
public:
static void connect();
static void disconnect();
static bool isConnected();
static void beginFrameTiming();
static void endFrameTiming();
static void configureCamera(Camera& camera, int screenWidth, int screenHeight);
static void display(Camera& whichCamera);
static void display(const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera);
static void reset();
/// param \yaw[out] yaw in radians
/// param \pitch[out] pitch in radians
/// param \roll[out] roll in radians
static void getEulerAngles(float& yaw, float& pitch, float& roll);
static void updateYawOffset();
static QSize getRenderTargetSize();
private:
#ifdef HAVE_LIBOVR
static void generateDistortionMesh();
static void renderDistortionMesh(ovrPosef eyeRenderPose[ovrEye_Count]);
struct DistortionVertex {
glm::vec2 pos;
glm::vec2 texR;
glm::vec2 texG;
glm::vec2 texB;
struct {
GLubyte r;
GLubyte g;
GLubyte b;
GLubyte a;
} color;
};
static ProgramObject _program;
//Uniforms
static int _textureLocation;
static int _lensCenterLocation;
static int _screenCenterLocation;
static int _scaleLocation;
static int _scaleInLocation;
static int _hmdWarpParamLocation;
static int _eyeToSourceUVScaleLocation;
static int _eyeToSourceUVOffsetLocation;
static int _eyeRotationStartLocation;
static int _eyeRotationEndLocation;
//Attributes
static int _positionAttributeLocation;
static int _colorAttributeLocation;
static int _texCoord0AttributeLocation;
static int _texCoord1AttributeLocation;
static int _texCoord2AttributeLocation;
static bool _isConnected;
#ifdef HAVE_LIBOVR
static OVR::Ptr<OVR::DeviceManager> _deviceManager;
static OVR::Ptr<OVR::HMDDevice> _hmdDevice;
static OVR::Ptr<OVR::SensorDevice> _sensorDevice;
static OVR::SensorFusion* _sensorFusion;
static OVR::Util::Render::StereoConfig _stereoConfig;
static ovrHmd _ovrHmd;
static ovrHmdDesc _ovrHmdDesc;
static ovrFovPort _eyeFov[ovrEye_Count];
static ovrEyeRenderDesc _eyeRenderDesc[ovrEye_Count];
static ovrSizei _renderTargetSize;
static ovrVector2f _UVScaleOffset[ovrEye_Count][2];
static GLuint _vertices[ovrEye_Count];
static GLuint _indices[ovrEye_Count];
static GLsizei _meshSize[ovrEye_Count];
static ovrFrameTiming _hmdFrameTiming;
static ovrRecti _eyeRenderViewport[ovrEye_Count];
static unsigned int _frameIndex;
static bool _frameTimingActive;
static bool _programInitialized;
static Camera* _camera;
#endif
};

View file

@ -13,6 +13,7 @@
#include "Application.h"
#include "SixenseManager.h"
#include "UserActivityLogger.h"
#ifdef HAVE_SIXENSE
const int CALIBRATION_STATE_IDLE = 0;
@ -39,6 +40,7 @@ SixenseManager::SixenseManager() {
sixenseInit();
#endif
_hydrasConnected = false;
_triggerPressed[0] = false;
_bumperPressed[0] = false;
_oldX[0] = -1;
@ -70,7 +72,11 @@ void SixenseManager::setFilter(bool filter) {
void SixenseManager::update(float deltaTime) {
#ifdef HAVE_SIXENSE
if (sixenseGetNumActiveControllers() == 0) {
_hydrasConnected = false;
return;
} else if (!_hydrasConnected) {
_hydrasConnected = true;
UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
}
MyAvatar* avatar = Application::getInstance()->getAvatar();
Hand* hand = avatar->getHand();

View file

@ -71,6 +71,7 @@ private:
float _lastDistance;
#endif
bool _hydrasConnected;
quint64 _lastMovement;
glm::vec3 _amountMoved;

View file

@ -135,7 +135,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual modelItems of the element
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
QList<ModelItem>& modelItems = modelTreeElement->getModels();

View file

@ -180,7 +180,7 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
glBindTexture(GL_TEXTURE_2D, oldDiffusedFBO->texture());
_diffuseProgram->bind();
QSize size = Application::getInstance()->getGLWidget()->size();
QSize size = primaryFBO->size();
_diffuseProgram->setUniformValue(_diffusionScaleLocation, 1.0f / size.width(), 1.0f / size.height());
renderFullscreenQuad();

View file

@ -28,10 +28,12 @@ TextureCache::TextureCache() :
_permutationNormalTextureID(0),
_whiteTextureID(0),
_blueTextureID(0),
_primaryDepthTextureID(0),
_primaryFramebufferObject(NULL),
_secondaryFramebufferObject(NULL),
_tertiaryFramebufferObject(NULL),
_shadowFramebufferObject(NULL)
_shadowFramebufferObject(NULL),
_frameBufferSize(100, 100)
{
}
@ -46,9 +48,41 @@ TextureCache::~TextureCache() {
glDeleteTextures(1, &_primaryDepthTextureID);
}
delete _primaryFramebufferObject;
delete _secondaryFramebufferObject;
delete _tertiaryFramebufferObject;
if (_primaryFramebufferObject) {
delete _primaryFramebufferObject;
}
if (_secondaryFramebufferObject) {
delete _secondaryFramebufferObject;
}
if (_tertiaryFramebufferObject) {
delete _tertiaryFramebufferObject;
}
}
void TextureCache::setFrameBufferSize(QSize frameBufferSize) {
//If the size changed, we need to delete our FBOs
if (_frameBufferSize != frameBufferSize) {
_frameBufferSize = frameBufferSize;
if (_primaryFramebufferObject) {
delete _primaryFramebufferObject;
_primaryFramebufferObject = NULL;
glDeleteTextures(1, &_primaryDepthTextureID);
_primaryDepthTextureID = 0;
}
if (_secondaryFramebufferObject) {
delete _secondaryFramebufferObject;
_secondaryFramebufferObject = NULL;
}
if (_tertiaryFramebufferObject) {
delete _tertiaryFramebufferObject;
_tertiaryFramebufferObject = NULL;
}
}
}
GLuint TextureCache::getPermutationNormalTextureID() {
@ -131,13 +165,14 @@ QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool no
}
QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() {
if (!_primaryFramebufferObject) {
_primaryFramebufferObject = createFramebufferObject();
glGenTextures(1, &_primaryDepthTextureID);
glBindTexture(GL_TEXTURE_2D, _primaryDepthTextureID);
QSize size = Application::getInstance()->getGLWidget()->size();
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width(), size.height(),
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _frameBufferSize.width(), _frameBufferSize.height(),
0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@ -230,7 +265,7 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
}
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size());
QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(_frameBufferSize);
Application::getInstance()->getGLWidget()->installEventFilter(this);
glBindTexture(GL_TEXTURE_2D, fbo->texture());

View file

@ -32,6 +32,9 @@ public:
TextureCache();
virtual ~TextureCache();
/// Sets the desired texture resolution for the framebuffer objects.
void setFrameBufferSize(QSize frameBufferSize);
/// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture
/// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
/// the second, a set of random unit vectors to be used as noise gradients.
@ -94,6 +97,8 @@ private:
QOpenGLFramebufferObject* _shadowFramebufferObject;
GLuint _shadowDepthTextureID;
QSize _frameBufferSize;
};
/// A simple object wrapper for an OpenGL texture.

View file

@ -41,8 +41,7 @@ ApplicationOverlay::ApplicationOverlay() :
_textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
_alpha(1.0f),
_active(true),
_crosshairTexture(0)
{
_crosshairTexture(0) {
memset(_reticleActive, 0, sizeof(_reticleActive));
memset(_magActive, 0, sizeof(_reticleActive));

View file

@ -29,6 +29,7 @@
#include <QVBoxLayout>
#include <AttributeRegistry.h>
#include <MetavoxelMessages.h>
#include "Application.h"
#include "MetavoxelEditor.h"

View file

@ -365,13 +365,14 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder());
QString incomingLateString = locale.toString((uint)stats.getIncomingLate());
QString incomingReallyLateString = locale.toString((uint)stats.getIncomingReallyLate());
QString incomingEarlyString = locale.toString((uint)stats.getIncomingEarly());
QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost());
QString incomingRecovered = locale.toString((uint)stats.getIncomingRecovered());
QString incomingDuplicateString = locale.toString((uint)stats.getIncomingPossibleDuplicate());
const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats();
QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder());
QString incomingLateString = locale.toString((uint)seqStats.getNumLate());
QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable());
QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly());
QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost());
QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered());
QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate());
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
@ -385,7 +386,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
serverDetails << "<br/>" << " Out of Order: " << qPrintable(incomingOutOfOrderString) <<
"/ Early: " << qPrintable(incomingEarlyString) <<
"/ Late: " << qPrintable(incomingLateString) <<
"/ Really Late: " << qPrintable(incomingReallyLateString) <<
"/ Unreasonable: " << qPrintable(incomingUnreasonableString) <<
"/ Duplicate: " << qPrintable(incomingDuplicateString);
serverDetails << "<br/>" <<

View file

@ -12,8 +12,9 @@
#include "Application.h"
#include "Menu.h"
#include "PreferencesDialog.h"
#include "ModelsBrowser.h"
#include "PreferencesDialog.h"
#include "UserActivityLogger.h"
const int SCROLL_PANEL_BOTTOM_MARGIN = 30;
const int OK_BUTTON_RIGHT_MARGIN = 30;
@ -176,6 +177,7 @@ void PreferencesDialog::savePreferences() {
QString displayNameStr(ui.displayNameEdit->text());
if (displayNameStr != _displayNameString) {
myAvatar->setDisplayName(displayNameStr);
UserActivityLogger::getInstance().changedDisplayName(displayNameStr);
shouldDispatchIdentityPacket = true;
}
@ -183,6 +185,7 @@ void PreferencesDialog::savePreferences() {
if (faceModelURL.toString() != _faceURLString) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
myAvatar->setFaceModelURL(faceModelURL);
UserActivityLogger::getInstance().changedModel("head", faceModelURL.toString());
shouldDispatchIdentityPacket = true;
}
@ -190,6 +193,7 @@ void PreferencesDialog::savePreferences() {
if (skeletonModelURL.toString() != _skeletonURLString) {
// change the skeletonModelURL in the profile, it will also update this user's Body
myAvatar->setSkeletonModelURL(skeletonModelURL);
UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURL.toString());
shouldDispatchIdentityPacket = true;
}

View file

@ -28,8 +28,12 @@
ScriptEditorWidget::ScriptEditorWidget() :
_scriptEditorWidgetUI(new Ui::ScriptEditorWidget),
_scriptEngine(NULL)
_scriptEngine(NULL),
_isRestarting(false),
_isReloading(false)
{
setAttribute(Qt::WA_DeleteOnClose);
_scriptEditorWidgetUI->setupUi(this);
connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::modificationChanged, this,
@ -51,15 +55,19 @@ ScriptEditorWidget::~ScriptEditorWidget() {
}
void ScriptEditorWidget::onScriptModified() {
if(_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isRunning()) {
if(_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isModified() && isRunning() && !_isReloading) {
_isRestarting = true;
setRunning(false);
setRunning(true);
// Script is restarted once current script instance finishes.
}
}
void ScriptEditorWidget::onScriptEnding() {
// signals will automatically be disonnected when the _scriptEngine is deleted later
void ScriptEditorWidget::onScriptFinished(const QString& scriptPath) {
_scriptEngine = NULL;
if (_isRestarting) {
_isRestarting = false;
setRunning(true);
}
}
bool ScriptEditorWidget::isModified() {
@ -71,27 +79,28 @@ bool ScriptEditorWidget::isRunning() {
}
bool ScriptEditorWidget::setRunning(bool run) {
if (run && !save()) {
if (run && isModified() && !save()) {
return false;
}
// Clean-up old connections.
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
disconnect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
if (run) {
_scriptEngine = Application::getInstance()->loadScript(_currentScript, true);
const QString& scriptURLString = QUrl(_currentScript).toString();
_scriptEngine = Application::getInstance()->loadScript(scriptURLString, true);
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
// Make new connections.
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
connect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
} else {
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
Application::getInstance()->stopScript(_currentScript);
_scriptEngine = NULL;
}
@ -108,13 +117,14 @@ bool ScriptEditorWidget::saveFile(const QString &scriptPath) {
QTextStream out(&file);
out << _scriptEditorWidgetUI->scriptEdit->toPlainText();
file.close();
setScriptFile(scriptPath);
return true;
}
void ScriptEditorWidget::loadFile(const QString& scriptPath) {
QUrl url(scriptPath);
QUrl url(scriptPath);
// if the scheme length is one or lower, maybe they typed in a file, let's try
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
@ -127,13 +137,15 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
}
QTextStream in(&file);
_scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
file.close();
setScriptFile(scriptPath);
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
disconnect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
} else {
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
@ -148,12 +160,14 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
}
}
_scriptEngine = Application::getInstance()->getScriptEngine(_currentScript);
const QString& scriptURLString = QUrl(_currentScript).toString();
_scriptEngine = Application::getInstance()->getScriptEngine(scriptURLString);
if (_scriptEngine != NULL) {
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
connect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
}
@ -175,6 +189,7 @@ bool ScriptEditorWidget::saveAs() {
void ScriptEditorWidget::setScriptFile(const QString& scriptPath) {
_currentScript = scriptPath;
_currentScriptModified = QFileInfo(_currentScript).lastModified();
_scriptEditorWidgetUI->scriptEdit->document()->setModified(false);
setWindowModified(false);
@ -198,3 +213,29 @@ void ScriptEditorWidget::onScriptError(const QString& message) {
void ScriptEditorWidget::onScriptPrint(const QString& message) {
_scriptEditorWidgetUI->debugText->appendPlainText("> " + message);
}
void ScriptEditorWidget::onWindowActivated() {
if (!_isReloading) {
_isReloading = true;
if (QFileInfo(_currentScript).lastModified() > _currentScriptModified) {
if (static_cast<ScriptEditorWindow*>(this->parent()->parent()->parent())->autoReloadScripts()
|| QMessageBox::warning(this, _currentScript,
tr("This file has been modified outside of the Interface editor.") + "\n\n"
+ (isModified()
? tr("Do you want to reload it and lose the changes you've made in the Interface editor?")
: tr("Do you want to reload it?")),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
loadFile(_currentScript);
if (_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isRunning()) {
_isRestarting = true;
setRunning(false);
// Script is restarted once current script instance finishes.
}
}
}
_isReloading = false;
}
}

View file

@ -13,6 +13,7 @@
#define hifi_ScriptEditorWidget_h
#include <QDockWidget>
#include "ScriptEngine.h"
namespace Ui {
@ -42,16 +43,22 @@ signals:
void scriptnameChanged();
void scriptModified();
public slots:
void onWindowActivated();
private slots:
void onScriptError(const QString& message);
void onScriptPrint(const QString& message);
void onScriptModified();
void onScriptEnding();
void onScriptFinished(const QString& scriptName);
private:
Ui::ScriptEditorWidget* _scriptEditorWidgetUI;
ScriptEngine* _scriptEngine;
QString _currentScript;
QDateTime _currentScriptModified;
bool _isRestarting;
bool _isReloading;
};
#endif // hifi_ScriptEditorWidget_h

View file

@ -36,6 +36,8 @@ ScriptEditorWindow::ScriptEditorWindow() :
_loadMenu(new QMenu),
_saveMenu(new QMenu)
{
setAttribute(Qt::WA_DeleteOnClose);
_ScriptEditorWindowUI->setupUi(this);
this->setWindowFlags(Qt::Tool);
show();
@ -140,6 +142,7 @@ ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) {
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptnameChanged, this, &ScriptEditorWindow::updateScriptNameOrStatus);
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptModified, this, &ScriptEditorWindow::updateScriptNameOrStatus);
connect(newScriptEditorWidget, &ScriptEditorWidget::runningStateChanged, this, &ScriptEditorWindow::updateButtons);
connect(this, &ScriptEditorWindow::windowActivated, newScriptEditorWidget, &ScriptEditorWidget::onWindowActivated);
_ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title);
_ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget);
newScriptEditorWidget->setUpdatesEnabled(true);
@ -216,3 +219,15 @@ void ScriptEditorWindow::terminateCurrentTab() {
this->raise();
}
}
bool ScriptEditorWindow::autoReloadScripts() {
return _ScriptEditorWindowUI->autoReloadCheckBox->isChecked();
}
bool ScriptEditorWindow::event(QEvent* event) {
if (event->type() == QEvent::WindowActivate) {
emit windowActivated();
}
return QWidget::event(event);
}

View file

@ -26,9 +26,14 @@ public:
~ScriptEditorWindow();
void terminateCurrentTab();
bool autoReloadScripts();
signals:
void windowActivated();
protected:
void closeEvent(QCloseEvent* event);
virtual bool event(QEvent* event);
private:
Ui::ScriptEditorWindow* _ScriptEditorWindowUI;

View file

@ -286,11 +286,16 @@ void Stats::display(
pingVoxel = totalPingVoxel/voxelServerCount;
}
lines = _expanded ? 4 : 3;
Audio* audio = Application::getInstance()->getAudio();
const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap();
lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3;
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;
Audio* audio = Application::getInstance()->getAudio();
char audioJitter[30];
sprintf(audioJitter,
@ -299,10 +304,9 @@ void Stats::display(
(float) audio->getNetworkSampleRate() * 1000.f);
drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
char audioPing[30];
sprintf(audioPing, "Audio ping: %d", pingAudio);
char avatarPing[30];
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
@ -322,12 +326,54 @@ void Stats::display(
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
char audioMixerStatsLabelString[] = "AudioMixer stats:";
char streamStatsFormatLabelString[] = "early/late/lost, jframes";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color);
char downstreamLabelString[] = " Downstream:";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats();
char downstreamAudioStatsString[30];
sprintf(downstreamAudioStatsString, " mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(),
downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(),
audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
char upstreamLabelString[] = " Upstream:";
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color);
char upstreamAudioStatsString[30];
sprintf(upstreamAudioStatsString, " mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly,
audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost,
audioMixerAvatarStreamStats._jitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) {
sprintf(upstreamAudioStatsString, " inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly,
injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames);
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
}
}
verticalOffset = 0;
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
}
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();

View file

@ -0,0 +1,136 @@
//
// BillboardOverlay.cpp
//
//
// Created by Clement on 7/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "../../Application.h"
#include "BillboardOverlay.h"
BillboardOverlay::BillboardOverlay()
: _manager(NULL),
_scale(1.0f),
_isFacingAvatar(true) {
}
void BillboardOverlay::render() {
if (_billboard.isEmpty()) {
return;
}
if (!_billboardTexture) {
QImage image = QImage::fromData(_billboard);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
_size = image.size();
_billboardTexture.reset(new Texture());
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
}
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
if (_isFacingAvatar) {
// rotate about vertical to face the camera
glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
} else {
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
}
glScalef(_scale, _scale, _scale);
float maxSize = glm::max(_size.width(), _size.height());
float x = _size.width() / (2.0f * maxSize);
float y = -_size.height() / (2.0f * maxSize);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS); {
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-x, -y);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(x, -y);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(x, y);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-x, y);
} glEnd();
} glPopMatrix();
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
glBindTexture(GL_TEXTURE_2D, 0);
}
void BillboardOverlay::setProperties(const QScriptValue &properties) {
Base3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
_url = urlValue.toVariant().toString();
setBillboardURL(_url);
}
QScriptValue scaleValue = properties.property("scale");
if (scaleValue.isValid()) {
_scale = scaleValue.toVariant().toFloat();
}
QScriptValue rotationValue = properties.property("rotation");
if (rotationValue.isValid()) {
QScriptValue x = rotationValue.property("x");
QScriptValue y = rotationValue.property("y");
QScriptValue z = rotationValue.property("z");
QScriptValue w = rotationValue.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
_rotation.x = x.toVariant().toFloat();
_rotation.y = y.toVariant().toFloat();
_rotation.z = z.toVariant().toFloat();
_rotation.w = w.toVariant().toFloat();
}
}
QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar");
if (isFacingAvatarValue.isValid()) {
_isFacingAvatar = isFacingAvatarValue.toVariant().toBool();
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void BillboardOverlay::setBillboardURL(const QUrl url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
}
void BillboardOverlay::replyFinished(QNetworkReply* reply) {
// replace our byte array with the downloaded data
_billboard = reply->readAll();
_manager->deleteLater();
_manager = NULL;
}

View file

@ -0,0 +1,46 @@
//
// BillboardOverlay.h
//
//
// Created by Clement on 7/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BillboardOverlay_h
#define hifi_BillboardOverlay_h
#include <QScopedPointer>
#include <QUrl>
#include "Base3DOverlay.h"
#include "../../renderer/TextureCache.h"
class BillboardOverlay : public Base3DOverlay {
Q_OBJECT
public:
BillboardOverlay();
virtual void render();
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply);
private:
void setBillboardURL(const QUrl url);
QNetworkAccessManager* _manager;
QUrl _url;
QByteArray _billboard;
QSize _size;
QScopedPointer<Texture> _billboardTexture;
glm::quat _rotation;
float _scale;
bool _isFacingAvatar;
};
#endif // hifi_BillboardOverlay_h

View file

@ -19,7 +19,7 @@
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_manager(0),
_manager(NULL),
_textureID(0),
_renderImage(false),
_textureBound(false),
@ -37,6 +37,7 @@ ImageOverlay::~ImageOverlay() {
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
@ -49,6 +50,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) {
_textureImage.loadFromData(rawData);
_renderImage = true;
_manager->deleteLater();
_manager = NULL;
}
void ImageOverlay::render() {

View file

@ -0,0 +1,115 @@
//
// ModelOverlay.cpp
//
//
// Created by Clement on 6/30/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "../../Menu.h"
#include "ModelOverlay.h"
ModelOverlay::ModelOverlay()
: _model(),
_scale(1.0f),
_updateModel(false) {
_model.init();
}
void ModelOverlay::update(float deltatime) {
if (_updateModel) {
_updateModel = false;
_model.setScaleToFit(true, _scale);
_model.setSnapModelToCenter(true);
_model.setRotation(_rotation);
_model.setTranslation(_position);
_model.setURL(_url);
_model.simulate(deltatime, true);
} else {
_model.simulate(deltatime);
}
}
void ModelOverlay::render() {
if (_model.isActive()) {
if (_model.isRenderable()) {
_model.render(_alpha);
}
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (displayModelBounds) {
glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model.getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, _rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = _model.getScale();
glPushMatrix(); {
glTranslatef(_position.x, _position.y, _position.z);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix(); {
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
} glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(_rotation);
glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
} glPopMatrix();
}
}
}
void ModelOverlay::setProperties(const QScriptValue &properties) {
Base3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
_url = urlValue.toVariant().toString();
_updateModel = true;
}
QScriptValue scaleValue = properties.property("scale");
if (scaleValue.isValid()) {
_scale = scaleValue.toVariant().toFloat();
_updateModel = true;
}
QScriptValue rotationValue = properties.property("rotation");
if (rotationValue.isValid()) {
QScriptValue x = rotationValue.property("x");
QScriptValue y = rotationValue.property("y");
QScriptValue z = rotationValue.property("z");
QScriptValue w = rotationValue.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
_rotation.x = x.toVariant().toFloat();
_rotation.y = y.toVariant().toFloat();
_rotation.z = z.toVariant().toFloat();
_rotation.w = w.toVariant().toFloat();
}
_updateModel = true;
}
if (properties.property("position").isValid()) {
_updateModel = true;
}
}

View file

@ -0,0 +1,38 @@
//
// ModelOverlay.h
//
//
// Created by Clement on 6/30/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelOverlay_h
#define hifi_ModelOverlay_h
#include "Base3DOverlay.h"
#include "../../renderer/Model.h"
class ModelOverlay : public Base3DOverlay {
Q_OBJECT
public:
ModelOverlay();
virtual void update(float deltatime);
virtual void render();
virtual void setProperties(const QScriptValue& properties);
private:
Model _model;
QUrl _url;
glm::quat _rotation;
float _scale;
bool _updateModel;
};
#endif // hifi_ModelOverlay_h

View file

@ -10,13 +10,15 @@
#include <Application.h>
#include "BillboardOverlay.h"
#include "Cube3DOverlay.h"
#include "ImageOverlay.h"
#include "Line3DOverlay.h"
#include "LocalVoxelsOverlay.h"
#include "ModelOverlay.h"
#include "Overlays.h"
#include "Sphere3DOverlay.h"
#include "TextOverlay.h"
#include "LocalVoxelsOverlay.h"
Overlays::Overlays() : _nextOverlayID(1) {
}
@ -82,13 +84,13 @@ void Overlays::render3D() {
return;
}
bool myAvatarComputed = false;
MyAvatar* avatar;
MyAvatar* avatar = NULL;
glm::quat myAvatarRotation;
glm::vec3 myAvatarPosition;
float angle;
glm::vec3 axis;
float myAvatarScale;
glm::vec3 myAvatarPosition(0.0f);
float angle = 0.0f;
glm::vec3 axis(0.0f, 1.0f, 0.0f);
float myAvatarScale = 1.0f;
foreach(Overlay* thisOverlay, _overlays3D) {
glPushMatrix();
switch (thisOverlay->getAnchor()) {
@ -156,6 +158,18 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "model") {
thisOverlay = new ModelOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
} else if (type == "billboard") {
thisOverlay = new BillboardOverlay();
thisOverlay->init(_parent);
thisOverlay->setProperties(properties);
created = true;
is3D = true;
}
if (created) {

View file

@ -137,7 +137,7 @@ background: transparent;</string>
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>16</pointsize>
<pointsize>-1</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
@ -326,9 +326,6 @@ padding-top: 3px;</string>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">margin: 0;</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
@ -336,7 +333,7 @@ padding-top: 3px;</string>
<number>0</number>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
@ -352,7 +349,7 @@ padding-top: 3px;</string>
<rect>
<x>0</x>
<y>0</y>
<width>269</width>
<width>284</width>
<height>16</height>
</rect>
</property>
@ -460,7 +457,6 @@ font: bold 16px;</string>
<string notr="true">background: transparent;
font-size: 14px;</string>
</property>
<zorder>runningScriptsList</zorder>
</widget>
</item>
</layout>
@ -627,10 +623,10 @@ QListView::item {
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>

View file

@ -33,7 +33,7 @@
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0">
<property name="spacing">
<number>3</number>
</property>
@ -185,6 +185,16 @@
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="autoReloadCheckBox">
<property name="styleSheet">
<string notr="true">font: 13px &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</string>
</property>
<property name="text">
<string>Automatically reload externally changed files</string>
</property>
</widget>
</item>
</layout>
</item>
<item>

View file

@ -61,6 +61,11 @@ void AudioInjector::injectAudio() {
QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
// pack some placeholder sequence number for now
int numPreSequenceNumberBytes = injectAudioPacket.size();
packetStream << (quint16)0;
// pack stream identifier (a generated UUID)
packetStream << QUuid::createUuid();
// pack the flag for loopback
@ -91,6 +96,7 @@ void AudioInjector::injectAudio() {
bool shouldLoop = _options.getLoop();
// loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
quint16 outgoingInjectedAudioSequenceNumber = 0;
while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
@ -98,6 +104,9 @@ void AudioInjector::injectAudio() {
// resize the QByteArray to the right size
injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
// pack the sequence number
memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
// copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
@ -107,6 +116,7 @@ void AudioInjector::injectAudio() {
// send off this audio packet
nodeList->writeDatagram(injectAudioPacket, audioMixer);
outgoingInjectedAudioSequenceNumber++;
currentSendPosition += bytesToCopy;

View file

@ -16,13 +16,14 @@
#include <QtCore/QDebug>
#include "PacketHeaders.h"
#include "AudioRingBuffer.h"
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
NodeData(),
_resetCount(0),
_overflowCount(0),
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
_isFull(false),
_numFrameSamples(numFrameSamples),
_isStarved(true),
_hasStarted(false),
@ -52,7 +53,7 @@ void AudioRingBuffer::reset() {
_isStarved = true;
}
void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) {
delete[] _buffer;
_sampleCapacity = numFrameSamples * RING_BUFFER_LENGTH_FRAMES;
_buffer = new int16_t[_sampleCapacity];
@ -64,18 +65,19 @@ void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
}
int AudioRingBuffer::parseData(const QByteArray& packet) {
int numBytesPacketHeader = numBytesForPacketHeader(packet);
return writeData(packet.data() + numBytesPacketHeader, packet.size() - numBytesPacketHeader);
// skip packet header and sequence number
int numBytesBeforeAudioData = numBytesForPacketHeader(packet) + sizeof(quint16);
return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData);
}
qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {
int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) {
return readData((char*) destination, maxSamples * sizeof(int16_t));
}
qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
int AudioRingBuffer::readData(char *data, int maxSize) {
// only copy up to the number of samples we have available
int numReadSamples = std::min((unsigned) (maxSize / sizeof(int16_t)), samplesAvailable());
int numReadSamples = std::min((int) (maxSize / sizeof(int16_t)), samplesAvailable());
// If we're in random access mode, then we consider our number of available read samples slightly
// differently. Namely, if anything has been written, we say we have as many samples as they ask for
@ -109,27 +111,29 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
// push the position of _nextOutput by the number of samples read
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples);
if (numReadSamples > 0) {
_isFull = false;
}
return numReadSamples * sizeof(int16_t);
}
qint64 AudioRingBuffer::writeSamples(const int16_t* source, qint64 maxSamples) {
int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) {
return writeData((const char*) source, maxSamples * sizeof(int16_t));
}
qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
int AudioRingBuffer::writeData(const char* data, int maxSize) {
// make sure we have enough bytes left for this to be the right amount of audio
// otherwise we should not copy that data, and leave the buffer pointers where they are
quint64 samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
if (_hasStarted && samplesToCopy > _sampleCapacity - samplesAvailable()) {
// this read will cross the next output, so call us starved and reset the buffer
qDebug() << "Filled the ring buffer. Resetting.";
_endOfLastWrite = _buffer;
_nextOutput = _buffer;
_isStarved = true;
_resetCount++;
int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity);
int samplesRoomFor = _sampleCapacity - samplesAvailable();
if (samplesToCopy > samplesRoomFor) {
// there's not enough room for this write. erase old data to make room for this new data
int samplesToDelete = samplesToCopy - samplesRoomFor;
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
_overflowCount++;
qDebug() << "Overflowed ring buffer! Overwriting old data";
}
if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
@ -141,7 +145,10 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
}
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
if (samplesToCopy > 0 && _endOfLastWrite == _nextOutput) {
_isFull = true;
}
return samplesToCopy * sizeof(int16_t);
}
@ -154,39 +161,54 @@ const int16_t& AudioRingBuffer::operator[] (const int index) const {
}
void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
}
unsigned int AudioRingBuffer::samplesAvailable() const {
if (!_endOfLastWrite) {
return 0;
} else {
int sampleDifference = _endOfLastWrite - _nextOutput;
if (sampleDifference < 0) {
sampleDifference += _sampleCapacity;
}
return sampleDifference;
if (numSamples > 0) {
_nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
_isFull = false;
}
}
void AudioRingBuffer::addSilentFrame(int numSilentSamples) {
int AudioRingBuffer::samplesAvailable() const {
if (!_endOfLastWrite) {
return 0;
}
if (_isFull) {
return _sampleCapacity;
}
int sampleDifference = _endOfLastWrite - _nextOutput;
if (sampleDifference < 0) {
sampleDifference += _sampleCapacity;
}
return sampleDifference;
}
int AudioRingBuffer::addSilentFrame(int numSilentSamples) {
int samplesRoomFor = _sampleCapacity - samplesAvailable();
if (numSilentSamples > samplesRoomFor) {
// there's not enough room for this write. write as many silent samples as we have room for
numSilentSamples = samplesRoomFor;
qDebug() << "Dropping some silent samples to prevent ring buffer overflow";
}
// memset zeroes into the buffer, accomodate a wrap around the end
// push the _endOfLastWrite to the correct spot
if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) {
memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t));
_endOfLastWrite += numSilentSamples;
} else {
int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite;
memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t));
memset(_buffer, 0, (numSilentSamples - numSamplesToEnd) * sizeof(int16_t));
_endOfLastWrite = _buffer + (numSilentSamples - numSamplesToEnd);
}
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numSilentSamples);
if (numSilentSamples > 0 && _nextOutput == _endOfLastWrite) {
_isFull = true;
}
return numSilentSamples * sizeof(int16_t);
}
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const {
bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const {
if (!_isStarved) {
return true;
} else {

View file

@ -43,7 +43,7 @@ public:
~AudioRingBuffer();
void reset();
void resizeForFrameSize(qint64 numFrameSamples);
void resizeForFrameSize(int numFrameSamples);
int getSampleCapacity() const { return _sampleCapacity; }
@ -53,28 +53,28 @@ public:
const int16_t* getNextOutput() const { return _nextOutput; }
const int16_t* getBuffer() const { return _buffer; }
qint64 readSamples(int16_t* destination, qint64 maxSamples);
qint64 writeSamples(const int16_t* source, qint64 maxSamples);
int readSamples(int16_t* destination, int maxSamples);
int writeSamples(const int16_t* source, int maxSamples);
qint64 readData(char* data, qint64 maxSize);
qint64 writeData(const char* data, qint64 maxSize);
int readData(char* data, int maxSize);
int writeData(const char* data, int maxSize);
int16_t& operator[](const int index);
const int16_t& operator[] (const int index) const;
void shiftReadPosition(unsigned int numSamples);
unsigned int samplesAvailable() const;
int samplesAvailable() const;
bool isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const;
bool isNotStarvedOrHasMinimumSamples(int numRequiredSamples) const;
bool isStarved() const { return _isStarved; }
void setIsStarved(bool isStarved) { _isStarved = isStarved; }
int getResetCount() const { return _resetCount; } /// how many times has the ring buffer written past the end and reset
int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data
bool hasStarted() const { return _hasStarted; }
void addSilentFrame(int numSilentSamples);
int addSilentFrame(int numSilentSamples);
protected:
// disallow copying of AudioRingBuffer objects
AudioRingBuffer(const AudioRingBuffer&);
@ -82,9 +82,10 @@ protected:
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
int _resetCount; /// how many times has the ring buffer written past the end and done a reset
int _overflowCount; /// how many times has the ring buffer has overwritten old data
int _sampleCapacity;
bool _isFull;
int _numFrameSamples;
int16_t* _nextOutput;
int16_t* _endOfLastWrite;

View file

@ -0,0 +1,46 @@
//
// AudioStreamStats.h
// libraries/audio/src
//
// Created by Yixin Wang on 6/25/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioStreamStats_h
#define hifi_AudioStreamStats_h
#include "PositionalAudioRingBuffer.h"
class AudioStreamStats {
public:
AudioStreamStats()
: _streamType(PositionalAudioRingBuffer::Microphone),
_streamIdentifier(),
_jitterBufferFrames(0),
_packetsReceived(0),
_packetsUnreasonable(0),
_packetsEarly(0),
_packetsLate(0),
_packetsLost(0),
_packetsRecovered(0),
_packetsDuplicate(0)
{}
PositionalAudioRingBuffer::Type _streamType;
QUuid _streamIdentifier;
quint16 _jitterBufferFrames;
quint32 _packetsReceived;
quint32 _packetsUnreasonable;
quint32 _packetsEarly;
quint32 _packetsLate;
quint32 _packetsLost;
quint32 _packetsRecovered;
quint32 _packetsDuplicate;
};
#endif // hifi_AudioStreamStats_h

View file

@ -38,6 +38,9 @@ int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
// push past the sequence number
packetStream.skipRawData(sizeof(quint16));
// push past the stream identifier
packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);

View file

@ -107,6 +107,9 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
// skip the packet header (includes the source UUID)
int readBytes = numBytesForPacketHeader(packet);
// skip the sequence number
readBytes += sizeof(quint16);
// hop over the channel flag that has already been read in AudioMixerClientData
readBytes += sizeof(quint8);
@ -203,13 +206,13 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
// if the buffer was starved, allow it to accrue at least the desired number of
// jitter buffer frames before we start taking frames from it for mixing
if (_shouldOutputStarveDebug) {
_shouldOutputStarveDebug = false;
}
return false;
} else if (samplesAvailable() < (unsigned int)samplesPerFrame) {
} else if (samplesAvailable() < samplesPerFrame) {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
_isStarved = true;
@ -253,7 +256,7 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
_desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
} else {
const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
_desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
if (_desiredJitterBufferFrames < 1) {
_desiredJitterBufferFrames = 1;

View file

@ -1414,8 +1414,10 @@ Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
*this << object->getOriginID();
QPointer<SharedObject> reference = _sharedObjectReferences.value(object->getOriginID());
if (reference) {
*this << true;
writeRawDelta((const QObject*)object.data(), (const QObject*)reference.data());
} else {
*this << false;
*this << (QObject*)object.data();
}
return *this;
@ -1430,19 +1432,27 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
}
int originID;
*this >> originID;
bool delta;
*this >> delta;
QPointer<SharedObject> reference = _sharedObjectReferences.value(originID);
QPointer<SharedObject>& pointer = _weakSharedObjectHash[id];
if (pointer) {
ObjectStreamerPointer objectStreamer;
_objectStreamerStreamer >> objectStreamer;
if (reference) {
if (delta) {
if (!reference) {
qWarning() << "Delta without reference" << id << originID;
}
objectStreamer->readRawDelta(*this, reference.data(), pointer.data());
} else {
objectStreamer->read(*this, pointer.data());
}
} else {
QObject* rawObject;
if (reference) {
if (delta) {
if (!reference) {
qWarning() << "Delta without reference" << id << originID;
}
readRawDelta(rawObject, (const QObject*)reference.data());
} else {
*this >> rawObject;

View file

@ -140,6 +140,12 @@ void DatagramSequencer::endPacket() {
_outgoingPacketStream.device()->seek(0);
}
void DatagramSequencer::cancelPacket() {
_outputStream.reset();
_outputStream.getAndResetWriteMappings();
_outgoingPacketStream.device()->seek(0);
}
/// Simple RAII-style object to keep a device open when in scope.
class QIODeviceOpener {
public:

View file

@ -111,6 +111,9 @@ public:
/// Sends the packet currently being written.
void endPacket();
/// Cancels the packet currently being written.
void cancelPacket();
/// Processes a datagram received from the other party, emitting readyToRead when the entire packet
/// has been successfully assembled.
Q_INVOKABLE void receivedDatagram(const QByteArray& datagram);

View file

@ -0,0 +1,109 @@
//
// Endpoint.cpp
// libraries/metavoxels/src
//
// Created by Andrzej Kapolka on 6/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <PacketHeaders.h>
#include "Endpoint.h"
Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord, PacketRecord* baselineReceiveRecord) :
_node(node),
_sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
// insert the baseline send and receive records
_sendRecords.append(baselineSendRecord);
_receiveRecords.append(baselineReceiveRecord);
}
Endpoint::~Endpoint() {
foreach (PacketRecord* record, _sendRecords) {
delete record;
}
foreach (PacketRecord* record, _receiveRecords) {
delete record;
}
}
void Endpoint::update() {
Bitstream& out = _sequencer.startPacket();
writeUpdateMessage(out);
_sequencer.endPacket();
// record the send
_sendRecords.append(maybeCreateSendRecord());
}
int Endpoint::parseData(const QByteArray& packet) {
// process through sequencer
_sequencer.receivedDatagram(packet);
return packet.size();
}
void Endpoint::sendDatagram(const QByteArray& data) {
NodeList::getInstance()->writeDatagram(data, _node);
}
void Endpoint::readMessage(Bitstream& in) {
QVariant message;
in >> message;
handleMessage(message, in);
// record the receipt
_receiveRecords.append(maybeCreateReceiveRecord());
}
void Endpoint::clearSendRecordsBefore(int index) {
QList<PacketRecord*>::iterator end = _sendRecords.begin() + index + 1;
for (QList<PacketRecord*>::const_iterator it = _sendRecords.begin(); it != end; it++) {
delete *it;
}
_sendRecords.erase(_sendRecords.begin(), end);
}
void Endpoint::clearReceiveRecordsBefore(int index) {
QList<PacketRecord*>::iterator end = _receiveRecords.begin() + index + 1;
for (QList<PacketRecord*>::const_iterator it = _receiveRecords.begin(); it != end; it++) {
delete *it;
}
_receiveRecords.erase(_receiveRecords.begin(), end);
}
void Endpoint::writeUpdateMessage(Bitstream& out) {
out << QVariant();
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
PacketRecord* Endpoint::maybeCreateSendRecord() const {
return NULL;
}
PacketRecord* Endpoint::maybeCreateReceiveRecord() const {
return NULL;
}
PacketRecord::PacketRecord(const MetavoxelLOD& lod, const MetavoxelData& data) :
_lod(lod),
_data(data) {
}
PacketRecord::~PacketRecord() {
}

View file

@ -0,0 +1,78 @@
//
// Endpoint.h
// libraries/metavoxels/src
//
// Created by Andrzej Kapolka on 6/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Endpoint_h
#define hifi_Endpoint_h
#include <NodeList.h>
#include "DatagramSequencer.h"
#include "MetavoxelData.h"
class PacketRecord;
/// Base class for communication endpoints: clients and server sessions.
class Endpoint : public NodeData {
Q_OBJECT
public:
Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL,
PacketRecord* baselineReceiveRecord = NULL);
virtual ~Endpoint();
virtual void update();
virtual int parseData(const QByteArray& packet);
protected slots:
virtual void sendDatagram(const QByteArray& data);
virtual void readMessage(Bitstream& in);
void clearSendRecordsBefore(int index);
void clearReceiveRecordsBefore(int index);
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
virtual PacketRecord* maybeCreateReceiveRecord() const;
PacketRecord* getLastAcknowledgedSendRecord() const { return _sendRecords.first(); }
PacketRecord* getLastAcknowledgedReceiveRecord() const { return _receiveRecords.first(); }
SharedNodePointer _node;
DatagramSequencer _sequencer;
QList<PacketRecord*> _sendRecords;
QList<PacketRecord*> _receiveRecords;
};
/// Base class for packet records.
class PacketRecord {
public:
PacketRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData());
virtual ~PacketRecord();
const MetavoxelLOD& getLOD() const { return _lod; }
const MetavoxelData& getData() const { return _data; }
private:
MetavoxelLOD _lod;
MetavoxelData _data;
};
#endif // hifi_Endpoint_h

View file

@ -0,0 +1,142 @@
//
// MetavoxelClientManager.cpp
// libraries/metavoxels/src
//
// Created by Andrzej Kapolka on 6/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MetavoxelClientManager.h"
#include "MetavoxelMessages.h"
void MetavoxelClientManager::init() {
connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
}
void MetavoxelClientManager::update() {
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
updateClient(client);
}
}
}
}
SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(const glm::vec3& origin,
const glm::vec3& direction, const AttributePointer& attribute, float& distance) {
SharedObjectPointer closestSpanner;
float closestDistance = FLT_MAX;
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
float clientDistance;
SharedObjectPointer clientSpanner = client->getData().findFirstRaySpannerIntersection(
origin, direction, attribute, clientDistance);
if (clientSpanner && clientDistance < closestDistance) {
closestSpanner = clientSpanner;
closestDistance = clientDistance;
}
}
}
}
if (closestSpanner) {
distance = closestDistance;
}
return closestSpanner;
}
void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
if (client) {
client->applyEdit(edit, reliable);
}
}
}
}
MetavoxelLOD MetavoxelClientManager::getLOD() const {
return MetavoxelLOD();
}
void MetavoxelClientManager::maybeAttachClient(const SharedNodePointer& node) {
if (node->getType() == NodeType::MetavoxelServer) {
QMutexLocker locker(&node->getMutex());
node->setLinkedData(createClient(node));
}
}
MetavoxelClient* MetavoxelClientManager::createClient(const SharedNodePointer& node) {
return new MetavoxelClient(node, this);
}
void MetavoxelClientManager::updateClient(MetavoxelClient* client) {
client->update();
}
MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) :
Endpoint(node, new PacketRecord(), new PacketRecord()),
_manager(manager) {
}
void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
visitor.setLOD(_manager->getLOD());
_data.guide(visitor);
}
void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
if (reliable) {
_sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit));
} else {
// apply immediately to local tree
edit.apply(_data, _sequencer.getWeakSharedObjectHash());
// start sending it out
_sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
}
}
void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
ClientStateMessage state = { _manager->getLOD() };
out << QVariant::fromValue(state);
}
void MetavoxelClient::readMessage(Bitstream& in) {
Endpoint::readMessage(in);
// reapply local edits
foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
if (message.data.userType() == MetavoxelEditMessage::Type) {
message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
}
}
}
void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
if (message.userType() == MetavoxelDeltaMessage::Type) {
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
} else {
Endpoint::handleMessage(message, in);
}
}
PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
return new PacketRecord(_manager->getLOD());
}
PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
return new PacketRecord(getLastAcknowledgedSendRecord()->getLOD(), _data);
}

View file

@ -0,0 +1,75 @@
//
// MetavoxelClientManager.h
// libraries/metavoxels/src
//
// Created by Andrzej Kapolka on 6/26/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MetavoxelClientManager_h
#define hifi_MetavoxelClientManager_h
#include "Endpoint.h"
class MetavoxelClient;
class MetavoxelEditMessage;
/// Manages the set of connected metavoxel clients.
class MetavoxelClientManager : public QObject {
Q_OBJECT
public:
virtual void init();
void update();
SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
const AttributePointer& attribute, float& distance);
Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
virtual MetavoxelLOD getLOD() const;
private slots:
void maybeAttachClient(const SharedNodePointer& node);
protected:
virtual MetavoxelClient* createClient(const SharedNodePointer& node);
virtual void updateClient(MetavoxelClient* client);
};
/// Base class for metavoxel clients.
class MetavoxelClient : public Endpoint {
Q_OBJECT
public:
MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager);
MetavoxelData& getData() { return _data; }
void guide(MetavoxelVisitor& visitor);
void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
protected:
virtual void writeUpdateMessage(Bitstream& out);
virtual void readMessage(Bitstream& in);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
virtual PacketRecord* maybeCreateReceiveRecord() const;
private:
MetavoxelClientManager* _manager;
MetavoxelData _data;
};
#endif // hifi_MetavoxelClientManager_h

View file

@ -11,6 +11,7 @@
#include "NodeList.h"
#include "PacketHeaders.h"
#include "UserActivityLogger.h"
#include "DomainHandler.h"
@ -83,6 +84,7 @@ void DomainHandler::setHostname(const QString& hostname) {
qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
UserActivityLogger::getInstance().changedDomain(_hostname);
emit hostnameChanged(_hostname);
}
}

View file

@ -50,6 +50,8 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeMicrophoneAudioNoEcho:
case PacketTypeMicrophoneAudioWithEcho:
case PacketTypeSilentAudioFrame:
return 2;
case PacketTypeMixedAudio:
return 1;
case PacketTypeAvatarData:
return 3;

View file

@ -40,7 +40,7 @@ enum PacketType {
PacketTypeCreateAssignment,
PacketTypeDomainOAuthRequest,
PacketTypeMuteEnvironment,
PacketTypeDataServerSend, // reusable
PacketTypeAudioStreamStats,
PacketTypeDataServerConfirm,
PacketTypeVoxelQuery,
PacketTypeVoxelData,

View file

@ -0,0 +1,184 @@
//
// SequenceNumberStats.cpp
// libraries/networking/src
//
// Created by Yixin Wang on 6/25/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SequenceNumberStats.h"
#include <limits>
SequenceNumberStats::SequenceNumberStats()
: _lastReceived(std::numeric_limits<quint16>::max()),
_missingSet(),
_numReceived(0),
_numUnreasonable(0),
_numEarly(0),
_numLate(0),
_numLost(0),
_numRecovered(0),
_numDuplicate(0),
_lastSenderUUID()
{
}
void SequenceNumberStats::reset() {
_missingSet.clear();
_numReceived = 0;
_numUnreasonable = 0;
_numEarly = 0;
_numLate = 0;
_numLost = 0;
_numRecovered = 0;
_numDuplicate = 0;
}
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
static const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderUUID, const bool wantExtraDebugging) {
// if the sender node has changed, reset all stats
if (senderUUID != _lastSenderUUID) {
qDebug() << "sequence number stats was reset due to new sender node";
qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID;
reset();
_lastSenderUUID = senderUUID;
}
// determine our expected sequence number... handle rollover appropriately
quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
_numReceived++;
if (incoming == expected) { // on time
_lastReceived = incoming;
} else { // out of order
if (wantExtraDebugging) {
qDebug() << "out of order... got:" << incoming << "expected:" << expected;
}
int incomingInt = (int)incoming;
int expectedInt = (int)expected;
// check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
int absGap = std::abs(incomingInt - expectedInt);
if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
// rollover likely occurred between incoming and expected.
// correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
if (incomingInt > expectedInt) {
incomingInt -= UINT16_RANGE;
} else {
expectedInt -= UINT16_RANGE;
}
} else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
// ignore packet if gap is unreasonable
qDebug() << "ignoring unreasonable sequence number:" << incoming
<< "previous:" << _lastReceived;
_numUnreasonable++;
return;
}
// now that rollover has been corrected for (if it occurred), incoming and expected can be
// compared to each other directly, though one of them might be negative
if (incomingInt > expectedInt) { // early
if (wantExtraDebugging) {
qDebug() << "this packet is earlier than expected...";
qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt);
}
_numEarly++;
_numLost += (incomingInt - expectedInt);
// add all sequence numbers that were skipped to the missing sequence numbers list
for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
_missingSet.insert((quint16)(missingInt < 0 ? missingInt + UINT16_RANGE : missingInt));
}
// prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
// will be removed.
if (_missingSet.size() > MAX_REASONABLE_SEQUENCE_GAP) {
pruneMissingSet(wantExtraDebugging);
}
_lastReceived = incoming;
} else { // late
if (wantExtraDebugging) {
qDebug() << "this packet is later than expected...";
}
_numLate++;
// remove this from missing sequence number if it's in there
if (_missingSet.remove(incoming)) {
if (wantExtraDebugging) {
qDebug() << "found it in _missingSet";
}
_numLost--;
_numRecovered++;
} else {
if (wantExtraDebugging) {
qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
}
_numDuplicate++;
}
// do not update _incomingLastSequence; it shouldn't become smaller
}
}
}
void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
if (wantExtraDebugging) {
qDebug() << "pruning _missingSet! size:" << _missingSet.size();
}
// some older sequence numbers may be from before a rollover point; this must be handled.
// some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
// before the most recent rollover.
int cutoff = (int)_lastReceived - MAX_REASONABLE_SEQUENCE_GAP;
if (cutoff >= 0) {
quint16 nonRolloverCutoff = (quint16)cutoff;
QSet<quint16>::iterator i = _missingSet.begin();
while (i != _missingSet.end()) {
quint16 missing = *i;
if (wantExtraDebugging) {
qDebug() << "checking item:" << missing << "is it in need of pruning?";
qDebug() << "old age cutoff:" << nonRolloverCutoff;
}
if (missing > _lastReceived || missing < nonRolloverCutoff) {
i = _missingSet.erase(i);
if (wantExtraDebugging) {
qDebug() << "pruning really old missing sequence:" << missing;
}
} else {
i++;
}
}
} else {
quint16 rolloverCutoff = (quint16)(cutoff + UINT16_RANGE);
QSet<quint16>::iterator i = _missingSet.begin();
while (i != _missingSet.end()) {
quint16 missing = *i;
if (wantExtraDebugging) {
qDebug() << "checking item:" << missing << "is it in need of pruning?";
qDebug() << "old age cutoff:" << rolloverCutoff;
}
if (missing > _lastReceived && missing < rolloverCutoff) {
i = _missingSet.erase(i);
if (wantExtraDebugging) {
qDebug() << "pruning really old missing sequence:" << missing;
}
} else {
i++;
}
}
}
}

View file

@ -0,0 +1,53 @@
//
// SequenceNumberStats.h
// libraries/networking/src
//
// Created by Yixin Wang on 6/25/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SequenceNumberStats_h
#define hifi_SequenceNumberStats_h
#include "SharedUtil.h"
#include <quuid.h>
class SequenceNumberStats {
public:
SequenceNumberStats();
void reset();
void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
quint32 getNumReceived() const { return _numReceived; }
quint32 getNumUnreasonable() const { return _numUnreasonable; }
quint32 getNumOutOfOrder() const { return _numEarly + _numLate; }
quint32 getNumEarly() const { return _numEarly; }
quint32 getNumLate() const { return _numLate; }
quint32 getNumLost() const { return _numLost; }
quint32 getNumRecovered() const { return _numRecovered; }
quint32 getNumDuplicate() const { return _numDuplicate; }
const QSet<quint16>& getMissingSet() const { return _missingSet; }
private:
void pruneMissingSet(const bool wantExtraDebugging);
quint16 _lastReceived;
QSet<quint16> _missingSet;
quint32 _numReceived;
quint32 _numUnreasonable;
quint32 _numEarly;
quint32 _numLate;
quint32 _numLost;
quint32 _numRecovered;
quint32 _numDuplicate;
QUuid _lastSenderUUID;
};
#endif // hifi_SequenceNumberStats_h

View file

@ -0,0 +1,155 @@
//
// UserActivityLogger.cpp
//
//
// Created by Clement on 5/21/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "UserActivityLogger.h"
#include <QEventLoop>
#include <QJsonDocument>
#include <QHttpMultiPart>
#include <QTimer>
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
UserActivityLogger& UserActivityLogger::getInstance() {
static UserActivityLogger sharedInstance;
return sharedInstance;
}
UserActivityLogger::UserActivityLogger() {
}
void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCallbackParameters params) {
AccountManager& accountManager = AccountManager::getInstance();
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
// Adding the action name
QHttpPart actionPart;
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
actionPart.setBody(QByteArray().append(action));
multipart->append(actionPart);
// If there are action details, add them to the multipart
if (!details.isEmpty()) {
QHttpPart detailsPart;
detailsPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
" name=\"action_details\"");
detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact));
multipart->append(detailsPart);
}
qDebug() << "Logging activity" << action;
// if no callbacks specified, call our owns
if (params.isEmpty()) {
params.jsonCallbackReceiver = this;
params.jsonCallbackMethod = "requestFinished";
params.errorCallbackReceiver = this;
params.errorCallbackMethod = "requestError";
}
accountManager.authenticatedRequest(USER_ACTIVITY_URL,
QNetworkAccessManager::PostOperation,
params,
NULL,
multipart);
}
void UserActivityLogger::requestFinished(const QJsonObject& object) {
qDebug() << object;
}
void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) {
qDebug() << error << ": " << string;
}
void UserActivityLogger::launch(QString applicationVersion) {
const QString ACTION_NAME = "launch";
QJsonObject actionDetails;
QString VERSION_KEY = "version";
actionDetails.insert(VERSION_KEY, applicationVersion);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::close(int delayTime) {
const QString ACTION_NAME = "close";
// In order to get the end of the session, we need to give the account manager enough time to send the packet.
QEventLoop loop;
// Here we connect the callbacks to stop the event loop
JSONCallbackParameters params;
params.jsonCallbackReceiver = &loop;
params.errorCallbackReceiver = &loop;
params.jsonCallbackMethod = "quit";
params.errorCallbackMethod = "quit";
// In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime
QTimer timer;
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
// Now we can log it
logAction(ACTION_NAME, QJsonObject(), params);
timer.start(delayTime);
loop.exec();
}
void UserActivityLogger::changedDisplayName(QString displayName) {
const QString ACTION_NAME = "changed_display_name";
QJsonObject actionDetails;
const QString DISPLAY_NAME = "display_name";
actionDetails.insert(DISPLAY_NAME, displayName);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) {
const QString ACTION_NAME = "changed_model";
QJsonObject actionDetails;
const QString TYPE_OF_MODEL = "type_of_model";
const QString MODEL_URL = "model_url";
actionDetails.insert(TYPE_OF_MODEL, typeOfModel);
actionDetails.insert(MODEL_URL, modelURL);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::changedDomain(QString domainURL) {
const QString ACTION_NAME = "changed_domain";
QJsonObject actionDetails;
const QString DOMAIN_URL = "domain_url";
actionDetails.insert(DOMAIN_URL, domainURL);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) {
const QString ACTION_NAME = "connected_device";
QJsonObject actionDetails;
const QString TYPE_OF_DEVICE = "type_of_device";
const QString DEVICE_NAME = "device_name";
actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice);
actionDetails.insert(DEVICE_NAME, deviceName);
logAction(ACTION_NAME, actionDetails);
}
void UserActivityLogger::loadedScript(QString scriptName) {
const QString ACTION_NAME = "loaded_script";
QJsonObject actionDetails;
const QString SCRIPT_NAME = "script_name";
actionDetails.insert(SCRIPT_NAME, scriptName);
logAction(ACTION_NAME, actionDetails);
}

View file

@ -0,0 +1,47 @@
//
// UserActivityLogger.h
//
//
// Created by Clement on 5/21/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_UserActivityLogger_h
#define hifi_UserActivityLogger_h
#include "AccountManager.h"
#include <QObject>
#include <QString>
#include <QJsonObject>
#include <QNetworkReply>
class UserActivityLogger : public QObject {
Q_OBJECT
public:
static UserActivityLogger& getInstance();
public slots:
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
void launch(QString applicationVersion);
void close(int delayTime);
void changedDisplayName(QString displayName);
void changedModel(QString typeOfModel, QString modelURL);
void changedDomain(QString domainURL);
void connectedDevice(QString typeOfDevice, QString deviceName);
void loadedScript(QString scriptName);
private slots:
void requestFinished(const QJsonObject& object);
void requestError(QNetworkReply::NetworkError error,const QString& string);
private:
UserActivityLogger();
};
#endif // hifi_UserActivityLogger_h

View file

@ -21,10 +21,6 @@
#include "OctreeSceneStats.h"
const uint16_t MAX_MISSING_SEQUENCE = 100; /// how many items in our _missingSequenceNumbers before we start to prune them
const uint16_t MAX_MISSING_SEQUENCE_OLD_AGE = 1000; /// age we allow items in _missingSequenceNumbers to be before pruning
const int samples = 100;
OctreeSceneStats::OctreeSceneStats() :
_isReadyToSend(false),
@ -39,14 +35,7 @@ OctreeSceneStats::OctreeSceneStats() :
_incomingPacket(0),
_incomingBytes(0),
_incomingWastedBytes(0),
_incomingLastSequence(0),
_incomingLikelyLost(0),
_incomingRecovered(0),
_incomingEarly(0),
_incomingLate(0),
_incomingReallyLate(0),
_incomingPossibleDuplicate(0),
_missingSequenceNumbers(),
_incomingOctreeSequenceNumberStats(),
_incomingFlightTimeAverage(samples),
_jurisdictionRoot(NULL)
{
@ -150,15 +139,8 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) {
_incomingPacket = other._incomingPacket;
_incomingBytes = other._incomingBytes;
_incomingWastedBytes = other._incomingWastedBytes;
_incomingLastSequence = other._incomingLastSequence;
_incomingLikelyLost = other._incomingLikelyLost;
_incomingRecovered = other._incomingRecovered;
_incomingEarly = other._incomingEarly;
_incomingLate = other._incomingLate;
_incomingReallyLate = other._incomingReallyLate;
_incomingPossibleDuplicate = other._incomingPossibleDuplicate;
_missingSequenceNumbers = other._missingSequenceNumbers;
_incomingOctreeSequenceNumberStats = other._incomingOctreeSequenceNumberStats;
}
@ -875,155 +857,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet,
qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime;
return; // ignore any packets that are unreasonable
}
const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
// determine our expected sequence number... handle rollover appropriately
OCTREE_PACKET_SEQUENCE expected = _incomingPacket > 0 ? _incomingLastSequence + (quint16)1 : sequence;
const int USECS_PER_MSEC = 1000;
float flightTimeMsecs = flightTime / USECS_PER_MSEC;
_incomingFlightTimeAverage.updateAverage(flightTimeMsecs);
// track out of order and possibly lost packets...
if (sequence == _incomingLastSequence) {
if (wantExtraDebugging) {
qDebug() << "last packet duplicate got:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
}
} else {
if (sequence != expected) {
if (wantExtraDebugging) {
qDebug() << "out of order... got:" << sequence << "expected:" << expected;
}
int sequenceInt = (int)sequence;
int expectedInt = (int)expected;
// if distance between sequence and expected are more than half of the total range of possible seq numbers,
// assume that a rollover occurred between the two.
// correct the larger one so it's in the range [-UINT16_RANGE, -1] while the other remains in [0, UINT16_RANGE-1]
// after doing so, sequenceInt and expectedInt can be correctly compared to each other, though one may be negative
if (std::abs(sequenceInt - expectedInt) > UINT16_RANGE / 2) {
if (sequenceInt > expectedInt) {
sequenceInt -= UINT16_RANGE;
}
else {
expectedInt -= UINT16_RANGE;
}
}
// Guard against possible corrupted packets... with bad sequence numbers
const int MAX_RESONABLE_SEQUENCE_OFFSET = 2000;
const int MIN_RESONABLE_SEQUENCE_OFFSET = -2000;
int sequenceOffset = (sequenceInt - expectedInt);
if (sequenceOffset > MAX_RESONABLE_SEQUENCE_OFFSET || sequenceOffset < MIN_RESONABLE_SEQUENCE_OFFSET) {
qDebug() << "ignoring unreasonable packet... sequence:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
return; // ignore any packets that are unreasonable
}
// if the sequence is less than our expected, then this might be a packet
// that was delayed and so we should find it in our lostSequence list
if (sequenceInt < expectedInt) {
// if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
// if rollover between them: sequenceInt in [-UINT16_RANGE, -1], expectedInt in [0, UINT16_RANGE-1]
if (wantExtraDebugging) {
qDebug() << "this packet is later than expected...";
}
if (sequenceInt < expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
_incomingReallyLate++;
}
else {
_incomingLate++;
}
if (_missingSequenceNumbers.contains(sequence)) {
if (wantExtraDebugging) {
qDebug() << "found it in _missingSequenceNumbers";
}
_missingSequenceNumbers.remove(sequence);
_incomingLikelyLost--;
_incomingRecovered++;
}
else {
// if we're still in our pruning window, and we didn't find it in our missing list,
// than this is really unexpected and can probably only happen if the packet was a
// duplicate
if (sequenceInt >= expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
if (wantExtraDebugging) {
qDebug() << "sequence:" << sequence << "WAS NOT found in _missingSequenceNumbers, and not that old... (expected - MAX_MISSING_SEQUENCE_OLD_AGE):"
<< (uint16_t)(expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE);
}
_incomingPossibleDuplicate++;
}
}
// don't update _incomingLastSequence in this case.
// only bump the last sequence if it was greater than our expected sequence, this will keep us from
// accidentally going backwards when an out of order (recovered) packet comes in
} else { // sequenceInt > expectedInt
// if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
// if rollover between them: sequenceInt in [0, UINT16_RANGE-1], expectedInt in [-UINT16_RANGE, -1]
if (wantExtraDebugging) {
qDebug() << "this packet is earlier than expected...";
}
_incomingEarly++;
// hmm... so, we either didn't get some packets, or this guy came early...
int missing = sequenceInt - expectedInt;
if (wantExtraDebugging) {
qDebug() << ">>>>>>>> missing gap=" << missing;
}
_incomingLikelyLost += missing;
for (int missingSequenceInt = expectedInt; missingSequenceInt < sequenceInt; missingSequenceInt++) {
OCTREE_PACKET_SEQUENCE missingSequence = missingSequenceInt >= 0 ? missingSequenceInt : missingSequenceInt + UINT16_RANGE;
_missingSequenceNumbers << missingSequence;
}
_incomingLastSequence = sequence;
}
} else { // sequence = expected
_incomingLastSequence = sequence;
}
}
// do some garbage collecting on our _missingSequenceNumbers
if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE) {
if (wantExtraDebugging) {
qDebug() << "too many _missingSequenceNumbers:" << _missingSequenceNumbers.size();
}
int oldAgeCutoff = (int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE;
foreach(uint16_t missingItem, _missingSequenceNumbers) {
if (wantExtraDebugging) {
qDebug() << "checking item:" << missingItem << "is it in need of pruning?";
qDebug() << "(_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE):"
<< (uint16_t)((int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE);
}
bool prune;
if (oldAgeCutoff >= 0) {
prune = (missingItem <= oldAgeCutoff || missingItem > _incomingLastSequence);
}
else {
prune = (missingItem <= oldAgeCutoff + UINT16_RANGE && missingItem > _incomingLastSequence);
}
if (prune) {
if (wantExtraDebugging) {
qDebug() << "pruning really old missing sequence:" << missingItem;
}
_missingSequenceNumbers.remove(missingItem);
}
}
}
_incomingOctreeSequenceNumberStats.sequenceNumberReceived(sequence);
// track packets here...
_incomingPacket++;

View file

@ -17,6 +17,7 @@
#include <SharedUtil.h>
#include "JurisdictionMap.h"
#include "OctreePacketData.h"
#include "SequenceNumberStats.h"
#define GREENISH 0x40ff40d0
#define YELLOWISH 0xffef40c0
@ -164,16 +165,9 @@ public:
quint32 getIncomingPackets() const { return _incomingPacket; }
quint64 getIncomingBytes() const { return _incomingBytes; }
quint64 getIncomingWastedBytes() const { return _incomingWastedBytes; }
quint32 getIncomingOutOfOrder() const { return _incomingLate + _incomingEarly; }
quint32 getIncomingLikelyLost() const { return _incomingLikelyLost; }
quint32 getIncomingRecovered() const { return _incomingRecovered; }
quint32 getIncomingEarly() const { return _incomingEarly; }
quint32 getIncomingLate() const { return _incomingLate; }
quint32 getIncomingReallyLate() const { return _incomingReallyLate; }
quint32 getIncomingPossibleDuplicate() const { return _incomingPossibleDuplicate; }
float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
const QSet<OCTREE_PACKET_SEQUENCE>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
const SequenceNumberStats& getIncomingOctreeSequenceNumberStats() const { return _incomingOctreeSequenceNumberStats; }
private:
@ -268,14 +262,8 @@ private:
quint64 _incomingBytes;
quint64 _incomingWastedBytes;
quint16 _incomingLastSequence; /// last incoming sequence number
quint32 _incomingLikelyLost; /// count of packets likely lost, may be off by _incomingReallyLate count
quint32 _incomingRecovered; /// packets that were late, and we had in our missing list, we consider recovered
quint32 _incomingEarly; /// out of order earlier than expected
quint32 _incomingLate; /// out of order later than expected
quint32 _incomingReallyLate; /// out of order and later than MAX_MISSING_SEQUENCE_OLD_AGE late
quint32 _incomingPossibleDuplicate; /// out of order possibly a duplicate
QSet<OCTREE_PACKET_SEQUENCE> _missingSequenceNumbers;
SequenceNumberStats _incomingOctreeSequenceNumberStats;
SimpleMovingAverage _incomingFlightTimeAverage;
// features related items

View file

@ -349,6 +349,7 @@ void ScriptEngine::run() {
init();
}
_isRunning = true;
_isFinished = false;
emit runningStateChanged();
QScriptValue result = _engine.evaluate(_scriptContents);
@ -459,13 +460,17 @@ void ScriptEngine::run() {
_numAvatarSoundSentBytes = 0;
}
}
QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
? PacketTypeSilentAudioFrame
: PacketTypeMicrophoneAudioNoEcho);
QDataStream packetStream(&audioPacket, QIODevice::Append);
// pack a placeholder value for sequence number for now, will be packed when destination node is known
int numPreSequenceNumberBytes = audioPacket.size();
packetStream << (quint16)0;
// use the orientation and position of this avatar for the source of this audio
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
glm::quat headOrientation = _avatarData->getHeadOrientation();
@ -485,7 +490,19 @@ void ScriptEngine::run() {
numAvailableSamples * sizeof(int16_t));
}
nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
// write audio packet to AudioMixer nodes
NodeList* nodeList = NodeList::getInstance();
foreach(const SharedNodePointer& node, nodeList->getNodeHash()) {
// only send to nodes of type AudioMixer
if (node->getType() == NodeType::AudioMixer) {
// pack sequence number
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16));
// send audio packet
nodeList->writeDatagram(audioPacket, node);
}
}
}
}
@ -661,3 +678,7 @@ void ScriptEngine::include(const QString& includeFile) {
_engine.clearExceptions();
}
}
void ScriptEngine::nodeKilled(SharedNodePointer node) {
_outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
}

View file

@ -100,6 +100,8 @@ public slots:
void include(const QString& includeFile);
void print(const QString& message);
void nodeKilled(SharedNodePointer node);
signals:
void update(float deltaTime);
void scriptEnding();
@ -146,6 +148,7 @@ private:
ScriptUUID _uuidLibrary;
AnimationCache _animationCache;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
};
#endif // hifi_ScriptEngine_h

View file

@ -24,9 +24,6 @@ int MAX_ENTITIES_PER_SIMULATION = 64;
int MAX_COLLISIONS_PER_SIMULATION = 256;
const int NUM_SHAPE_BITS = 6;
const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1;
PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION),
_numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) {
}

View file

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(TARGET_NAME audio-tests)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
#find_package(Qt5Network REQUIRED)
#find_package(Qt5Script REQUIRED)
#find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
#include glm
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)

View file

@ -0,0 +1,146 @@
//
// AudioRingBufferTests.cpp
// tests/audio/src
//
// Created by Yixin Wang on 6/24/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioRingBufferTests.h"
#include "SharedUtil.h"
void AudioRingBufferTests::assertBufferSize(const AudioRingBuffer& buffer, int samples) {
if (buffer.samplesAvailable() != samples) {
qDebug("Unexpected num samples available! Exptected: %d Actual: %d\n", samples, buffer.samplesAvailable());
}
}
void AudioRingBufferTests::runAllTests() {
int16_t writeData[10000];
for (int i = 0; i < 10000; i++) { writeData[i] = i; }
int writeIndexAt;
int16_t readData[10000];
int readIndexAt;
AudioRingBuffer ringBuffer(10); // makes buffer of 100 int16_t samples
for (int T = 0; T < 300; T++) {
writeIndexAt = 0;
readIndexAt = 0;
// write 73 samples, 73 samples in buffer
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 73) / sizeof(int16_t);
assertBufferSize(ringBuffer, 73);
// read 43 samples, 30 samples in buffer
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 43) / sizeof(int16_t);
assertBufferSize(ringBuffer, 30);
// write 70 samples, 100 samples in buffer (full)
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 70) / sizeof(int16_t);
assertBufferSize(ringBuffer, 100);
// read 100 samples, 0 samples in buffer (empty)
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
assertBufferSize(ringBuffer, 0);
// verify 143 samples of read data
for (int i = 0; i < 143; i++) {
if (readData[i] != i) {
qDebug("first readData[%d] incorrect! Expcted: %d Actual: %d", i, i, readData[i]);
return;
}
}
writeIndexAt = 0;
readIndexAt = 0;
// write 59 samples, 59 samples in buffer
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 59) / sizeof(int16_t);
assertBufferSize(ringBuffer, 59);
// write 99 samples, 100 samples in buffer
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 99) / sizeof(int16_t);
assertBufferSize(ringBuffer, 100);
// read 100 samples, 0 samples in buffer
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
assertBufferSize(ringBuffer, 0);
// verify 100 samples of read data
for (int i = 0; i < 100; i++) {
readData[i] = writeIndexAt - 100 + i;
}
writeIndexAt = 0;
readIndexAt = 0;
// write 77 samples, 77 samples in buffer
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 77) / sizeof(int16_t);
assertBufferSize(ringBuffer, 77);
// write 24 samples, 100 samples in buffer (overwrote one sample: "0")
writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 24) / sizeof(int16_t);
assertBufferSize(ringBuffer, 100);
// write 29 silent samples, 100 samples in buffer, make sure non were added
int samplesWritten;
if ((samplesWritten = ringBuffer.addSilentFrame(29)) != 0) {
qDebug("addSilentFrame(29) incorrect! Expected: 0 Actual: %d", samplesWritten);
return;
}
assertBufferSize(ringBuffer, 100);
// read 3 samples, 97 samples in buffer (expect to read "1", "2", "3")
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
for (int i = 0; i < 3; i++) {
if (readData[i] != i + 1) {
qDebug("Second readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]);
return;
}
}
assertBufferSize(ringBuffer, 97);
// write 4 silent samples, 100 samples in buffer
if ((samplesWritten = ringBuffer.addSilentFrame(4) / sizeof(int16_t)) != 3) {
qDebug("addSilentFrame(4) incorrect! Exptected: 3 Actual: %d", samplesWritten);
return;
}
assertBufferSize(ringBuffer, 100);
// read back 97 samples (the non-silent samples), 3 samples in buffer (expect to read "4" thru "100")
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 97) / sizeof(int16_t);
for (int i = 3; i < 100; i++) {
if (readData[i] != i + 1) {
qDebug("third readData[%d] incorrect! Expcted: %d Actual: %d", i, i + 1, readData[i]);
return;
}
}
assertBufferSize(ringBuffer, 3);
// read back 3 silent samples, 0 samples in buffer
readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
for (int i = 100; i < 103; i++) {
if (readData[i] != 0) {
qDebug("Fourth readData[%d] incorrect! Expcted: %d Actual: %d", i, 0, readData[i]);
return;
}
}
assertBufferSize(ringBuffer, 0);
}
qDebug() << "PASSED";
}

View file

@ -0,0 +1,25 @@
//
// AudioRingBufferTests.h
// tests/audio/src
//
// Created by Yixin Wang on 6/24/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioRingBufferTests_h
#define hifi_AudioRingBufferTests_h
#include "AudioRingBuffer.h"
namespace AudioRingBufferTests {
void runAllTests();
void assertBufferSize(const AudioRingBuffer& buffer, int samples);
};
#endif // hifi_AudioRingBufferTests_h

19
tests/audio/src/main.cpp Normal file
View file

@ -0,0 +1,19 @@
//
// main.cpp
// tests/audio/src
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AudioRingBufferTests.h"
#include <stdio.h>
int main(int argc, char** argv) {
AudioRingBufferTests::runAllTests();
printf("all tests passed. press enter to exit\n");
getchar();
return 0;
}

View file

@ -27,6 +27,7 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}")
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
IF (WIN32)

View file

@ -440,6 +440,8 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
}
bool MetavoxelTests::run() {
LimitedNodeList::createInstance();
// seed the random number generator so that our tests are reproducible
srand(0xBAAAAABE);
@ -456,14 +458,13 @@ bool MetavoxelTests::run() {
}
}
QByteArray datagramHeader("testheader");
const int SIMULATION_ITERATIONS = 10000;
if (test == 0 || test == 2) {
qDebug() << "Running transmission test...";
qDebug();
// create two endpoints with the same header
Endpoint alice(datagramHeader), bob(datagramHeader);
TestEndpoint alice, bob;
alice.setOther(&bob);
bob.setOther(&alice);
@ -497,7 +498,7 @@ bool MetavoxelTests::run() {
datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
// create two endpoints with the same header
Endpoint alice(datagramHeader, Endpoint::CONGESTION_MODE), bob(datagramHeader, Endpoint::CONGESTION_MODE);
TestEndpoint alice(TestEndpoint::CONGESTION_MODE), bob(TestEndpoint::CONGESTION_MODE);
alice.setOther(&bob);
bob.setOther(&alice);
@ -537,8 +538,8 @@ bool MetavoxelTests::run() {
datagramsSent = bytesSent = datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
// create client and server endpoints
Endpoint client(datagramHeader, Endpoint::METAVOXEL_CLIENT_MODE);
Endpoint server(datagramHeader, Endpoint::METAVOXEL_SERVER_MODE);
TestEndpoint client(TestEndpoint::METAVOXEL_CLIENT_MODE);
TestEndpoint server(TestEndpoint::METAVOXEL_SERVER_MODE);
client.setOther(&server);
server.setOther(&client);
@ -599,28 +600,57 @@ int RandomVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
}
Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) :
class TestSendRecord : public PacketRecord {
public:
TestSendRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData(),
const SharedObjectPointer& localState = SharedObjectPointer(), int packetNumber = 0);
const SharedObjectPointer& getLocalState() const { return _localState; }
int getPacketNumber() const { return _packetNumber; }
private:
SharedObjectPointer _localState;
int _packetNumber;
};
TestSendRecord::TestSendRecord(const MetavoxelLOD& lod, const MetavoxelData& data,
const SharedObjectPointer& localState, int packetNumber) :
PacketRecord(lod, data),
_localState(localState),
_packetNumber(packetNumber) {
}
class TestReceiveRecord : public PacketRecord {
public:
TestReceiveRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData(),
const SharedObjectPointer& remoteState = SharedObjectPointer());
const SharedObjectPointer& getRemoteState() const { return _remoteState; }
private:
SharedObjectPointer _remoteState;
};
TestReceiveRecord::TestReceiveRecord(const MetavoxelLOD& lod,
const MetavoxelData& data, const SharedObjectPointer& remoteState) :
PacketRecord(lod, data),
_remoteState(remoteState) {
}
TestEndpoint::TestEndpoint(Mode mode) :
Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()),
_mode(mode),
_sequencer(new DatagramSequencer(datagramHeader, this)),
_highPriorityMessagesToSend(0.0f),
_reliableMessagesToSend(0.0f) {
connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
connect(_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
connect(_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
// insert the baseline send record
SendRecord sendRecord = { 0 };
_sendRecords.append(sendRecord);
// insert the baseline receive record
ReceiveRecord receiveRecord = { 0 };
_receiveRecords.append(receiveRecord);
if (mode == METAVOXEL_CLIENT_MODE) {
_lod = MetavoxelLOD(glm::vec3(), 0.01f);
return;
@ -643,15 +673,15 @@ Endpoint::Endpoint(const QByteArray& datagramHeader, Mode mode) :
// create the object that represents out delta-encoded state
_localState = new TestSharedObjectA();
connect(_sequencer->getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
SLOT(handleReliableMessage(const QVariant&)));
ReliableChannel* secondInput = _sequencer->getReliableInputChannel(1);
ReliableChannel* secondInput = _sequencer.getReliableInputChannel(1);
secondInput->setMessagesEnabled(false);
connect(&secondInput->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
// enqueue a large amount of data in a low-priority channel
ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
ReliableChannel* output = _sequencer.getReliableOutputChannel(1);
output->setPriority(0.25f);
output->setMessagesEnabled(false);
QByteArray bytes;
@ -800,11 +830,11 @@ int MutateVisitor::visit(MetavoxelInfo& info) {
return STOP_RECURSION;
}
bool Endpoint::simulate(int iterationNumber) {
bool TestEndpoint::simulate(int iterationNumber) {
// update/send our delayed datagrams
for (QList<ByteArrayIntPair>::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
if (it->second-- == 1) {
_other->receiveDatagram(it->first);
_other->parseData(it->first);
it = _delayedDatagrams.erase(it);
} else {
@ -819,41 +849,39 @@ bool Endpoint::simulate(int iterationNumber) {
ByteArrayVector datagrams = _pipeline.takeLast();
_pipeline.prepend(ByteArrayVector());
foreach (const QByteArray& datagram, datagrams) {
_sequencer->receivedDatagram(datagram);
_sequencer.receivedDatagram(datagram);
datagramsReceived++;
bytesReceived += datagram.size();
_remainingPipelineCapacity += datagram.size();
}
int packetCount = _sequencer->startPacketGroup();
int packetCount = _sequencer.startPacketGroup();
groupsSent++;
maxPacketsPerGroup = qMax(maxPacketsPerGroup, packetCount);
for (int i = 0; i < packetCount; i++) {
oldDatagramsSent = datagramsSent;
oldBytesSent = bytesSent;
Bitstream& out = _sequencer->startPacket();
Bitstream& out = _sequencer.startPacket();
out << QVariant();
_sequencer->endPacket();
_sequencer.endPacket();
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber() };
_sendRecords.append(record);
_sendRecords.append(maybeCreateSendRecord());
}
return false;
} else if (_mode == METAVOXEL_CLIENT_MODE) {
Bitstream& out = _sequencer->startPacket();
Bitstream& out = _sequencer.startPacket();
ClientStateMessage state = { _lod };
out << QVariant::fromValue(state);
_sequencer->endPacket();
_sequencer.endPacket();
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber(), SharedObjectPointer(), MetavoxelData(), _lod };
_sendRecords.append(record);
_sendRecords.append(maybeCreateSendRecord());
} else if (_mode == METAVOXEL_SERVER_MODE) {
// make a random change
@ -879,16 +907,15 @@ bool Endpoint::simulate(int iterationNumber) {
if (!_lod.isValid()) {
return false;
}
Bitstream& out = _sequencer->startPacket();
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(MetavoxelDeltaMessage());
_data.writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod);
PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
_data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
_sequencer.endPacket();
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber() + 1, SharedObjectPointer(), _data, _lod };
_sendRecords.append(record);
_sequencer->endPacket();
_sendRecords.append(maybeCreateSendRecord());
} else {
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
@ -897,7 +924,7 @@ bool Endpoint::simulate(int iterationNumber) {
while (_highPriorityMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_highPriorityMessagesSent.append(message);
_sequencer->sendHighPriorityMessage(message);
_sequencer.sendHighPriorityMessage(message);
highPriorityMessagesSent++;
_highPriorityMessagesToSend -= 1.0f;
}
@ -909,7 +936,7 @@ bool Endpoint::simulate(int iterationNumber) {
while (_reliableMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_reliableMessagesSent.append(message);
_sequencer->getReliableOutputChannel()->sendMessage(message);
_sequencer.getReliableOutputChannel()->sendMessage(message);
reliableMessagesSent++;
_reliableMessagesToSend -= 1.0f;
}
@ -919,12 +946,12 @@ bool Endpoint::simulate(int iterationNumber) {
// send a packet
try {
Bitstream& out = _sequencer->startPacket();
Bitstream& out = _sequencer.startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
_sequencer->endPacket();
_sequencer.endPacket();
} catch (const QString& message) {
qDebug() << message;
@ -932,15 +959,29 @@ bool Endpoint::simulate(int iterationNumber) {
}
// record the send
SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
_sendRecords.append(record);
_sendRecords.append(maybeCreateSendRecord());
}
maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
return false;
}
void Endpoint::sendDatagram(const QByteArray& datagram) {
int TestEndpoint::parseData(const QByteArray& packet) {
if (_mode == CONGESTION_MODE) {
if (packet.size() <= _remainingPipelineCapacity) {
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_pipeline[0].append(QByteArray(packet.constData(), packet.size()));
_remainingPipelineCapacity -= packet.size();
}
} else {
_sequencer.receivedDatagram(packet);
datagramsReceived++;
bytesReceived += packet.size();
}
return packet.size();
}
void TestEndpoint::sendDatagram(const QByteArray& datagram) {
datagramsSent++;
bytesSent += datagram.size();
@ -967,31 +1008,16 @@ void Endpoint::sendDatagram(const QByteArray& datagram) {
}
}
_other->receiveDatagram(datagram);
_other->parseData(datagram);
}
void Endpoint::handleHighPriorityMessage(const QVariant& message) {
if (message.userType() == ClearSharedObjectMessage::Type) {
return;
}
if (_other->_highPriorityMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent high priority message.");
}
QVariant sentMessage = _other->_highPriorityMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received high priority message mismatch.");
}
highPriorityMessagesReceived++;
}
void Endpoint::readMessage(Bitstream& in) {
void TestEndpoint::readMessage(Bitstream& in) {
if (_mode == CONGESTION_MODE) {
QVariant message;
in >> message;
// record the receipt
ReceiveRecord record = { _sequencer->getIncomingPacketNumber() };
_receiveRecords.append(record);
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
if (_mode == METAVOXEL_CLIENT_MODE) {
@ -1000,10 +1026,11 @@ void Endpoint::readMessage(Bitstream& in) {
handleMessage(message, in);
// deep-compare data to sent version
int packetNumber = _sequencer->getIncomingPacketNumber();
foreach (const SendRecord& sendRecord, _other->_sendRecords) {
if (sendRecord.packetNumber == packetNumber) {
if (!sendRecord.data.deepEquals(_data, _sendRecords.first().lod)) {
int packetNumber = _sequencer.getIncomingPacketNumber();
foreach (PacketRecord* record, _other->_sendRecords) {
TestSendRecord* sendRecord = static_cast<TestSendRecord*>(record);
if (sendRecord->getPacketNumber() == packetNumber) {
if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) {
qDebug() << "Sent/received metavoxel data mismatch.";
exit(true);
}
@ -1012,8 +1039,7 @@ void Endpoint::readMessage(Bitstream& in) {
}
// record the receipt
ReceiveRecord record = { packetNumber, SharedObjectPointer(), _data, _sendRecords.first().lod };
_receiveRecords.append(record);
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
if (_mode == METAVOXEL_SERVER_MODE) {
@ -1022,8 +1048,7 @@ void Endpoint::readMessage(Bitstream& in) {
handleMessage(message, in);
// record the receipt
ReceiveRecord record = { _sequencer->getIncomingPacketNumber() };
_receiveRecords.append(record);
_receiveRecords.append(maybeCreateReceiveRecord());
return;
}
@ -1033,8 +1058,7 @@ void Endpoint::readMessage(Bitstream& in) {
_remoteState = message.state;
// record the receipt
ReceiveRecord record = { _sequencer->getIncomingPacketNumber(), message.state };
_receiveRecords.append(record);
_receiveRecords.append(maybeCreateReceiveRecord());
for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
it != _other->_unreliableMessagesSent.end(); it++) {
@ -1056,7 +1080,48 @@ void Endpoint::readMessage(Bitstream& in) {
exit(true);
}
void Endpoint::handleReliableMessage(const QVariant& message) {
void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value<ClientStateMessage>();
_lod = state.lod;
} else if (userType == MetavoxelDeltaMessage::Type) {
PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
_data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
PacketRecord* TestEndpoint::maybeCreateSendRecord() const {
return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data,
_localState, _sequencer.getOutgoingPacketNumber());
}
PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const {
return new TestReceiveRecord(getLastAcknowledgedSendRecord()->getLOD(),
(_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState);
}
void TestEndpoint::handleHighPriorityMessage(const QVariant& message) {
if (message.userType() == ClearSharedObjectMessage::Type) {
return;
}
if (_other->_highPriorityMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent high priority message.");
}
QVariant sentMessage = _other->_highPriorityMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received high priority message mismatch.");
}
highPriorityMessagesReceived++;
}
void TestEndpoint::handleReliableMessage(const QVariant& message) {
if (message.userType() == ClearSharedObjectMessage::Type ||
message.userType() == ClearMainChannelSharedObjectMessage::Type) {
return;
@ -1071,8 +1136,8 @@ void Endpoint::handleReliableMessage(const QVariant& message) {
reliableMessagesReceived++;
}
void Endpoint::readReliableChannel() {
CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
void TestEndpoint::readReliableChannel() {
CircularBuffer& buffer = _sequencer.getReliableInputChannel(1)->getBuffer();
QByteArray bytes = buffer.read(buffer.bytesAvailable());
if (_other->_dataStreamed.size() < bytes.size()) {
throw QString("Received unsent/already sent streamed data.");
@ -1085,44 +1150,6 @@ void Endpoint::readReliableChannel() {
streamedBytesReceived += bytes.size();
}
void Endpoint::clearSendRecordsBefore(int index) {
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
}
void Endpoint::clearReceiveRecordsBefore(int index) {
_receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
}
void Endpoint::receiveDatagram(const QByteArray& datagram) {
if (_mode == CONGESTION_MODE) {
if (datagram.size() <= _remainingPipelineCapacity) {
// have to copy the datagram; the one we're passed is a reference to a shared buffer
_pipeline[0].append(QByteArray(datagram.constData(), datagram.size()));
_remainingPipelineCapacity -= datagram.size();
}
} else {
_sequencer->receivedDatagram(datagram);
datagramsReceived++;
bytesReceived += datagram.size();
}
}
void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
int userType = message.userType();
if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value<ClientStateMessage>();
_lod = state.lod;
} else if (userType == MetavoxelDeltaMessage::Type) {
_data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod);
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
handleMessage(element, in);
}
}
}
TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) :
_foo(foo),
_baz(baz),

View file

@ -15,8 +15,7 @@
#include <QCoreApplication>
#include <QVariantList>
#include <DatagramSequencer.h>
#include <MetavoxelData.h>
#include <Endpoint.h>
#include <ScriptCache.h>
class SequencedTestMessage;
@ -35,60 +34,43 @@ public:
};
/// Represents a simulated endpoint.
class Endpoint : public QObject {
class TestEndpoint : public Endpoint {
Q_OBJECT
public:
enum Mode { BASIC_PEER_MODE, CONGESTION_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
Endpoint(const QByteArray& datagramHeader, Mode mode = BASIC_PEER_MODE);
TestEndpoint(Mode mode = BASIC_PEER_MODE);
void setOther(Endpoint* other) { _other = other; }
void setOther(TestEndpoint* other) { _other = other; }
/// Perform a simulation step.
/// \return true if failure was detected
bool simulate(int iterationNumber);
private slots:
virtual int parseData(const QByteArray& packet);
protected:
void sendDatagram(const QByteArray& datagram);
virtual void sendDatagram(const QByteArray& data);
virtual void readMessage(Bitstream& in);
virtual void handleMessage(const QVariant& message, Bitstream& in);
virtual PacketRecord* maybeCreateSendRecord() const;
virtual PacketRecord* maybeCreateReceiveRecord() const;
private slots:
void handleHighPriorityMessage(const QVariant& message);
void readMessage(Bitstream& in);
void handleReliableMessage(const QVariant& message);
void readReliableChannel();
void clearSendRecordsBefore(int index);
void clearReceiveRecordsBefore(int index);
private:
void receiveDatagram(const QByteArray& datagram);
void handleMessage(const QVariant& message, Bitstream& in);
class SendRecord {
public:
int packetNumber;
SharedObjectPointer localState;
MetavoxelData data;
MetavoxelLOD lod;
};
class ReceiveRecord {
public:
int packetNumber;
SharedObjectPointer remoteState;
MetavoxelData data;
MetavoxelLOD lod;
};
Mode _mode;
DatagramSequencer* _sequencer;
QList<SendRecord> _sendRecords;
QList<ReceiveRecord> _receiveRecords;
SharedObjectPointer _localState;
SharedObjectPointer _remoteState;
@ -97,7 +79,7 @@ private:
SharedObjectPointer _sphere;
Endpoint* _other;
TestEndpoint* _other;
typedef QPair<QByteArray, int> ByteArrayIntPair;
QList<ByteArrayIntPair> _delayedDatagrams;

View file

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(TARGET_NAME networking-tests)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
#find_package(Qt5Network REQUIRED)
#find_package(Qt5Script REQUIRED)
#find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
#include glm
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)

View file

@ -0,0 +1,267 @@
//
// AudioRingBufferTests.cpp
// tests/networking/src
//
// Created by Yixin Wang on 6/24/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SequenceNumberStatsTests.h"
#include "SharedUtil.h"
#include <limits>
void SequenceNumberStatsTests::runAllTests() {
rolloverTest();
earlyLateTest();
duplicateTest();
pruneTest();
}
const quint32 UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
void SequenceNumberStatsTests::rolloverTest() {
SequenceNumberStats stats;
// insert enough samples to cause 3 rollovers
quint16 seq = 79; // start on some random number
for (int R = 0; R < 2; R++) {
for (quint32 i = 0; i < 3 * UINT16_RANGE; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
assert(stats.getNumDuplicate() == 0);
assert(stats.getNumEarly() == 0);
assert(stats.getNumLate() == 0);
assert(stats.getNumLost() == 0);
assert(stats.getNumReceived() == i + 1);
assert(stats.getNumRecovered() == 0);
}
stats.reset();
}
}
void SequenceNumberStatsTests::earlyLateTest() {
SequenceNumberStats stats;
quint16 seq = 65530;
quint32 numSent = 0;
quint32 numEarly = 0;
quint32 numLate = 0;
quint32 numLost = 0;
quint32 numRecovered = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 10000; T++) {
// insert 7 consecutive
for (int i = 0; i < 7; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
assert(stats.getNumDuplicate() == 0);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == numRecovered);
}
// skip 10
quint16 skipped = seq;
seq = seq + (quint16)10;
// insert 36 consecutive
numEarly++;
numLost += 10;
for (int i = 0; i < 36; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
assert(stats.getNumDuplicate() == 0);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == numRecovered);
}
// send ones we skipped
for (int i = 0; i < 10; i++) {
stats.sequenceNumberReceived(skipped);
skipped = skipped + (quint16)1;
numSent++;
numLate++;
numLost--;
numRecovered++;
assert(stats.getNumDuplicate() == 0);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == numRecovered);
}
}
stats.reset();
}
}
void SequenceNumberStatsTests::duplicateTest() {
SequenceNumberStats stats;
quint16 seq = 12345;
quint32 numSent = 0;
quint32 numDuplicate = 0;
quint32 numEarly = 0;
quint32 numLate = 0;
quint32 numLost = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 10000; T++) {
quint16 duplicate = seq;
// insert 7 consecutive
for (int i = 0; i < 7; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
assert(stats.getNumDuplicate() == numDuplicate);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == 0);
}
// skip 10
seq = seq + (quint16)10;
quint16 duplicate2 = seq;
numEarly++;
numLost += 10;
// insert 36 consecutive
for (int i = 0; i < 36; i++) {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
assert(stats.getNumDuplicate() == numDuplicate);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == 0);
}
// send 5 duplicates from before skip
for (int i = 0; i < 5; i++) {
stats.sequenceNumberReceived(duplicate);
duplicate = duplicate + (quint16)1;
numSent++;
numDuplicate++;
numLate++;
assert(stats.getNumDuplicate() == numDuplicate);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == 0);
}
// send 5 duplicates from after skip
for (int i = 0; i < 5; i++) {
stats.sequenceNumberReceived(duplicate2);
duplicate2 = duplicate2 + (quint16)1;
numSent++;
numDuplicate++;
numLate++;
assert(stats.getNumDuplicate() == numDuplicate);
assert(stats.getNumEarly() == numEarly);
assert(stats.getNumLate() == numLate);
assert(stats.getNumLost() == numLost);
assert(stats.getNumReceived() == numSent);
assert(stats.getNumRecovered() == 0);
}
}
stats.reset();
}
}
void SequenceNumberStatsTests::pruneTest() {
SequenceNumberStats stats;
quint16 seq = 54321;
quint32 numSent = 0;
quint32 numEarly = 0;
quint32 numLost = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 1000; T++) {
// insert 1 seq
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
// skip 1000 seq
seq = seq + (quint16)1000;
quint16 highestSkipped = seq - (quint16)1;
// insert 1 seq
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
numEarly++;
numLost += 1000;
// skip 10 seq
seq = seq + (quint16)10;
quint16 highestSkipped2 = seq - (quint16)1;
// insert 1 seq
// insert 1 seq
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
numSent++;
numEarly++;
numLost += 10;
const QSet<quint16>& missingSet = stats.getMissingSet();
assert(missingSet.size() <= 1000);
for (int i = 0; i < 10; i++) {
assert(missingSet.contains(highestSkipped2));
highestSkipped2 = highestSkipped2 - (quint16)1;
}
for (int i = 0; i < 989; i++) {
assert(missingSet.contains(highestSkipped));
highestSkipped = highestSkipped - (quint16)1;
}
for (int i = 0; i < 11; i++) {
assert(!missingSet.contains(highestSkipped));
highestSkipped = highestSkipped - (quint16)1;
}
}
stats.reset();
}
}

View file

@ -0,0 +1,28 @@
//
// AudioRingBufferTests.h
// tests/networking/src
//
// Created by Yixin Wang on 6/24/2014
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_SequenceNumberStatsTests_h
#define hifi_SequenceNumberStatsTests_h
#include "SequenceNumberStatsTests.h"
#include "SequenceNumberStats.h"
namespace SequenceNumberStatsTests {
void runAllTests();
void rolloverTest();
void earlyLateTest();
void duplicateTest();
void pruneTest();
};
#endif // hifi_SequenceNumberStatsTests_h

View file

@ -0,0 +1,19 @@
//
// main.cpp
// tests/networking/src
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SequenceNumberStatsTests.h"
#include <stdio.h>
int main(int argc, char** argv) {
SequenceNumberStatsTests::runAllTests();
printf("tests passed! press enter to exit");
getchar();
return 0;
}

View file

@ -18,9 +18,14 @@ include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)

View file

@ -18,9 +18,14 @@ include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
ENDIF(WIN32)
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)