Merge branch 'master' into 20872

This commit is contained in:
David Rowe 2016-04-20 08:51:53 +12:00
commit 54b032f612
76 changed files with 4992 additions and 663 deletions

View file

@ -19,6 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#ifdef _WIN32
#include <math.h>
@ -577,15 +578,15 @@ void AudioMixer::domainSettingsRequestComplete() {
void AudioMixer::broadcastMixes() {
auto nodeList = DependencyManager::get<NodeList>();
int64_t nextFrame = 0;
QElapsedTimer timer;
timer.start();
int64_t usecToSleep = AudioConstants::NETWORK_FRAME_USECS;
auto nextFrameTimestamp = p_high_resolution_clock::now();
auto timeToSleep = std::chrono::microseconds(0);
const int TRAILING_AVERAGE_FRAMES = 100;
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
int currentFrame { 1 };
int numFramesPerSecond { (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC) };
while (!_isFinished) {
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
@ -595,12 +596,12 @@ void AudioMixer::broadcastMixes() {
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
if (usecToSleep < 0) {
usecToSleep = 0;
if (timeToSleep.count() < 0) {
timeToSleep = std::chrono::microseconds(0);
}
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
+ (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS);
+ (timeToSleep.count() * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS);
float lastCutoffRatio = _performanceThrottlingRatio;
bool hasRatioChanged = false;
@ -694,10 +695,11 @@ void AudioMixer::broadcastMixes() {
nodeList->sendPacket(std::move(mixPacket), *node);
nodeData->incrementOutgoingMixedAudioSequenceNumber();
static const int FRAMES_PER_SECOND = int(ceilf(1.0f / AudioConstants::NETWORK_FRAME_SECS));
// send an audio stream stats packet to the client approximately every second
if (nextFrame % FRAMES_PER_SECOND == 0) {
++currentFrame;
currentFrame %= numFramesPerSecond;
if (nodeData->shouldSendStats(currentFrame)) {
nodeData->sendAudioStreamStatsPackets(node);
}
@ -718,11 +720,14 @@ void AudioMixer::broadcastMixes() {
break;
}
usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - (timer.nsecsElapsed() / 1000);
// push the next frame timestamp to when we should send the next
nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS);
if (usecToSleep > 0) {
usleep(usecToSleep);
}
// sleep as long as we need until next frame, if we can
auto now = p_high_resolution_clock::now();
timeToSleep = std::chrono::duration_cast<std::chrono::microseconds>(nextFrameTimestamp - now);
std::this_thread::sleep_for(timeToSleep);
}
}

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <random>
#include <QtCore/QDebug>
#include <QtCore/QJsonArray>
@ -26,7 +28,14 @@ AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
_outgoingMixedAudioSequenceNumber(0),
_downstreamAudioStreamStats()
{
// of the ~94 blocks in a second of audio sent from the AudioMixer, pick a random one to send out a stats packet on
// this ensures we send out stats to this client around every second
// but do not send all of the stats packets out at the same time
std::random_device randomDevice;
std::mt19937 numberGenerator { randomDevice() };
std::uniform_int_distribution<> distribution { 1, (int) ceil(1.0f / AudioConstants::NETWORK_FRAME_SECS) };
_frameToSendStats = distribution(numberGenerator);
}
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
@ -180,6 +189,10 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend() {
}
}
bool AudioMixerClientData::shouldSendStats(int frameNumber) {
return frameNumber == _frameToSendStats;
}
void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) {
auto nodeList = DependencyManager::get<NodeList>();

View file

@ -58,6 +58,9 @@ public:
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
// uses randomization to have the AudioMixer send a stats packet to this node around every second
bool shouldSendStats(int frameNumber);
signals:
void injectorStreamFinished(const QUuid& streamIdentifier);
@ -72,6 +75,8 @@ private:
quint16 _outgoingMixedAudioSequenceNumber;
AudioStreamStats _downstreamAudioStreamStats;
int _frameToSendStats { 0 };
};
#endif // hifi_AudioMixerClientData_h

View file

@ -36,14 +36,7 @@ const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXE
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
ThreadedAssignment(message),
_broadcastThread(),
_lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()),
_trailingSleepRatio(1.0f),
_performanceThrottlingRatio(0.0f),
_sumListeners(0),
_numStatFrames(0),
_sumBillboardPackets(0),
_sumIdentityPackets(0)
_broadcastThread()
{
// make sure we hear about node kills so we can tell the other nodes
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
@ -51,7 +44,6 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::AvatarBillboard, this, "handleAvatarBillboardPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
}
@ -66,13 +58,18 @@ AvatarMixer::~AvatarMixer() {
// An 80% chance of sending a identity packet within a 5 second interval.
// assuming 60 htz update rate.
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
// NOTE: some additional optimizations to consider.
// 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present
// if the avatar is not in view or in the keyhole.
void AvatarMixer::broadcastAvatarData() {
int idleTime = QDateTime::currentMSecsSinceEpoch() - _lastFrameTimestamp;
int idleTime = AVATAR_DATA_SEND_INTERVAL_MSECS;
if (_lastFrameTimestamp.time_since_epoch().count() > 0) {
auto idleDuration = p_high_resolution_clock::now() - _lastFrameTimestamp;
idleTime = std::chrono::duration_cast<std::chrono::microseconds>(idleDuration).count();
}
++_numStatFrames;
@ -245,32 +242,13 @@ void AvatarMixer::broadcastAvatarData() {
return;
}
// make sure we send out identity and billboard packets to and from new arrivals.
// make sure we send out identity packets to and from new arrivals.
bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID());
// we will also force a send of billboard or identity packet
// if either has changed in the last frame
if (otherNodeData->getBillboardChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
QByteArray rfcUUID = otherNode->getUUID().toRfc4122();
QByteArray billboard = otherNodeData->getAvatar().getBillboard();
auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size());
billboardPacket->write(rfcUUID);
billboardPacket->write(billboard);
nodeList->sendPacket(std::move(billboardPacket), *node);
++_sumBillboardPackets;
}
if (otherNodeData->getIdentityChangeTimestamp() > 0
if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0
&& (forceSend
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|| distribution(generator) < IDENTITY_SEND_PROBABILITY)) {
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
@ -385,7 +363,7 @@ void AvatarMixer::broadcastAvatarData() {
otherAvatar.doneEncoding(false);
});
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
_lastFrameTimestamp = p_high_resolution_clock::now();
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
@ -438,26 +416,12 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
// parse the identity packet and update the change timestamp if appropriate
if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
nodeData->flagIdentityChange();
}
}
}
}
void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
if (nodeData) {
AvatarData& avatar = nodeData->getAvatar();
// parse the billboard packet and update the change timestamp if appropriate
if (avatar.hasBillboardChangedAfterParsing(message->getMessage())) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
}
}
}
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message) {
DependencyManager::get<NodeList>()->processKillNode(*message);
}
@ -466,7 +430,6 @@ void AvatarMixer::sendStatsPacket() {
QJsonObject statsObject;
statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;
statsObject["average_billboard_packets_per_frame"] = (float) _sumBillboardPackets / (float) _numStatFrames;
statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames;
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
@ -507,7 +470,6 @@ void AvatarMixer::sendStatsPacket() {
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
_sumListeners = 0;
_sumBillboardPackets = 0;
_sumIdentityPackets = 0;
_numStatFrames = 0;
}

View file

@ -15,6 +15,8 @@
#ifndef hifi_AvatarMixer_h
#define hifi_AvatarMixer_h
#include <PortableHighResolutionClock.h>
#include <ThreadedAssignment.h>
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
@ -34,7 +36,6 @@ public slots:
private slots:
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
void domainSettingsRequestComplete();
@ -44,15 +45,14 @@ private:
QThread _broadcastThread;
quint64 _lastFrameTimestamp;
p_high_resolution_clock::time_point _lastFrameTimestamp;
float _trailingSleepRatio;
float _performanceThrottlingRatio;
float _trailingSleepRatio { 1.0f };
float _performanceThrottlingRatio { 0.0f };
int _sumListeners;
int _numStatFrames;
int _sumBillboardPackets;
int _sumIdentityPackets;
int _sumListeners { 0 };
int _numStatFrames { 0 };
int _sumIdentityPackets { 0 };
float _maxKbpsPerNode = 0.0f;

View file

@ -24,6 +24,7 @@
#include <NodeData.h>
#include <NumericalConstants.h>
#include <udt/PacketHeaders.h>
#include <PortableHighResolutionClock.h>
#include <SimpleMovingAverage.h>
#include <UUIDHasher.h>
@ -33,6 +34,8 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
class AvatarMixerClientData : public NodeData {
Q_OBJECT
public:
using HRCTime = p_high_resolution_clock::time_point;
int parseData(ReceivedMessage& message) override;
AvatarData& getAvatar() { return *_avatar; }
@ -45,11 +48,8 @@ public:
uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; }
quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; }
void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; }
quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; }
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
float getFullRateDistance() const { return _fullRateDistance; }
@ -86,8 +86,7 @@ private:
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
quint64 _billboardChangeTimestamp = 0;
quint64 _identityChangeTimestamp = 0;
HRCTime _identityChangeTimestamp;
float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX;

View file

@ -9,16 +9,20 @@
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#
set(INTERFACE_DISPLAY_NAME "Interface")
set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SHORTCUT_NAME@")
set(INTERFACE_HF_SHORTCUT_NAME "@INTERFACE_HF_SHORTCUT_NAME@")
set(INTERFACE_WIN_EXEC_NAME "@INTERFACE_EXEC_PREFIX@.exe")
set(CONSOLE_DISPLAY_NAME "Sandbox")
set(CONSOLE_INSTALL_SUBDIR "@CONSOLE_INSTALL_DIR@")
set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SHORTCUT_NAME@")
set(CONSOLE_HF_SHORTCUT_NAME "@CONSOLE_HF_SHORTCUT_NAME@")
set(CONSOLE_WIN_EXEC_NAME "@CONSOLE_EXEC_NAME@")
set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@")
set(PRE_SANDBOX_CONSOLE_SHORTCUT_NAME "@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@")
set(DS_DISPLAY_NAME "Domain Server")
set(DS_EXEC_NAME "@DS_EXEC_NAME@")
set(AC_DISPLAY_NAME "Assignment Client")
set(AC_EXEC_NAME "@AC_EXEC_NAME@")
set(HIGH_FIDELITY_PROTOCOL "@HIGH_FIDELITY_PROTOCOL@")
set(PRODUCTION_BUILD "@PRODUCTION_BUILD@")

View file

@ -737,10 +737,10 @@ SectionEnd
!macroend
!macro CheckForRunningApplications action prompter
!insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "@CONSOLE_SHORTCUT_NAME@" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "@INTERFACE_SHORTCUT_NAME@" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@DS_EXEC_NAME@" "Domain Server" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@AC_EXEC_NAME@" "Assignment Client" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "@INTERFACE_DISPLAY_NAME@" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "@CONSOLE_DISPLAY_NAME@" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@DS_EXEC_NAME@" "@DS_DISPLAY_NAME@" ${action} ${prompter}
!insertmacro PromptForRunningApplication "@AC_EXEC_NAME@" "@AC_DISPLAY_NAME@" ${action} ${prompter}
!macroend
;--------------------------------

View file

@ -14,11 +14,9 @@ Script.load("edit.js");
Script.load("examples.js");
Script.load("selectAudioDevice.js");
Script.load("notifications.js");
Script.load("users.js");
Script.load("controllers/handControllerGrab.js");
Script.load("controllers/squeezeHands.js");
Script.load("grab.js");
Script.load("directory.js");
Script.load("dialTone.js");
// Script.load("attachedEntitiesManager.js");
Script.load("depthReticle.js");

View file

@ -0,0 +1,50 @@
//
// disableAvatarAnimations.js
// examples
//
// Copyright 2016 High Fidelity, Inc.
//
// When launched, it will replace all of the avatars animations with a single frame idle pose.
// full body IK and hand grabbing animations will still continue to function, but all other
// animations will be replaced.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var skeletonModelURL = "";
var jointCount = 0;
var excludedRoles = ["rightHandGraspOpen", "rightHandGraspClosed", "leftHandGraspOpen", "leftHandGraspClosed"];
var IDLE_URL = "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/idle.fbx";
function overrideAnims() {
var roles = MyAvatar.getAnimationRoles();
var i, l = roles.length;
for (i = 0; i < l; i++) {
if (excludedRoles.indexOf(roles[i]) == -1) {
MyAvatar.overrideRoleAnimation(roles[i], IDLE_URL, 30, false, 1, 1);
}
}
}
function restoreAnims() {
var roles = MyAvatar.getAnimationRoles();
var i, l = roles.length;
for (i = 0; i < l; i++) {
if (excludedRoles.indexOf(roles[i]) == -1) {
MyAvatar.restoreRoleAnimation(roles[i]);
}
}
}
overrideAnims();
MyAvatar.onLoadComplete.connect(function () {
overrideAnims();
});
Script.scriptEnding.connect(function () {
restoreAnims();
});

View file

@ -0,0 +1,56 @@
//
// exampleSelfCallingTimeoutNoCleanup.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 4/18/16.
// Copyright 2016 High Fidelity, Inc.
//
// This is an example of an entity script which hooks the update signal
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
ExampleUpdate = function() {
_this = this;
};
ExampleUpdate.prototype = {
timeOutFunction: function() {
var entityID = _this.entityID;
print("timeOutFunction in entityID:" + entityID);
Script.setTimeout(function() {
_this.timeOutFunction();
}, 3000);
},
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * connecting to the update signal so we can check our grabbed state
preload: function(entityID) {
print("preload - entityID:" + entityID);
this.entityID = entityID;
print("preload - entityID:" + entityID + "-- calling timeOutFunction()....");
_this.timeOutFunction();
},
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
// to the update signal
unload: function(entityID) {
print("unload - entityID:" + entityID);
print("NOTE --- WE DID NOT CALL clear our timeout");
},
};
// entity scripts always need to return a newly constructed object of our type
return new ExampleUpdate();
})

View file

@ -0,0 +1,50 @@
//
// exampleTimeoutNoCleanup.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 4/18/16.
// Copyright 2016 High Fidelity, Inc.
//
// This is an example of an entity script which hooks the update signal
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
ExampleUpdate = function() {
_this = this;
};
ExampleUpdate.prototype = {
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * connecting to the update signal so we can check our grabbed state
preload: function(entityID) {
print("preload - entityID:" + entityID);
this.entityID = entityID;
Script.setInterval(function() {
var entityID = _this.entityID;
print("timer interval in entityID:" + entityID);
}, 3000);
},
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
// to the update signal
unload: function(entityID) {
print("unload - entityID:" + entityID);
print("NOTE --- WE DID NOT CALL clear our timeout");
},
};
// entity scripts always need to return a newly constructed object of our type
return new ExampleUpdate();
})

View file

@ -0,0 +1,54 @@
//
// exampleUpdate.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 4/18/16.
// Copyright 2016 High Fidelity, Inc.
//
// This is an example of an entity script which hooks the update signal
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
ExampleUpdate = function() {
_this = this;
};
ExampleUpdate.prototype = {
// update() will be called regulary, because we've hooked the update signal in our preload() function.
// we will check the avatars hand positions and if either hand is in our bounding box, we will notice that
update: function() {
// because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID
var entityID = _this.entityID;
print("update in entityID:" + entityID);
},
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * connecting to the update signal so we can check our grabbed state
preload: function(entityID) {
print("preload - entityID:" + entityID);
this.entityID = entityID;
Script.update.connect(this.update);
},
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
// to the update signal
unload: function(entityID) {
print("unload - entityID:" + entityID);
Script.update.disconnect(this.update);
},
};
// entity scripts always need to return a newly constructed object of our type
return new ExampleUpdate();
})

View file

@ -0,0 +1,54 @@
//
// exampleUpdateNoDisconnect.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 4/18/16.
// Copyright 2016 High Fidelity, Inc.
//
// This is an example of an entity script which hooks the update signal
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var _this;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
ExampleUpdate = function() {
_this = this;
};
ExampleUpdate.prototype = {
// update() will be called regulary, because we've hooked the update signal in our preload() function.
// we will check the avatars hand positions and if either hand is in our bounding box, we will notice that
update: function() {
// because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID
var entityID = _this.entityID;
print("update in entityID:" + entityID);
},
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * connecting to the update signal so we can check our grabbed state
preload: function(entityID) {
print("preload - entityID:" + entityID);
this.entityID = entityID;
Script.update.connect(this.update);
},
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application. In all cases we want to unhook our connection
// to the update signal
unload: function(entityID) {
print("unload - entityID:" + entityID);
print("NOTE --- WE DID NOT CALL Script.update.disconnect()");
},
};
// entity scripts always need to return a newly constructed object of our type
return new ExampleUpdate();
})

View file

@ -0,0 +1,53 @@
(function() {
function ConsoleReporter(options) {
this.jasmineStarted = function (obj) {
print("jasmineStarted: numSpecs = " + obj.totalSpecsDefined);
};
this.jasmineDone = function (obj) {
print("jasmineDone");
};
this.suiteStarted = function(obj) {
print("suiteStarted: \"" + obj.fullName + "\"");
};
this.suiteDone = function(obj) {
print("suiteDone: \"" + obj.fullName + "\" " + obj.status);
};
this.specStarted = function(obj) {
print("specStarted: \"" + obj.fullName + "\"");
};
this.specDone = function(obj) {
print("specDone: \"" + obj.fullName + "\" " + obj.status);
var i, l = obj.failedExpectations.length;
for (i = 0; i < l; i++) {
print(" " + obj.failedExpectations[i].message);
}
};
return this;
}
setTimeout = Script.setTimeout;
setInterval = Script.setInterval;
clearTimeout = Script.clearTimeout;
clearInterval = Script.clearInterval;
var jasmine = jasmineRequire.core(jasmineRequire);
var env = jasmine.getEnv();
env.addReporter(new ConsoleReporter());
var jasmineInterface = jasmineRequire.interface(jasmine, env);
extend(this, jasmineInterface);
function extend(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
return destination;
}
}());

File diff suppressed because it is too large Load diff

View file

@ -355,20 +355,36 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
});
}
};
that.windowDimensions = Controller.getViewportDimensions();
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
var recommendedRect = Controller.getRecommendedOverlayRect();
var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height };
that.windowDimensions = recommendedDimmensions; // Controller.getViewportDimensions();
that.origin = { x: recommendedRect.x, y: recommendedRect.y };
// Maybe fixme: Keeping the same percent of the window size isn't always the right thing.
// For example, maybe we want "keep the same percentage to whatever two edges are closest to the edge of screen".
// If we change that, the places to do so are onResizeViewport, save (maybe), and the initial move based on Settings, below.
that.onResizeViewport = function (newSize) { // Can be overridden or extended by clients.
var fractionX = that.x / that.windowDimensions.x;
var fractionY = that.y / that.windowDimensions.y;
that.windowDimensions = newSize || Controller.getViewportDimensions();
that.move(fractionX * that.windowDimensions.x, fractionY * that.windowDimensions.y);
var recommendedRect = Controller.getRecommendedOverlayRect();
var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height };
var originRelativeX = (that.x - that.origin.x);
var originRelativeY = (that.y - that.origin.y);
var fractionX = clamp(originRelativeX / that.windowDimensions.x, 0, 1);
var fractionY = clamp(originRelativeY / that.windowDimensions.y, 0, 1);
that.windowDimensions = newSize || recommendedDimmensions;
that.origin = { x: recommendedRect.x, y: recommendedRect.y };
var newX = (fractionX * that.windowDimensions.x) + recommendedRect.x;
var newY = (fractionY * that.windowDimensions.y) + recommendedRect.y;
that.move(newX, newY);
};
if (optionalPersistenceKey) {
this.fractionKey = optionalPersistenceKey + '.fraction';
this.save = function () {
var screenSize = Controller.getViewportDimensions();
var recommendedRect = Controller.getRecommendedOverlayRect();
var screenSize = { x: recommendedRect.width, y: recommendedRect.height };
if (screenSize.x > 0 && screenSize.y > 0) {
// Guard against invalid screen size that can occur at shut-down.
var fraction = {x: that.x / screenSize.x, y: that.y / screenSize.y};
@ -411,7 +427,9 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y);
};
that.checkResize = function () { // Can be overriden or extended, but usually not. See onResizeViewport.
var currentWindowSize = Controller.getViewportDimensions();
var recommendedRect = Controller.getRecommendedOverlayRect();
var currentWindowSize = { x: recommendedRect.width, y: recommendedRect.height };
if ((currentWindowSize.x !== that.windowDimensions.x) || (currentWindowSize.y !== that.windowDimensions.y)) {
that.onResizeViewport(currentWindowSize);
}
@ -434,7 +452,8 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit
}
if (this.fractionKey || optionalInitialPositionFunction) {
var savedFraction = JSON.parse(Settings.getValue(this.fractionKey) || '0'); // getValue can answer empty string
var screenSize = Controller.getViewportDimensions();
var recommendedRect = Controller.getRecommendedOverlayRect();
var screenSize = { x: recommendedRect.width, y: recommendedRect.height };
if (savedFraction) {
// If we have saved data, keep the toolbar at the same proportion of the screen width/height.
that.move(savedFraction.x * screenSize.x, savedFraction.y * screenSize.y);

View file

@ -0,0 +1,59 @@
Script.include("../libraries/jasmine/jasmine.js");
Script.include("../libraries/jasmine/hifi-boot.js");
// Art3mis
var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758";
var ORIGIN = {x: 0, y: 0, z: 0};
var ONE_HUNDRED = {x: 100, y: 100, z: 100};
var ROT_IDENT = {x: 0, y: 0, z: 0, w: 1};
describe("MyAvatar", function () {
// reload the avatar from scratch before each test.
beforeEach(function (done) {
MyAvatar.skeletonModelURL = DEFAULT_AVATAR_URL;
// wait until we are finished loading
var id = Script.setInterval(function () {
if (MyAvatar.jointNames.length == 72) {
// assume we are finished loading.
Script.clearInterval(id);
MyAvatar.position = ORIGIN;
MyAvatar.orientation = ROT_IDENT;
// give the avatar 1/2 a second to settle on the ground in the idle pose.
Script.setTimeout(function () {
done();
}, 500);
}
}, 500);
});
// makes the assumption that there is solid ground somewhat underneath the avatar.
it("position and orientation getters", function () {
var pos = MyAvatar.position;
expect(Math.abs(pos.x)).toBeLessThan(0.1);
expect(Math.abs(pos.y)).toBeLessThan(1.0);
expect(Math.abs(pos.z)).toBeLessThan(0.1);
var rot = MyAvatar.orientation;
expect(Math.abs(rot.x)).toBeLessThan(0.01);
expect(Math.abs(rot.y)).toBeLessThan(0.01);
expect(Math.abs(rot.z)).toBeLessThan(0.01);
expect(Math.abs(1 - rot.w)).toBeLessThan(0.01);
});
it("position and orientation setters", function (done) {
MyAvatar.position = ONE_HUNDRED;
Script.setTimeout(function () {
expect(Vec3.length(Vec3.subtract(MyAvatar.position, ONE_HUNDRED))).toBeLessThan(0.1);
done();
}, 100);
});
});
jasmine.getEnv().execute();

View file

@ -39,6 +39,7 @@ Column {
"Shadow",
"Pyramid Depth",
"Ambient Occlusion",
"Ambient Occlusion Blurred",
"Custom Shader"
]
RadioButton {

View file

@ -80,11 +80,6 @@ Item {
label: "GPU",
color: "#1AC567"
},
{
prop: "frameTextureCount",
label: "Frame",
color: "#E2334D"
},
{
prop: "textureGPUTransferCount",
label: "Transfer",
@ -114,13 +109,7 @@ Item {
prop: "textureGPUVirtualMemoryUsage",
label: "GPU Virtual",
color: "#9495FF"
},
{
prop: "frameTextureMemoryUsage",
label: "Frame",
color: "#E2334D"
}
]
}
@ -170,6 +159,24 @@ Item {
]
}
PlotPerf {
title: "State Changes"
height: parent.evalEvenHeight()
object: stats.config
plots: [
{
prop: "frameTextureCount",
label: "Textures",
color: "#00B4EF"
},
{
prop: "frameSetPipelineCount",
label: "Pipelines",
color: "#E2334D"
}
]
}
property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred")
property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred")
property var drawLightConfig: Render.getConfig("DrawLight")
@ -178,9 +185,10 @@ Item {
title: "Items"
height: parent.evalEvenHeight()
object: parent.drawOpaqueConfig
plots: [
{
object: Render.getConfig("DrawOpaqueDeferred"),
object: parent.drawOpaqueConfig,
prop: "numDrawn",
label: "Opaques",
color: "#1AC567"
@ -198,7 +206,42 @@ Item {
color: "#FED959"
}
]
}
}
PlotPerf {
title: "Timing"
height: parent.evalEvenHeight()
object: parent.drawOpaqueConfig
valueUnit: "ms"
valueScale: 1000
valueNumDigits: "1"
plots: [
{
object: Render.getConfig("DrawOpaqueDeferred"),
prop: "cpuRunTime",
label: "Opaques",
color: "#1AC567"
},
{
object: Render.getConfig("DrawTransparentDeferred"),
prop: "cpuRunTime",
label: "Translucents",
color: "#00B4EF"
},
{
object: Render.getConfig("RenderDeferred"),
prop: "cpuRunTime",
label: "Lighting",
color: "#FED959"
},
{
object: Render.getConfig("RenderDeferredTask"),
prop: "cpuRunTime",
label: "RenderFrame",
color: "#E2334D"
}
]
}
}
}

View file

@ -23,6 +23,8 @@ Hifi.AvatarInputs {
readonly property int mirrorWidth: 265
readonly property int iconSize: 24
readonly property int iconPadding: 5
readonly property bool shouldReposition: true
Settings {
category: "Overlay.AvatarInputs"

View file

@ -21,8 +21,11 @@ FocusScope {
objectName: "desktop"
anchors.fill: parent
onHeightChanged: d.repositionAll();
onWidthChanged: d.repositionAll();
property rect recommendedRect: rect(0,0,0,0);
onHeightChanged: d.handleSizeChanged();
onWidthChanged: d.handleSizeChanged();
// Controls and windows can trigger this signal to ensure the desktop becomes visible
// when they're opened.
@ -50,6 +53,20 @@ FocusScope {
QtObject {
id: d
function handleSizeChanged() {
var oldRecommendedRect = recommendedRect;
var newRecommendedRectJS = Controller.getRecommendedOverlayRect();
var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
newRecommendedRectJS.width,
newRecommendedRectJS.height);
if (oldRecommendedRect != Qt.rect(0,0,0,0)
&& oldRecommendedRect != newRecommendedRect) {
d.repositionAll();
}
recommendedRect = newRecommendedRect;
}
function findChild(item, name) {
for (var i = 0; i < item.children.length; ++i) {
if (item.children[i].objectName === name) {
@ -202,12 +219,42 @@ FocusScope {
// }
}
function getRepositionChildren(predicate) {
var currentWindows = [];
if (!desktop) {
console.log("Could not find desktop");
return currentWindows;
}
for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i];
if (child.shouldReposition === true && (!predicate || predicate(child))) {
currentWindows.push(child)
}
}
return currentWindows;
}
function repositionAll() {
var oldRecommendedRect = recommendedRect;
var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
var newRecommendedRect = Controller.getRecommendedOverlayRect();
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
var windows = d.getTopLevelWindows();
for (var i = 0; i < windows.length; ++i) {
reposition(windows[i]);
var targetWindow = windows[i];
if (targetWindow.visible) {
repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
}
}
// also reposition the other children that aren't top level windows but want to be repositioned
var otherChildren = d.getRepositionChildren();
for (var i = 0; i < otherChildren.length; ++i) {
var child = otherChildren[i];
repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
}
}
}
@ -232,38 +279,56 @@ FocusScope {
targetWindow.focus = true;
}
reposition(targetWindow);
var oldRecommendedRect = recommendedRect;
var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
var newRecommendedRect = Controller.getRecommendedOverlayRect();
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
showDesktop();
}
function reposition(item) {
function repositionWindow(targetWindow, forceReposition,
oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) {
if (desktop.width === 0 || desktop.height === 0) {
return;
}
var targetWindow = d.getDesktopWindow(item);
if (!targetWindow) {
console.warn("Could not find top level window for " + item);
return;
}
var recommended = Controller.getRecommendedOverlayRect();
var maxX = recommended.x + recommended.width;
var maxY = recommended.y + recommended.height;
var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y);
// If the window is completely offscreen, reposition it
if ((targetWindow.x > desktop.width || (targetWindow.x + targetWindow.width) < 0) ||
(targetWindow.y > desktop.height || (targetWindow.y + targetWindow.height) < 0)) {
// if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it
if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) ||
(targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) {
newPosition.x = -1
newPosition.y = -1
}
if (newPosition.x === -1 && newPosition.y === -1) {
// Set initial window position
// var minPosition = Qt.vector2d(-windowRect.x, -windowRect.y);
// var maxPosition = Qt.vector2d(desktop.width - windowRect.width, desktop.height - windowRect.height);
// newPosition = Utils.clampVector(newPosition, minPosition, maxPosition);
// newPosition = Utils.randomPosition(minPosition, maxPosition);
newPosition = Qt.vector2d(desktop.width / 2 - targetWindow.width / 2,
desktop.height / 2 - targetWindow.height / 2);
var originRelativeX = (targetWindow.x - oldRecommendedRect.x);
var originRelativeY = (targetWindow.y - oldRecommendedRect.y);
if (isNaN(originRelativeX)) {
originRelativeX = 0;
}
if (isNaN(originRelativeY)) {
originRelativeY = 0;
}
var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1);
var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1);
var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x;
var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y;
newPosition = Qt.vector2d(newX, newY);
}
targetWindow.x = newPosition.x;
targetWindow.y = newPosition.y;

View file

@ -195,12 +195,7 @@ static const uint32_t INVALID_FRAME = UINT32_MAX;
static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check for entities that aren't ready for simulation
#ifndef __APPLE__
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
#else
// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js");
#endif
Setting::Handle<int> maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS);
@ -1183,8 +1178,6 @@ void Application::cleanupBeforeQuit() {
}
_keyboardFocusHighlight = nullptr;
getEntities()->clear(); // this will allow entity scripts to properly shutdown
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
@ -1195,6 +1188,7 @@ void Application::cleanupBeforeQuit() {
nodeList->getPacketReceiver().setShouldDropPackets(true);
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
DependencyManager::get<ScriptEngines>()->saveScripts();
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
DependencyManager::destroy<ScriptEngines>();
@ -2686,8 +2680,6 @@ void Application::idle(uint64_t now) {
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
}
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
@ -4191,8 +4183,13 @@ void Application::updateWindowTitle() const {
}
void Application::clearDomainOctreeDetails() {
// if we're about to quit, we really don't need to do any of these things...
if (_aboutToQuit) {
return;
}
qCDebug(interfaceapp) << "Clearing domain octree details...";
// reset the environment so that we don't erroneously end up with multiple
resetPhysicsReadyInformation();
@ -4216,7 +4213,6 @@ void Application::clearDomainOctreeDetails() {
void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle();
clearDomainOctreeDetails();
// disable physics until we have enough information about our new location to not cause craziness.
resetPhysicsReadyInformation();
}
@ -4899,19 +4895,44 @@ QRect Application::getRenderingGeometry() const {
}
glm::uvec2 Application::getUiSize() const {
return getActiveDisplayPlugin()->getRecommendedUiSize();
static const uint MIN_SIZE = 1;
glm::uvec2 result(MIN_SIZE);
if (_displayPlugin) {
result = getActiveDisplayPlugin()->getRecommendedUiSize();
}
return result;
}
QRect Application::getRecommendedOverlayRect() const {
auto uiSize = getUiSize();
QRect result(0, 0, uiSize.x, uiSize.y);
if (_displayPlugin) {
result = getActiveDisplayPlugin()->getRecommendedOverlayRect();
}
return result;
}
QSize Application::getDeviceSize() const {
return fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize());
static const int MIN_SIZE = 1;
QSize result(MIN_SIZE, MIN_SIZE);
if (_displayPlugin) {
result = fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize());
}
return result;
}
bool Application::isThrottleRendering() const {
return getActiveDisplayPlugin()->isThrottled();
if (_displayPlugin) {
return getActiveDisplayPlugin()->isThrottled();
}
return false;
}
bool Application::hasFocus() const {
return getActiveDisplayPlugin()->hasFocus();
if (_displayPlugin) {
return getActiveDisplayPlugin()->hasFocus();
}
return (QApplication::activeWindow() != nullptr);
}
glm::vec2 Application::getViewportDimensions() const {

View file

@ -117,6 +117,7 @@ public:
QRect getRenderingGeometry() const;
glm::uvec2 getUiSize() const;
QRect getRecommendedOverlayRect() const;
QSize getDeviceSize() const;
bool hasFocus() const;

View file

@ -14,7 +14,6 @@
#include <mutex>
#include <QElapsedTimer>
#include <gpu/Context.h>
#include <NumericalConstants.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
@ -42,26 +41,11 @@ static const float TAU = 6.28318530717958f;
//static const float MILKY_WAY_RATIO = 0.4f;
static const char* UNIFORM_TIME_NAME = "iGlobalTime";
Stars::Stars() {
}
Stars::~Stars() {
}
// Produce a random float value between 0 and 1
static float frand() {
return (float)rand() / (float)RAND_MAX;
}
// Produce a random radian value between 0 and 2 PI (TAU)
/*
static float rrand() {
return frand() * TAU;
}
*/
// http://mathworld.wolfram.com/SpherePointPicking.html
static vec2 randPolar() {
vec2 result(frand(), frand());
@ -115,59 +99,56 @@ struct StarVertex {
vec4 colorAndSize;
};
// FIXME star colors
void Stars::render(RenderArgs* renderArgs, float alpha) {
static gpu::BufferPointer vertexBuffer;
static gpu::Stream::FormatPointer streamFormat;
static gpu::Element positionElement, colorElement;
static gpu::PipelinePointer _gridPipeline;
static gpu::PipelinePointer _starsPipeline;
static int32_t _timeSlot{ -1 };
static std::once_flag once;
static const int STARS_VERTICES_SLOT{ 0 };
static const int STARS_COLOR_SLOT{ 1 };
const int VERTICES_SLOT = 0;
const int COLOR_SLOT = 1;
gpu::PipelinePointer Stars::_gridPipeline{};
gpu::PipelinePointer Stars::_starsPipeline{};
int32_t Stars::_timeSlot{ -1 };
std::call_once(once, [&] {
{
auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert));
auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag));
auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram((*program));
_timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME);
if (_timeSlot == gpu::Shader::INVALID_LOCATION) {
_timeSlot = program->getUniforms().findLocation(UNIFORM_TIME_NAME);
}
auto state = gpu::StatePointer(new gpu::State());
// enable decal blend
state->setDepthTest(gpu::State::DepthTest(false));
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_gridPipeline = gpu::Pipeline::create(program, state);
}
{
auto vs = gpu::Shader::createVertex(std::string(stars_vert));
auto ps = gpu::Shader::createPixel(std::string(stars_frag));
auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram((*program));
auto state = gpu::StatePointer(new gpu::State());
// enable decal blend
state->setDepthTest(gpu::State::DepthTest(false));
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
state->setAntialiasedLineEnable(true); // line smoothing also smooth points
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_starsPipeline = gpu::Pipeline::create(program, state);
void Stars::init() {
if (!_gridPipeline) {
auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert));
auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag));
auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram((*program));
_timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME);
if (_timeSlot == gpu::Shader::INVALID_LOCATION) {
_timeSlot = program->getUniforms().findLocation(UNIFORM_TIME_NAME);
}
auto state = gpu::StatePointer(new gpu::State());
// enable decal blend
state->setDepthTest(gpu::State::DepthTest(false));
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_gridPipeline = gpu::Pipeline::create(program, state);
}
if (!_starsPipeline) {
auto vs = gpu::Shader::createVertex(std::string(stars_vert));
auto ps = gpu::Shader::createPixel(std::string(stars_frag));
auto program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::makeProgram((*program));
auto state = gpu::StatePointer(new gpu::State());
// enable decal blend
state->setDepthTest(gpu::State::DepthTest(false));
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
state->setAntialiasedLineEnable(true); // line smoothing also smooth points
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
_starsPipeline = gpu::Pipeline::create(program, state);
}
unsigned limit = STARFIELD_NUM_STARS;
std::vector<StarVertex> points;
points.resize(limit);
{ // generate stars
QElapsedTimer startTime;
startTime.start();
vertexBuffer.reset(new gpu::Buffer);
srand(STARFIELD_SEED);
unsigned limit = STARFIELD_NUM_STARS;
std::vector<StarVertex> points;
points.resize(limit);
for (size_t star = 0; star < limit; ++star) {
points[star].position = vec4(fromPolar(randPolar()), 1);
float size = frand() * 2.5f + 0.5f;
@ -179,16 +160,32 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
points[star].colorAndSize = vec4(color, size);
}
}
double timeDiff = (double)startTime.nsecsElapsed() / 1000000.0; // ns to ms
qDebug() << "Total time to generate stars: " << timeDiff << " msec";
}
gpu::Element positionElement, colorElement;
const size_t VERTEX_STRIDE = sizeof(StarVertex);
vertexBuffer->append(VERTEX_STRIDE * limit, (const gpu::Byte*)&points[0]);
streamFormat.reset(new gpu::Stream::Format()); // 1 for everyone
streamFormat->setAttribute(gpu::Stream::POSITION, STARS_VERTICES_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), 0);
streamFormat->setAttribute(gpu::Stream::COLOR, STARS_COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA));
positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element;
colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element;
size_t offset = offsetof(StarVertex, position);
positionView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement);
offset = offsetof(StarVertex, colorAndSize);
colorView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, colorElement);
}
// FIXME star colors
void Stars::render(RenderArgs* renderArgs, float alpha) {
std::call_once(once, [&]{ init(); });
vertexBuffer->append(sizeof(StarVertex) * limit, (const gpu::Byte*)&points[0]);
streamFormat.reset(new gpu::Stream::Format()); // 1 for everyone
streamFormat->setAttribute(gpu::Stream::POSITION, VERTICES_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), 0);
streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA));
positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element;
colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element;
});
auto modelCache = DependencyManager::get<ModelCache>();
auto textureCache = DependencyManager::get<TextureCache>();
@ -210,17 +207,10 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
batch._glUniform1f(_timeSlot, secs);
geometryCache->renderCube(batch);
static const size_t VERTEX_STRIDE = sizeof(StarVertex);
size_t offset = offsetof(StarVertex, position);
gpu::BufferView posView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement);
offset = offsetof(StarVertex, colorAndSize);
gpu::BufferView colView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, colorElement);
// Render the stars
batch.setPipeline(_starsPipeline);
batch.setInputFormat(streamFormat);
batch.setInputBuffer(VERTICES_SLOT, posView);
batch.setInputBuffer(COLOR_SLOT, colView);
batch.setInputBuffer(STARS_VERTICES_SLOT, positionView);
batch.setInputBuffer(STARS_COLOR_SLOT, colorView);
batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS);
}

View file

@ -12,21 +12,37 @@
#ifndef hifi_Stars_h
#define hifi_Stars_h
#include <gpu/Context.h>
class RenderArgs;
// Starfield rendering component.
class Stars {
public:
Stars();
~Stars();
Stars() = default;
~Stars() = default;
Stars(Stars const&) = delete;
Stars& operator=(Stars const&) = delete;
// Renders the starfield from a local viewer's perspective.
// The parameters specifiy the field of view.
void render(RenderArgs* args, float alpha);
private:
// don't copy/assign
Stars(Stars const&); // = delete;
Stars& operator=(Stars const&); // delete;
// Pipelines
static gpu::PipelinePointer _gridPipeline;
static gpu::PipelinePointer _starsPipeline;
static int32_t _timeSlot;
// Buffers
gpu::BufferPointer vertexBuffer;
gpu::Stream::FormatPointer streamFormat;
gpu::BufferView positionView;
gpu::BufferView colorView;
std::once_flag once;
void init();
};

View file

@ -198,6 +198,8 @@ MyAvatar::MyAvatar(RigPointer rig) :
_headData->setLookAtPosition(headData->getLookAtPosition());
}
});
connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
}
MyAvatar::~MyAvatar() {
@ -349,6 +351,10 @@ void MyAvatar::simulate(float deltaTime) {
_skeletonModel->simulate(deltaTime);
}
// we've achived our final adjusted position and rotation for the avatar
// and all of its joints, now update our attachements.
Avatar::simulateAttachments(deltaTime);
if (!_skeletonModel->hasSkeleton()) {
// All the simulation that can be done has been done
return;
@ -1169,9 +1175,6 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
// now that physics has adjusted our position, we can update attachements.
Avatar::simulateAttachments(deltaTime);
}
QString MyAvatar::getScriptedMotorFrame() const {
@ -1575,6 +1578,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe
float speedIncreaseFactor = 1.8f;
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
if (motorSpeed < maxBoostSpeed) {
// an active keyboard motor should never be slower than this
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
@ -2089,7 +2093,7 @@ float MyAvatar::getAccelerationEnergy() {
int changeInVelocity = abs(velocity.length() - priorVelocity.length());
float changeInEnergy = priorVelocity.length() * changeInVelocity * AVATAR_MOVEMENT_ENERGY_CONSTANT;
priorVelocity = velocity;
return changeInEnergy;
}
@ -2111,4 +2115,3 @@ bool MyAvatar::didTeleport() {
lastPosition = pos;
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
}

View file

@ -299,7 +299,7 @@ signals:
void collisionWithEntity(const Collision& collision);
void energyChanged(float newEnergy);
void positionGoneTo();
void onLoadComplete();
private:

View file

@ -80,6 +80,11 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const {
return qApp->getUiSize();
}
QVariant ControllerScriptingInterface::getRecommendedOverlayRect() const {
auto rect = qApp->getRecommendedOverlayRect();
return qRectToVariant(rect);
}
controller::InputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) {
// This is where we retrieve the Device Tracker category and then the sub tracker within it
auto icIt = _inputControllers.find(0);

View file

@ -96,6 +96,7 @@ public slots:
virtual void releaseJoystick(int joystickIndex);
virtual glm::vec2 getViewportDimensions() const;
virtual QVariant getRecommendedOverlayRect() const;
/// Factory to create an InputController
virtual controller::InputController* createInputController(const QString& deviceName, const QString& tracker);

View file

@ -1161,6 +1161,7 @@ void Rig::initAnimGraph(const QUrl& url) {
overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
}
emit onLoadComplete();
});
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;

View file

@ -32,6 +32,7 @@ typedef std::shared_ptr<Rig> RigPointer;
// However only specific methods thread-safe. Noted below.
class Rig : public QObject, public std::enable_shared_from_this<Rig> {
Q_OBJECT
public:
struct StateHandler {
AnimVariantMap results;
@ -223,7 +224,10 @@ public:
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
protected:
signals:
void onLoadComplete();
protected:
bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); }
void updateAnimationStateHandlers();
void applyOverridePoses();

View file

@ -388,7 +388,7 @@ void SwingTwistConstraint::dynamicallyAdjustLimits(const glm::quat& rotation) {
glm::vec3 swungY = swingRotation * Vectors::UNIT_Y;
glm::vec3 swingAxis = glm::cross(Vectors::UNIT_Y, swungY);
float theta = atan2f(-swingAxis.z, swingAxis.x);
if (isnan(theta)) {
if (glm::isnan(theta)) {
// atan2f() will only return NaN if either of its arguments is NaN, which can only
// happen if we've been given a bad rotation. Since a NaN value here could potentially
// cause a crash (we use the value of theta to compute indices into a std::vector)

View file

@ -29,6 +29,7 @@ namespace AudioConstants {
const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample);
const float NETWORK_FRAME_SECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / float(AudioConstants::SAMPLE_RATE));
const float NETWORK_FRAME_MSECS = NETWORK_FRAME_SECS * 1000.0f;
const float NETWORK_FRAMES_PER_SEC = 1.0f / NETWORK_FRAME_SECS;
// be careful with overflows when using this constant
const int NETWORK_FRAME_USECS = static_cast<int>(NETWORK_FRAME_MSECS * 1000.0f);

View file

@ -34,6 +34,7 @@ static const float reticleSize = TWO_PI / 100.0f;
static QString _tooltipId;
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV
const QRect CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT = QRect(956, 0, 2048, 1188); // don't include entire width only center 2048
const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y;
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO);
const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles

View file

@ -42,6 +42,7 @@ class CompositorHelper : public QObject, public Dependency {
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
public:
static const uvec2 VIRTUAL_SCREEN_SIZE;
static const QRect VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT;
static const float VIRTUAL_UI_ASPECT_RATIO;
static const vec2 VIRTUAL_UI_TARGET_FOV;
static const vec2 MOUSE_EXTENTS_ANGULAR_SIZE;

View file

@ -34,6 +34,11 @@ glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const {
return CompositorHelper::VIRTUAL_SCREEN_SIZE;
}
QRect HmdDisplayPlugin::getRecommendedOverlayRect() const {
return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT;
}
bool HmdDisplayPlugin::internalActivate() {
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);

View file

@ -26,6 +26,8 @@ public:
void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final;
bool isDisplayVisible() const override { return isHmdMounted(); }
QRect getRecommendedOverlayRect() const override final;
virtual glm::mat4 getHeadPose() const override;

View file

@ -75,9 +75,29 @@ EntityTreeRenderer::~EntityTreeRenderer() {
// it is registered with ScriptEngines, which will call deleteLater for us.
}
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
void EntityTreeRenderer::setupEntitiesScriptEngine() {
QSharedPointer<ScriptEngine> oldEngine = _entitiesScriptEngine; // save the old engine through this function, so the EntityScriptingInterface doesn't have problems with it.
_entitiesScriptEngine = QSharedPointer<ScriptEngine>(new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)), &QObject::deleteLater);
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data());
_entitiesScriptEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine.data());
}
void EntityTreeRenderer::clear() {
leaveAllEntities();
_entitiesScriptEngine->unloadAllEntityScripts();
if (_entitiesScriptEngine) {
_entitiesScriptEngine->unloadAllEntityScripts();
_entitiesScriptEngine->stop();
}
if (_wantScripts && !_shuttingDown) {
// NOTE: you can't actually need to delete it here because when we call setupEntitiesScriptEngine it will
// assign a new instance to our shared pointer, which will deref the old instance and ultimately call
// the custom deleter which calls deleteLater
setupEntitiesScriptEngine();
}
auto scene = _viewState->getMain3DScene();
render::PendingChanges pendingChanges;
@ -94,7 +114,7 @@ void EntityTreeRenderer::reloadEntityScripts() {
_entitiesScriptEngine->unloadAllEntityScripts();
foreach(auto entity, _entitiesInScene) {
if (!entity->getScript().isEmpty()) {
_entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), entity->getScript(), true);
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entity->getEntityItemID(), entity->getScript(), true);
}
}
}
@ -105,10 +125,7 @@ void EntityTreeRenderer::init() {
entityTree->setFBXService(this);
if (_wantScripts) {
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities");
_scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
_entitiesScriptEngine->runInThread();
DependencyManager::get<EntityScriptingInterface>()->setEntitiesScriptEngine(_entitiesScriptEngine);
setupEntitiesScriptEngine();
}
forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities
@ -122,6 +139,8 @@ void EntityTreeRenderer::init() {
void EntityTreeRenderer::shutdown() {
_entitiesScriptEngine->disconnectNonEssentialSignals(); // disconnect all slots/signals from the script engine, except essential
_shuttingDown = true;
clear(); // always clear() on shutdown
}
void EntityTreeRenderer::setTree(OctreePointer newTree) {
@ -763,7 +782,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const
if (entity && entity->shouldPreloadScript()) {
QString scriptUrl = entity->getScript();
scriptUrl = ResourceManager::normalizeURL(scriptUrl);
_entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload);
ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload);
entity->scriptHasPreloaded();
}
}

View file

@ -126,6 +126,8 @@ protected:
}
private:
void setupEntitiesScriptEngine();
void addEntityToScene(EntityItemPointer entity);
bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar);
@ -155,7 +157,7 @@ private:
NetworkTexturePointer _ambientTexture;
bool _wantScripts;
ScriptEngine* _entitiesScriptEngine;
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
bool isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree,
const EntityItemID& id, const Collision& collision);
@ -196,6 +198,8 @@ private:
QHash<EntityItemID, EntityItemPointer> _entitiesInScene;
// For Scene.shouldRenderEntities
QList<EntityItemID> _entityIDsLastInScene;
static int _entitiesScriptEngineCount;
};

View file

@ -18,7 +18,7 @@
//
const QString SHADER_COMMON = R"SHADER(#version 410 core
const QString SHADER_COMMON = R"SHADER(
layout(location = 0) out vec4 _fragColor0;
layout(location = 1) out vec4 _fragColor1;
layout(location = 2) out vec4 _fragColor2;

View file

@ -414,7 +414,13 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
}
}
void EntityScriptingInterface::setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) {
std::lock_guard<std::mutex> lock(_entitiesScriptEngineLock);
_entitiesScriptEngine = engine;
}
void EntityScriptingInterface::callEntityMethod(QUuid id, const QString& method, const QStringList& params) {
std::lock_guard<std::mutex> lock(_entitiesScriptEngineLock);
if (_entitiesScriptEngine) {
EntityItemID entityID{ id };
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params);

View file

@ -71,7 +71,7 @@ public:
void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; }
void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine);
float calculateCost(float mass, float oldVelocity, float newVelocity);
public slots:
@ -214,6 +214,8 @@ private:
bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard);
EntityTreePointer _entityTree;
std::mutex _entitiesScriptEngineLock;
EntitiesScriptEngineProvider* _entitiesScriptEngine { nullptr };
bool _bidOnSimulationOwnership { false };

View file

@ -83,11 +83,18 @@ protected:
};
friend class OffscreenQmlSurface;
QJsonObject getGLContextData();
Queue _queue;
QMutex _mutex;
QWaitCondition _waitCondition;
std::atomic<bool> _rendering { false };
QJsonObject _glData;
QMutex _glMutex;
QWaitCondition _glWait;
private:
// Event-driven methods
void init();
@ -211,22 +218,31 @@ void OffscreenQmlRenderThread::setupFbo() {
}
}
QJsonObject OffscreenQmlRenderThread::getGLContextData() {
_glMutex.lock();
if (_glData.isEmpty()) {
_glWait.wait(&_glMutex);
}
_glMutex.unlock();
return _glData;
}
void OffscreenQmlRenderThread::init() {
qDebug() << "Initializing QML Renderer";
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
if (!_canvas.makeCurrent()) {
qWarning("Failed to make context current on QML Renderer Thread");
_quit = true;
return;
}
// Expose GL data to QML
auto glData = getGLContextData();
auto setGL = [=]{ _surface->getRootContext()->setContextProperty("GL", glData); };
_surface->executeOnUiThread(setGL);
_glMutex.lock();
_glData = ::getGLContextData();
_glMutex.unlock();
_glWait.wakeAll();
connect(_renderControl, &QQuickRenderControl::renderRequested, _surface, &OffscreenQmlSurface::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, _surface, &OffscreenQmlSurface::requestUpdate);
_renderControl->initialize(_canvas.getContext());
setupFbo();
@ -386,14 +402,16 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController());
}
_qmlEngine->rootContext()->setContextProperty("GL", _renderer->getGLContextData());
_qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlComponent = new QQmlComponent(_qmlEngine);
// When Quick says there is a need to render, we will not render immediately. Instead,
// a timer with a small interval is used to get better performance.
_updateTimer.setInterval(MIN_TIMER_MS);
QObject::connect(&_updateTimer, &QTimer::timeout, this, &OffscreenQmlSurface::updateQuick);
QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &OffscreenQmlSurface::onAboutToQuit);
_updateTimer.setInterval(MIN_TIMER_MS);
_updateTimer.start();
_qmlComponent = new QQmlComponent(_qmlEngine);
_qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
}
void OffscreenQmlSurface::resize(const QSize& newSize_) {

View file

@ -13,11 +13,11 @@
<@if GLPROFILE == PC_GL @>
<@def GPU_FEATURE_PROFILE GPU_CORE@>
<@def VERSION_HEADER #version 410 core@>
<@def VERSION_HEADER //PC 410 core@>
<@elif GLPROFILE == MAC_GL @>
<@def GPU_FEATURE_PROFILE GPU_CORE@>
<@def VERSION_HEADER #version 410 core@>
<@def VERSION_HEADER //MAC 410 core@>
<@else@>
<@def GPU_FEATURE_PROFILE GPU_CORE@>
<@def VERSION_HEADER #version 410 core@>
<@def VERSION_HEADER //410 core@>
<@endif@>

View file

@ -39,6 +39,8 @@ public:
int _DSNumAPIDrawcalls = 0;
int _DSNumDrawcalls = 0;
int _DSNumTriangles = 0;
int _PSNumSetPipelines = 0;
ContextStats() {}
ContextStats(const ContextStats& stats) = default;

View file

@ -461,8 +461,10 @@ void GLBackend::resetStages() {
#define ADD_COMMAND_GL(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size());
//#define DO_IT_NOW(call, offset) runLastCommand();
#define DO_IT_NOW(call, offset)
#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc
// THis will be used in the next PR
// #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc)
void Batch::_glActiveBindTexture(GLenum unit, GLenum target, GLuint texture) {
// clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine
@ -472,14 +474,11 @@ void Batch::_glActiveBindTexture(GLenum unit, GLenum target, GLuint texture) {
_params.push_back(texture);
_params.push_back(target);
_params.push_back(unit);
DO_IT_NOW(_glActiveBindTexture, 3);
}
void GLBackend::do_glActiveBindTexture(Batch& batch, size_t paramOffset) {
glActiveTexture(batch._params[paramOffset + 2]._uint);
glBindTexture(
batch._params[paramOffset + 1]._uint,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._uint),
batch._params[paramOffset + 0]._uint);
(void) CHECK_GL_ERROR();
@ -492,8 +491,6 @@ void Batch::_glUniform1i(GLint location, GLint v0) {
ADD_COMMAND_GL(glUniform1i);
_params.push_back(v0);
_params.push_back(location);
DO_IT_NOW(_glUniform1i, 1);
}
void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -503,7 +500,7 @@ void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform1f(
batch._params[paramOffset + 1]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int),
batch._params[paramOffset + 0]._int);
(void) CHECK_GL_ERROR();
}
@ -515,8 +512,6 @@ void Batch::_glUniform1f(GLint location, GLfloat v0) {
ADD_COMMAND_GL(glUniform1f);
_params.push_back(v0);
_params.push_back(location);
DO_IT_NOW(_glUniform1f, 1);
}
void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -527,7 +522,7 @@ void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) {
updatePipeline();
glUniform1f(
batch._params[paramOffset + 1]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int),
batch._params[paramOffset + 0]._float);
(void) CHECK_GL_ERROR();
}
@ -538,8 +533,6 @@ void Batch::_glUniform2f(GLint location, GLfloat v0, GLfloat v1) {
_params.push_back(v1);
_params.push_back(v0);
_params.push_back(location);
DO_IT_NOW(_glUniform2f, 1);
}
void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) {
@ -550,7 +543,7 @@ void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform2f(
batch._params[paramOffset + 2]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
batch._params[paramOffset + 1]._float,
batch._params[paramOffset + 0]._float);
(void) CHECK_GL_ERROR();
@ -563,8 +556,6 @@ void Batch::_glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {
_params.push_back(v1);
_params.push_back(v0);
_params.push_back(location);
DO_IT_NOW(_glUniform3f, 1);
}
void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) {
@ -575,7 +566,7 @@ void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform3f(
batch._params[paramOffset + 3]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int),
batch._params[paramOffset + 2]._float,
batch._params[paramOffset + 1]._float,
batch._params[paramOffset + 0]._float);
@ -591,8 +582,6 @@ void Batch::_glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLf
_params.push_back(v1);
_params.push_back(v0);
_params.push_back(location);
DO_IT_NOW(_glUniform4f, 1);
}
@ -604,7 +593,7 @@ void GLBackend::do_glUniform4f(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform4f(
batch._params[paramOffset + 4]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 4]._int),
batch._params[paramOffset + 3]._float,
batch._params[paramOffset + 2]._float,
batch._params[paramOffset + 1]._float,
@ -619,8 +608,6 @@ void Batch::_glUniform3fv(GLint location, GLsizei count, const GLfloat* value) {
_params.push_back(cacheData(count * VEC3_SIZE, value));
_params.push_back(count);
_params.push_back(location);
DO_IT_NOW(_glUniform3fv, 3);
}
void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -630,7 +617,7 @@ void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform3fv(
batch._params[paramOffset + 2]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
batch._params[paramOffset + 1]._uint,
(const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint));
@ -645,8 +632,6 @@ void Batch::_glUniform4fv(GLint location, GLsizei count, const GLfloat* value) {
_params.push_back(cacheData(count * VEC4_SIZE, value));
_params.push_back(count);
_params.push_back(location);
DO_IT_NOW(_glUniform4fv, 3);
}
void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -656,7 +641,7 @@ void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) {
}
updatePipeline();
GLint location = batch._params[paramOffset + 2]._int;
GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int);
GLsizei count = batch._params[paramOffset + 1]._uint;
const GLfloat* value = (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint);
glUniform4fv(location, count, value);
@ -671,8 +656,6 @@ void Batch::_glUniform4iv(GLint location, GLsizei count, const GLint* value) {
_params.push_back(cacheData(count * VEC4_SIZE, value));
_params.push_back(count);
_params.push_back(location);
DO_IT_NOW(_glUniform4iv, 3);
}
void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -682,7 +665,7 @@ void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniform4iv(
batch._params[paramOffset + 2]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
batch._params[paramOffset + 1]._uint,
(const GLint*)batch.editData(batch._params[paramOffset + 0]._uint));
@ -697,8 +680,6 @@ void Batch::_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpo
_params.push_back(transpose);
_params.push_back(count);
_params.push_back(location);
DO_IT_NOW(_glUniformMatrix4fv, 4);
}
void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) {
if (_pipeline._program == 0) {
@ -708,7 +689,7 @@ void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) {
}
updatePipeline();
glUniformMatrix4fv(
batch._params[paramOffset + 3]._int,
GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int),
batch._params[paramOffset + 2]._uint,
batch._params[paramOffset + 1]._uint,
(const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint));
@ -722,8 +703,6 @@ void Batch::_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
_params.push_back(blue);
_params.push_back(green);
_params.push_back(red);
DO_IT_NOW(_glColor4f, 4);
}
void GLBackend::do_glColor4f(Batch& batch, size_t paramOffset) {

View file

@ -153,17 +153,42 @@ public:
class GLShader : public GPUObject {
public:
GLuint _shader;
GLuint _program;
enum Version {
Mono = 0,
NumVersions
};
struct ShaderObject {
GLuint glshader{ 0 };
GLuint glprogram{ 0 };
GLint transformCameraSlot{ -1 };
GLint transformObjectSlot{ -1 };
};
using ShaderObjects = std::array< ShaderObject, NumVersions >;
using UniformMapping = std::map<GLint, GLint>;
using UniformMappingVersions = std::vector<UniformMapping>;
GLint _transformCameraSlot = -1;
GLint _transformObjectSlot = -1;
GLShader();
~GLShader();
ShaderObjects _shaderObjects;
UniformMappingVersions _uniformMappings;
GLuint getProgram() const {
return _shaderObjects[Mono].glprogram;
}
GLint getUniformLocation(GLint srcLoc) {
return srcLoc;
// THIS will be used in the next PR
// return _uniformMappings[Mono][srcLoc];
}
};
static GLShader* syncGPUObject(const Shader& shader);
static GLuint getShaderID(const ShaderPointer& shader);
class GLState : public GPUObject {
public:
@ -464,6 +489,7 @@ protected:
PipelinePointer _pipeline;
GLuint _program;
GLShader* _programShader;
bool _invalidProgram;
State::Data _stateCache;
@ -475,6 +501,7 @@ protected:
PipelineStageState() :
_pipeline(),
_program(0),
_programShader(nullptr),
_invalidProgram(false),
_stateCache(State::DEFAULT),
_stateSignatureCache(0),

View file

@ -64,11 +64,15 @@ void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) {
return;
}
// A true new Pipeline
_stats._PSNumSetPipelines++;
// null pipeline == reset
if (!pipeline) {
_pipeline._pipeline.reset();
_pipeline._program = 0;
_pipeline._programShader = nullptr;
_pipeline._invalidProgram = true;
_pipeline._state = nullptr;
@ -80,8 +84,10 @@ void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) {
}
// check the program cache
if (_pipeline._program != pipelineObject->_program->_program) {
_pipeline._program = pipelineObject->_program->_program;
GLuint glprogram = pipelineObject->_program->getProgram();
if (_pipeline._program != glprogram) {
_pipeline._program = glprogram;
_pipeline._programShader = pipelineObject->_program;
_pipeline._invalidProgram = true;
}
@ -142,6 +148,7 @@ void GLBackend::resetPipelineStage() {
// Second the shader side
_pipeline._invalidProgram = false;
_pipeline._program = 0;
_pipeline._programShader = nullptr;
_pipeline._pipeline.reset();
glUseProgram(0);
}

View file

@ -13,27 +13,205 @@
using namespace gpu;
GLBackend::GLShader::GLShader() :
_shader(0),
_program(0)
{}
GLBackend::GLShader::GLShader()
{
}
GLBackend::GLShader::~GLShader() {
if (_shader != 0) {
glDeleteShader(_shader);
}
if (_program != 0) {
glDeleteProgram(_program);
for (auto& so : _shaderObjects) {
if (so.glshader != 0) {
glDeleteShader(so.glshader);
}
if (so.glprogram != 0) {
glDeleteProgram(so.glprogram);
}
}
}
void makeBindings(GLBackend::GLShader* shader) {
if(!shader || !shader->_program) {
bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject) {
if (shaderSource.empty()) {
qCDebug(gpulogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create";
return false;
}
// Create the shader object
GLuint glshader = glCreateShader(shaderDomain);
if (!glshader) {
qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader object";
return nullptr;
}
// Assign the source
const int NUM_SOURCE_STRINGS = 2;
const GLchar* srcstr[] = { defines.c_str(), shaderSource.c_str() };
glShaderSource(glshader, NUM_SOURCE_STRINGS, srcstr, NULL);
// Compile !
glCompileShader(glshader);
// check if shader compiled
GLint compiled = 0;
glGetShaderiv(glshader, GL_COMPILE_STATUS, &compiled);
// if compilation fails
if (!compiled) {
// save the source code to a temp file so we can debug easily
/* std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
}
*/
GLint infoLength = 0;
glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength];
glGetShaderInfoLog(glshader, infoLength, NULL, temp);
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << std::string(temp);
filestream.close();
}
*/
qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:";
for (auto s : srcstr) {
qCWarning(gpulogging) << s;
}
qCWarning(gpulogging) << "GLShader::compileShader - errors:";
qCWarning(gpulogging) << temp;
delete[] temp;
glDeleteShader(glshader);
return false;
}
GLuint glprogram = 0;
#ifdef SEPARATE_PROGRAM
// so far so good, program is almost done, need to link:
GLuint glprogram = glCreateProgram();
if (!glprogram) {
qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader & gl program object";
return false;
}
glProgramParameteri(glprogram, GL_PROGRAM_SEPARABLE, GL_TRUE);
glAttachShader(glprogram, glshader);
glLinkProgram(glprogram);
GLint linked = 0;
glGetProgramiv(glprogram, GL_LINK_STATUS, &linked);
if (!linked) {
/*
// save the source code to a temp file so we can debug easily
std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
}
*/
GLint infoLength = 0;
glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength];
glGetProgramInfoLog(glprogram, infoLength, NULL, temp);
qCDebug(gpulogging) << "GLShader::compileShader - failed to LINK the gl program object :";
qCDebug(gpulogging) << temp;
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << String(temp);
filestream.close();
}
*/
delete[] temp;
glDeleteShader(glshader);
glDeleteProgram(glprogram);
return false;
}
#endif
shaderObject = glshader;
programObject = glprogram;
return true;
}
GLuint compileProgram(const std::vector<GLuint>& glshaders) {
// A brand new program:
GLuint glprogram = glCreateProgram();
if (!glprogram) {
qCDebug(gpulogging) << "GLShader::compileProgram - failed to create the gl program object";
return 0;
}
// glProgramParameteri(glprogram, GL_PROGRAM_, GL_TRUE);
// Create the program from the sub shaders
for (auto so : glshaders) {
glAttachShader(glprogram, so);
}
// Link!
glLinkProgram(glprogram);
GLint linked = 0;
glGetProgramiv(glprogram, GL_LINK_STATUS, &linked);
if (!linked) {
/*
// save the source code to a temp file so we can debug easily
std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
}
*/
GLint infoLength = 0;
glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength];
glGetProgramInfoLog(glprogram, infoLength, NULL, temp);
qCDebug(gpulogging) << "GLShader::compileProgram - failed to LINK the gl program object :";
qCDebug(gpulogging) << temp;
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << std::string(temp);
filestream.close();
}
*/
delete[] temp;
glDeleteProgram(glprogram);
return 0;
}
return glprogram;
}
void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) {
if (!shaderObject.glprogram) {
return;
}
GLuint glprogram = shader->_program;
GLuint glprogram = shaderObject.glprogram;
GLint loc = -1;
//Check for gpu specific attribute slotBindings
loc = glGetAttribLocation(glprogram, "inPosition");
if (loc >= 0 && loc != gpu::Stream::POSITION) {
@ -96,226 +274,111 @@ void makeBindings(GLBackend::GLShader* shader) {
loc = glGetProgramResourceIndex(glprogram, GL_SHADER_STORAGE_BLOCK, "transformObjectBuffer");
if (loc >= 0) {
glShaderStorageBlockBinding(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT);
shader->_transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
shaderObject.transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
}
#else
loc = glGetUniformLocation(glprogram, "transformObjectBuffer");
if (loc >= 0) {
glProgramUniform1i(glprogram, loc, gpu::TRANSFORM_OBJECT_SLOT);
shader->_transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
shaderObject.transformObjectSlot = gpu::TRANSFORM_OBJECT_SLOT;
}
#endif
loc = glGetUniformBlockIndex(glprogram, "transformCameraBuffer");
if (loc >= 0) {
glUniformBlockBinding(glprogram, loc, gpu::TRANSFORM_CAMERA_SLOT);
shader->_transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT;
shaderObject.transformCameraSlot = gpu::TRANSFORM_CAMERA_SLOT;
}
(void)CHECK_GL_ERROR();
}
GLBackend::GLShader* compileShader(const Shader& shader) {
GLBackend::GLShader* compileBackendShader(const Shader& shader) {
// Any GLSLprogram ? normally yes...
const std::string& shaderSource = shader.getSource().getCode();
if (shaderSource.empty()) {
qCDebug(gpulogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create";
return nullptr;
}
// GLSL version
const std::string glslVersion = {
"#version 410 core"
};
// Shader domain
const GLenum SHADER_DOMAINS[2] = { GL_VERTEX_SHADER, GL_FRAGMENT_SHADER };
const int NUM_SHADER_DOMAINS = 2;
const GLenum SHADER_DOMAINS[NUM_SHADER_DOMAINS] = {
GL_VERTEX_SHADER,
GL_FRAGMENT_SHADER
};
GLenum shaderDomain = SHADER_DOMAINS[shader.getType()];
// Create the shader object
GLuint glshader = glCreateShader(shaderDomain);
if (!glshader) {
qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader object";
return nullptr;
}
// Domain specific defines
const std::string domainDefines[NUM_SHADER_DOMAINS] = {
"#define VERTEX_SHADER",
"#define PIXEL_SHADER"
};
// Assign the source
const GLchar* srcstr = shaderSource.c_str();
glShaderSource(glshader, 1, &srcstr, NULL);
// Compile !
glCompileShader(glshader);
// check if shader compiled
GLint compiled = 0;
glGetShaderiv(glshader, GL_COMPILE_STATUS, &compiled);
// Versions specific of the shader
const std::string versionDefines[GLBackend::GLShader::NumVersions] = {
""
};
// if compilation fails
if (!compiled) {
// save the source code to a temp file so we can debug easily
/* std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
GLBackend::GLShader::ShaderObjects shaderObjects;
for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) {
auto& shaderObject = shaderObjects[version];
std::string shaderDefines = glslVersion + "\n" + domainDefines[shader.getType()] + "\n" + versionDefines[version];
bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram);
if (!result) {
return nullptr;
}
*/
GLint infoLength = 0;
glGetShaderiv(glshader, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength] ;
glGetShaderInfoLog(glshader, infoLength, NULL, temp);
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << std::string(temp);
filestream.close();
}
*/
qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:";
qCWarning(gpulogging) << srcstr;
qCWarning(gpulogging) << "GLShader::compileShader - errors:";
qCWarning(gpulogging) << temp;
delete[] temp;
glDeleteShader(glshader);
return nullptr;
}
GLuint glprogram = 0;
#ifdef SEPARATE_PROGRAM
// so far so good, program is almost done, need to link:
GLuint glprogram = glCreateProgram();
if (!glprogram) {
qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader & gl program object";
return nullptr;
}
glProgramParameteri(glprogram, GL_PROGRAM_SEPARABLE, GL_TRUE);
glAttachShader(glprogram, glshader);
glLinkProgram(glprogram);
GLint linked = 0;
glGetProgramiv(glprogram, GL_LINK_STATUS, &linked);
if (!linked) {
/*
// save the source code to a temp file so we can debug easily
std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
}
*/
GLint infoLength = 0;
glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength] ;
glGetProgramInfoLog(glprogram, infoLength, NULL, temp);
qCDebug(gpulogging) << "GLShader::compileShader - failed to LINK the gl program object :";
qCDebug(gpulogging) << temp;
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << String(temp);
filestream.close();
}
*/
delete[] temp;
glDeleteShader(glshader);
glDeleteProgram(glprogram);
return nullptr;
}
#endif
// So far so good, the shader is created successfully
GLBackend::GLShader* object = new GLBackend::GLShader();
object->_shader = glshader;
object->_program = glprogram;
makeBindings(object);
object->_shaderObjects = shaderObjects;
return object;
}
GLBackend::GLShader* compileProgram(const Shader& program) {
if(!program.isProgram()) {
GLBackend::GLShader* compileBackendProgram(const Shader& program) {
if (!program.isProgram()) {
return nullptr;
}
// Let's go through every shaders and make sure they are ready to go
std::vector< GLuint > shaderObjects;
for (auto subShader : program.getShaders()) {
GLuint so = GLBackend::getShaderID(subShader);
if (!so) {
qCDebug(gpulogging) << "GLShader::compileProgram - One of the shaders of the program is not compiled?";
GLBackend::GLShader::ShaderObjects programObjects;
for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) {
auto& programObject = programObjects[version];
// Let's go through every shaders and make sure they are ready to go
std::vector< GLuint > shaderGLObjects;
for (auto subShader : program.getShaders()) {
auto object = GLBackend::syncGPUObject(*subShader);
if (object) {
shaderGLObjects.push_back(object->_shaderObjects[version].glshader);
} else {
qCDebug(gpulogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?";
return nullptr;
}
}
GLuint glprogram = compileProgram(shaderGLObjects);
if (glprogram == 0) {
return nullptr;
}
shaderObjects.push_back(so);
programObject.glprogram = glprogram;
makeProgramBindings(programObject);
}
// so far so good, program is almost done, need to link:
GLuint glprogram = glCreateProgram();
if (!glprogram) {
qCDebug(gpulogging) << "GLShader::compileProgram - failed to create the gl program object";
return nullptr;
}
// glProgramParameteri(glprogram, GL_PROGRAM_, GL_TRUE);
// Create the program from the sub shaders
for (auto so : shaderObjects) {
glAttachShader(glprogram, so);
}
// Link!
glLinkProgram(glprogram);
GLint linked = 0;
glGetProgramiv(glprogram, GL_LINK_STATUS, &linked);
if (!linked) {
/*
// save the source code to a temp file so we can debug easily
std::ofstream filestream;
filestream.open("debugshader.glsl");
if (filestream.is_open()) {
filestream << shaderSource->source;
filestream.close();
}
*/
GLint infoLength = 0;
glGetProgramiv(glprogram, GL_INFO_LOG_LENGTH, &infoLength);
char* temp = new char[infoLength] ;
glGetProgramInfoLog(glprogram, infoLength, NULL, temp);
qCDebug(gpulogging) << "GLShader::compileProgram - failed to LINK the gl program object :";
qCDebug(gpulogging) << temp;
/*
filestream.open("debugshader.glsl.info.txt");
if (filestream.is_open()) {
filestream << std::string(temp);
filestream.close();
}
*/
delete[] temp;
glDeleteProgram(glprogram);
return nullptr;
}
// So far so good, the program is created successfully
// So far so good, the program versions have all been created successfully
GLBackend::GLShader* object = new GLBackend::GLShader();
object->_shader = 0;
object->_program = glprogram;
makeBindings(object);
object->_shaderObjects = programObjects;
return object;
}
@ -329,14 +392,14 @@ GLBackend::GLShader* GLBackend::syncGPUObject(const Shader& shader) {
}
// need to have a gpu object?
if (shader.isProgram()) {
GLShader* tempObject = compileProgram(shader);
if (tempObject) {
GLShader* tempObject = compileBackendProgram(shader);
if (tempObject) {
object = tempObject;
Backend::setGPUObject(shader, object);
}
} else if (shader.isDomain()) {
GLShader* tempObject = compileShader(shader);
if (tempObject) {
GLShader* tempObject = compileBackendShader(shader);
if (tempObject) {
object = tempObject;
Backend::setGPUObject(shader, object);
}
@ -345,23 +408,6 @@ GLBackend::GLShader* GLBackend::syncGPUObject(const Shader& shader) {
return object;
}
GLuint GLBackend::getShaderID(const ShaderPointer& shader) {
if (!shader) {
return 0;
}
GLShader* object = GLBackend::syncGPUObject(*shader);
if (object) {
if (shader->isProgram()) {
return object->_program;
} else {
return object->_shader;
}
} else {
return 0;
}
}
class ElementResource {
public:
gpu::Element _element;
@ -714,27 +760,38 @@ bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindin
return false;
}
if (object->_program) {
Shader::SlotSet buffers;
makeUniformBlockSlots(object->_program, slotBindings, buffers);
// Apply bindings to all program versions and generate list of slots from default version
for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) {
auto& shaderObject = object->_shaderObjects[version];
if (shaderObject.glprogram) {
Shader::SlotSet buffers;
makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers);
Shader::SlotSet uniforms;
Shader::SlotSet textures;
Shader::SlotSet samplers;
makeUniformSlots(object->_program, slotBindings, uniforms, textures, samplers);
Shader::SlotSet inputs;
makeInputSlots(object->_program, slotBindings, inputs);
Shader::SlotSet uniforms;
Shader::SlotSet textures;
Shader::SlotSet samplers;
makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers);
Shader::SlotSet outputs;
makeOutputSlots(object->_program, slotBindings, outputs);
Shader::SlotSet inputs;
makeInputSlots(shaderObject.glprogram, slotBindings, inputs);
shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs);
} else if (object->_shader) {
Shader::SlotSet outputs;
makeOutputSlots(shaderObject.glprogram, slotBindings, outputs);
// Define the public slots only from the default version
if (version == 0) {
shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs);
} else {
GLShader::UniformMapping mapping;
for (auto srcUniform : shader.getUniforms()) {
mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name);
}
object->_uniformMappings.push_back(mapping);
}
}
}
return true;
}

View file

@ -325,3 +325,26 @@ Buffer::Size Buffer::append(Size size, const Byte* data) {
return newSize;
}
const Element BufferView::DEFAULT_ELEMENT = Element( gpu::SCALAR, gpu::UINT8, gpu::RAW );
BufferView::BufferView() :
BufferView(DEFAULT_ELEMENT) {}
BufferView::BufferView(const Element& element) :
BufferView(BufferPointer(), element) {}
BufferView::BufferView(Buffer* newBuffer, const Element& element) :
BufferView(BufferPointer(newBuffer), element) {}
BufferView::BufferView(const BufferPointer& buffer, const Element& element) :
BufferView(buffer, DEFAULT_OFFSET, buffer ? buffer->getSize() : 0, element.getSize(), element) {}
BufferView::BufferView(const BufferPointer& buffer, Size offset, Size size, const Element& element) :
BufferView(buffer, offset, size, element.getSize(), element) {}
BufferView::BufferView(const BufferPointer& buffer, Size offset, Size size, uint16 stride, const Element& element) :
_buffer(buffer),
_offset(offset),
_size(size),
_element(element),
_stride(stride) {}

View file

@ -174,73 +174,30 @@ typedef std::vector< BufferPointer > Buffers;
class BufferView {
protected:
void initFromBuffer(const BufferPointer& buffer) {
_buffer = (buffer);
if (_buffer) {
_size = (buffer->getSize());
}
}
protected:
static const Resource::Size DEFAULT_OFFSET{ 0 };
static const Element DEFAULT_ELEMENT;
public:
typedef Resource::Size Size;
typedef int Index;
using Size = Resource::Size;
using Index = int;
BufferPointer _buffer;
Size _offset{ 0 };
Size _size{ 0 };
Size _offset;
Size _size;
Element _element;
uint16 _stride{ 1 };
uint16 _stride;
BufferView() :
_buffer(NULL),
_offset(0),
_size(0),
_element(gpu::SCALAR, gpu::UINT8, gpu::RAW),
_stride(1)
{};
BufferView(const Element& element) :
_buffer(NULL),
_offset(0),
_size(0),
_element(element),
_stride(uint16(element.getSize()))
{};
// create the BufferView and own the Buffer
BufferView(Buffer* newBuffer, const Element& element = Element(gpu::SCALAR, gpu::UINT8, gpu::RAW)) :
_offset(0),
_element(element),
_stride(uint16(element.getSize()))
{
initFromBuffer(BufferPointer(newBuffer));
};
BufferView(const BufferPointer& buffer, const Element& element = Element(gpu::SCALAR, gpu::UINT8, gpu::RAW)) :
_offset(0),
_element(element),
_stride(uint16(element.getSize()))
{
initFromBuffer(buffer);
};
BufferView(const BufferPointer& buffer, Size offset, Size size, const Element& element = Element(gpu::SCALAR, gpu::UINT8, gpu::RAW)) :
_buffer(buffer),
_offset(offset),
_size(size),
_element(element),
_stride(uint16(element.getSize()))
{};
BufferView(const BufferPointer& buffer, Size offset, Size size, uint16 stride, const Element& element = Element(gpu::SCALAR, gpu::UINT8, gpu::RAW)) :
_buffer(buffer),
_offset(offset),
_size(size),
_element(element),
_stride(stride)
{};
~BufferView() {}
BufferView(const BufferView& view) = default;
BufferView& operator=(const BufferView& view) = default;
BufferView();
BufferView(const Element& element);
BufferView(Buffer* newBuffer, const Element& element = DEFAULT_ELEMENT);
BufferView(const BufferPointer& buffer, const Element& element = DEFAULT_ELEMENT);
BufferView(const BufferPointer& buffer, Size offset, Size size, const Element& element = DEFAULT_ELEMENT);
BufferView(const BufferPointer& buffer, Size offset, Size size, uint16 stride, const Element& element = DEFAULT_ELEMENT);
Size getNumElements() const { return _size / _element.getSize(); }
//Template iterator with random access on the buffer sysmem

View file

@ -155,9 +155,12 @@ void ResourceCache::clearATPAssets() {
}
{
QWriteLocker locker(&_resourcesToBeGottenLock);
for (auto& url : _resourcesToBeGotten) {
if (url.scheme() == URL_SCHEME_ATP) {
_resourcesToBeGotten.removeAll(url);
auto it = _resourcesToBeGotten.begin();
while (it != _resourcesToBeGotten.end()) {
if (it->scheme() == URL_SCHEME_ATP) {
it = _resourcesToBeGotten.erase(it);
} else {
++it;
}
}
}

View file

@ -329,10 +329,21 @@ void SendQueue::run() {
auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod;
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
// sleep as long as we need until next packet send, if we can
// sleep as long as we need for next packet send, if we can
auto now = p_high_resolution_clock::now();
auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - now);
// we use nextPacketTimestamp so that we don't fall behind, not to force long sleeps
// we'll never allow nextPacketTimestamp to force us to sleep for more than nextPacketDelta
// so cap it to that value
if (timeToSleep > std::chrono::microseconds(nextPacketDelta)) {
// reset the nextPacketTimestamp so that it is correct next time we come around
nextPacketTimestamp = now + std::chrono::microseconds(nextPacketDelta);
timeToSleep = std::chrono::microseconds(nextPacketDelta);
}
// we're seeing SendQueues sleep for a long period of time here,
// which can lock the NodeList if it's attempting to clear connections
// for now we guard this by capping the time this thread and sleep for

View file

@ -72,6 +72,17 @@ CharacterController::CharacterController() {
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
}
CharacterController::~CharacterController() {
if (_rigidBody) {
btCollisionShape* shape = _rigidBody->getCollisionShape();
if (shape) {
delete shape;
}
delete _rigidBody;
_rigidBody = nullptr;
}
}
bool CharacterController::needsRemoval() const {
return ((_pendingFlags & PENDING_FLAG_REMOVE_FROM_SIMULATION) == PENDING_FLAG_REMOVE_FROM_SIMULATION);
}

View file

@ -36,7 +36,7 @@ class btDynamicsWorld;
class CharacterController : public btCharacterControllerInterface {
public:
CharacterController();
virtual ~CharacterController() {}
virtual ~CharacterController();
bool needsRemoval() const;
bool needsAddition() const;

View file

@ -105,6 +105,12 @@ public:
return aspect(getRecommendedRenderSize());
}
// The recommended bounds for primary overlay placement
virtual QRect getRecommendedOverlayRect() const {
auto recommendedSize = getRecommendedUiSize();
return QRect(0, 0, recommendedSize.x, recommendedSize.y);
}
// Stereo specific methods
virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
return baseProjection;

View file

@ -46,6 +46,7 @@ const float DEFAULT_METALLIC = 0;
const vec3 DEFAULT_SPECULAR = vec3(0.1);
const vec3 DEFAULT_EMISSIVE = vec3(0.0);
const float DEFAULT_OCCLUSION = 1.0;
const vec3 DEFAULT_FRESNEL = DEFAULT_EMISSIVE;
void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion) {

View file

@ -90,6 +90,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu
// Specular highlight from ambient
vec3 direction = -reflect(fragEyeDir, fragNormal);
float levels = getLightAmbientMapNumMips(light);
float lod = min(floor((roughness) * levels), levels);
vec4 skyboxLight = evalSkyboxLight(direction, lod);

View file

@ -96,7 +96,7 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
addJob<PrepareDeferred>("PrepareDeferred");
// Render opaque objects in DeferredBuffer
addJob<DrawDeferred>("DrawOpaqueDeferred", opaques, shapePlumber);
addJob<DrawStateSortDeferred>("DrawOpaqueDeferred", opaques, shapePlumber);
// Once opaque is all rendered create stencil background
addJob<DrawStencilDeferred>("DrawOpaqueStencil");
@ -185,9 +185,6 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
config->setNumDrawn((int)inItems.size());
emit config->numDrawnChanged();
glm::mat4 projMat;
Transform viewMat;
args->_viewFrustum->evalProjectionMatrix(projMat);
@ -199,6 +196,40 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
args->_batch = nullptr;
});
config->setNumDrawn((int)inItems.size());
}
void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);
auto config = std::static_pointer_cast<Config>(renderContext->jobConfig);
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
args->_batch = &batch;
batch.setViewportTransform(args->_viewport);
batch.setStateScissorRect(args->_viewport);
glm::mat4 projMat;
Transform viewMat;
args->_viewFrustum->evalProjectionMatrix(projMat);
args->_viewFrustum->evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
if (_stateSort) {
renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
} else {
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
}
args->_batch = nullptr;
});
config->setNumDrawn((int)inItems.size());
}
DrawOverlay3D::DrawOverlay3D(bool opaque) :

View file

@ -22,6 +22,7 @@ public:
using JobModel = render::Job::Model<SetupDeferred>;
};
class PrepareDeferred {
public:
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
@ -29,32 +30,31 @@ public:
using JobModel = render::Job::Model<PrepareDeferred>;
};
class RenderDeferred {
public:
using JobModel = render::Job::Model<RenderDeferred>;
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
};
class DrawConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY newStats)
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
public:
int getNumDrawn() { return numDrawn; }
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
int getNumDrawn() { return _numDrawn; }
void setNumDrawn(int numDrawn) { _numDrawn = numDrawn; emit newStats(); }
int maxDrawn{ -1 };
signals:
void numDrawnChanged();
void newStats();
void dirty();
protected:
int numDrawn{ 0 };
int _numDrawn{ 0 };
};
class DrawDeferred {
@ -72,6 +72,44 @@ protected:
int _maxDrawn; // initialized by Config
};
class DrawStateSortConfig : public render::Job::Config {
Q_OBJECT
Q_PROPERTY(int numDrawn READ getNumDrawn NOTIFY numDrawnChanged)
Q_PROPERTY(int maxDrawn MEMBER maxDrawn NOTIFY dirty)
Q_PROPERTY(bool stateSort MEMBER stateSort NOTIFY dirty)
public:
int getNumDrawn() { return numDrawn; }
void setNumDrawn(int num) { numDrawn = num; emit numDrawnChanged(); }
int maxDrawn{ -1 };
bool stateSort{ true };
signals:
void numDrawnChanged();
void dirty();
protected:
int numDrawn{ 0 };
};
class DrawStateSortDeferred {
public:
using Config = DrawStateSortConfig;
using JobModel = render::Job::ModelI<DrawStateSortDeferred, render::ItemBounds, Config>;
DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
void configure(const Config& config) { _maxDrawn = config.maxDrawn; _stateSort = config.stateSort; }
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
protected:
render::ShapePlumberPointer _shapePlumber;
int _maxDrawn; // initialized by Config
bool _stateSort;
};
class DrawStencilDeferred {
public:
using JobModel = render::Job::Model<DrawStencilDeferred>;

View file

@ -27,7 +27,7 @@ ToneMappingEffect::ToneMappingEffect() {
}
void ToneMappingEffect::init() {
const char BlitTextureGamma_frag[] = R"SCRIBE(#version 410 core
const char BlitTextureGamma_frag[] = R"SCRIBE(
// Generated on Sat Oct 24 09:34:37 2015
//
// Draw texture 0 fetched at texcoord.xy

View file

@ -41,20 +41,15 @@ void main() {
float w = clamp( s, 0.0, 0.5);
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
// gamma correction for linear attenuation
a = pow(a, 1.0 / gamma);
// discard if unvisible
if (a < 0.01) {
discard;
}
packDeferredFragmentLightmap(
normalize(_normal),
1.0,
vec3(1.0),
DEFAULT_ROUGHNESS,
DEFAULT_METALLIC,
DEFAULT_SPECULAR,
Color.rgb);
packDeferredFragmentTranslucent(
normalize(_normal),
a,
Color.rgb,
DEFAULT_FRESNEL,
DEFAULT_ROUGHNESS);
}

View file

@ -65,6 +65,57 @@ void render::renderShapes(const SceneContextPointer& sceneContext, const RenderC
}
}
void render::renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems) {
auto& scene = sceneContext->_scene;
RenderArgs* args = renderContext->args;
int numItemsToDraw = (int)inItems.size();
if (maxDrawnItems != -1) {
numItemsToDraw = glm::min(numItemsToDraw, maxDrawnItems);
}
using SortedPipelines = std::vector<render::ShapeKey>;
using SortedShapes = std::unordered_map<render::ShapeKey, std::vector<Item>, render::ShapeKey::Hash, render::ShapeKey::KeyEqual>;
SortedPipelines sortedPipelines;
SortedShapes sortedShapes;
std::vector<Item> ownPipelineBucket;
for (auto i = 0; i < numItemsToDraw; ++i) {
auto item = scene->getItem(inItems[i].id);
{
assert(item.getKey().isShape());
const auto& key = item.getShapeKey();
if (key.isValid() && !key.hasOwnPipeline()) {
auto& bucket = sortedShapes[key];
if (bucket.empty()) {
sortedPipelines.push_back(key);
}
bucket.push_back(item);
} else if (key.hasOwnPipeline()) {
ownPipelineBucket.push_back(item);
} else {
qDebug() << "Item could not be rendered: invalid key ?" << key;
}
}
}
// Then render
for (auto& pipelineKey : sortedPipelines) {
auto& bucket = sortedShapes[pipelineKey];
args->_pipeline = shapeContext->pickPipeline(args, pipelineKey);
for (auto& item : bucket) {
item.render(args);
}
}
args->_pipeline = nullptr;
for (auto& item : ownPipelineBucket) {
item.render(args);
}
}
void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inLights) {
assert(renderContext->args);
assert(renderContext->args->_viewFrustum);

View file

@ -18,6 +18,7 @@ namespace render {
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems = -1);
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);

View file

@ -49,5 +49,7 @@ void EngineStats::run(const SceneContextPointer& sceneContext, const RenderConte
config->frameTextureRate = config->frameTextureCount * frequency;
config->frameTextureMemoryUsage = _gpuStats._RSAmountTextureMemoryBounded - gpuStats._RSAmountTextureMemoryBounded;
config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines - gpuStats._PSNumSetPipelines;
config->emitDirty();
}

View file

@ -47,6 +47,9 @@ namespace render {
Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty)
Q_PROPERTY(quint32 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY dirty)
Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY dirty)
public:
EngineStatsConfig() : Job::Config(true) {}
@ -73,6 +76,10 @@ namespace render {
quint32 frameTextureRate{ 0 };
qint64 frameTextureMemoryUsage{ 0 };
quint32 frameSetPipelineCount{ 0 };
void emitDirty() { emit dirty(); }
signals:

View file

@ -127,6 +127,9 @@ protected:
// A default Config is always on; to create an enableable Config, use the ctor JobConfig(bool enabled)
class JobConfig : public QObject {
Q_OBJECT
Q_PROPERTY(quint64 cpuRunTime READ getCPUTRunTime NOTIFY newStats())
quint64 _CPURunTime{ 0 };
public:
using Persistent = PersistentConfig<JobConfig>;
@ -151,11 +154,17 @@ public:
Q_INVOKABLE QString toJSON() { return QJsonDocument(toJsonValue(*this).toObject()).toJson(QJsonDocument::Compact); }
Q_INVOKABLE void load(const QVariantMap& map) { qObjectFromJsonValue(QJsonObject::fromVariantMap(map), *this); emit loaded(); }
// Running Time measurement
// The new stats signal is emitted once per run time of a job when stats (cpu runtime) are updated
void setCPURunTime(quint64 ustime) { _CPURunTime = ustime; emit newStats(); }
quint64 getCPUTRunTime() const { return _CPURunTime; }
public slots:
void load(const QJsonObject& val) { qObjectFromJsonValue(val, *this); emit loaded(); }
signals:
void loaded();
void newStats();
};
class TaskConfig : public JobConfig {
@ -223,7 +232,11 @@ public:
virtual void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) = 0;
protected:
void setCPURunTime(quint64 ustime) { std::static_pointer_cast<Config>(_config)->setCPURunTime(ustime); }
QConfigPointer _config;
friend class Job;
};
using ConceptPointer = std::shared_ptr<Concept>;
@ -278,8 +291,11 @@ public:
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
PerformanceTimer perfTimer(_name.c_str());
PROFILE_RANGE(_name.c_str());
auto start = usecTimestampNow();
_concept->run(sceneContext, renderContext);
_concept->setCPURunTime(usecTimestampNow() - start);
}
protected:

View file

@ -18,6 +18,7 @@
#include <QtNetwork/QNetworkReply>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptValueIterator>
#include <QtCore/QStringList>
#include <AudioConstants.h>
@ -143,7 +144,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
ScriptEngine::~ScriptEngine() {
qCDebug(scriptengine) << "Script Engine shutting down (destructor) for script:" << getFilename();
auto scriptEngines = DependencyManager::get<ScriptEngines>();
if (scriptEngines) {
scriptEngines->removeScriptEngine(this);
@ -1047,39 +1047,25 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
// since all of these operations can be asynch we will always do the actual work in the response handler
// for the download
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
if (QThread::currentThread() != thread()) {
void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
// NOTE: If the script content is not currently in the cache, the LAMBDA here will be called on the Main Thread
// which means we're guaranteed that it's not the correct thread for the ScriptEngine. This means
// when we get into entityScriptContentAvailable() we will likely invokeMethod() to get it over
// to the "Entities" ScriptEngine thread.
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [theEngine, entityID](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
QSharedPointer<ScriptEngine> strongEngine = theEngine.toStrongRef();
if (strongEngine) {
#ifdef THREAD_DEBUGGING
qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread ["
<< QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
"entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload;
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread ["
<< QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]";
#endif
QMetaObject::invokeMethod(this, "loadEntityScript",
Q_ARG(const EntityItemID&, entityID),
Q_ARG(const QString&, entityScript),
Q_ARG(bool, forceRedownload));
return;
}
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::loadEntityScript() called on correct thread [" << thread() << "] "
"entityID:" << entityID << "entityScript:" << entityScript << "forceRedownload:" << forceRedownload;
#endif
// If we've been called our known entityScripts should not know about us..
assert(!_entityScripts.contains(entityID));
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::loadEntityScript() calling scriptCache->getScriptContents() on thread ["
<< QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
DependencyManager::get<ScriptCache>()->getScriptContents(entityScript, [=](const QString& scriptOrURL, const QString& contents, bool isURL, bool success) {
#ifdef THREAD_DEBUGGING
qDebug() << "ScriptEngine::entityScriptContentAvailable() IN LAMBDA contentAvailable on thread ["
<< QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
this->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
} else {
// FIXME - I'm leaving this in for testing, so that QA can confirm that sometimes the script contents
// returns after the ScriptEngine has been deleted, we can remove this after QA verifies the
// repro case.
qDebug() << "ScriptCache::getScriptContents() returned after our ScriptEngine was deleted... script:" << scriptOrURL;
}
}, forceRedownload);
}
@ -1213,6 +1199,16 @@ void ScriptEngine::unloadAllEntityScripts() {
callEntityScriptMethod(entityID, "unload");
}
_entityScripts.clear();
#ifdef DEBUG_ENGINE_STATE
qDebug() << "---- CURRENT STATE OF ENGINE: --------------------------";
QScriptValueIterator it(globalObject());
while (it.hasNext()) {
it.next();
qDebug() << it.name() << ":" << it.value().toString();
}
qDebug() << "--------------------------------------------------------";
#endif // DEBUG_ENGINE_STATE
}
void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {

View file

@ -124,7 +124,7 @@ public:
Q_INVOKABLE QUrl resolvePath(const QString& path) const;
// Entity Script Related methods
Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload = false); // will call the preload method once loaded
static void loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload);
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method
Q_INVOKABLE void unloadAllEntityScripts();
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList());

View file

@ -22,12 +22,7 @@
#define __STR1__(x) __STR2__(x)
#define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning Msg: "
#ifndef __APPLE__
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
#else
// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/script.js");
#endif
ScriptsModel& getScriptsModel() {
static ScriptsModel scriptsModel;

View file

@ -55,8 +55,11 @@ public:
}
~Counter() { log(); }
// Increase the count for key.
void add(const K& key);
// Increase the count for key (by inc).
void add(const K& key, size_t inc = 1);
// Decrease the count for key (by dec).
void sub(const K& key, size_t dec = 1);
// Log current counts (called on destruction).
void log();
@ -123,20 +126,25 @@ private:
};
template<class K>
void Counter<K>::add(const K& k) {
void Counter<K>::add(const K& k, size_t inc) {
std::lock_guard<std::mutex> lock(_mutex);
auto& it = _map.find(k);
if (it == _map.end()) {
// No entry for k; add it
_map.insert(std::pair<K, size_t>(k, 1));
_map.insert(std::pair<K, size_t>(k, inc));
} else {
// Entry for k; update it
it->second++;
it->second += inc;
}
}
template<class K>
void Counter<K>::sub(const K& k, size_t dec) {
add(k, -dec);
}
template <class K>
void Counter<K>::log() {
// Avoid logging nothing

View file

@ -34,7 +34,7 @@ float PIDController::update(float measuredValue, float dt, bool resetAccumulator
if (getIsLogging()) { // if logging/reporting
updateHistory(measuredValue, dt, error, accumulatedError, changeInError, p, i, d, computedValue);
}
Q_ASSERT(!isnan(computedValue));
Q_ASSERT(!glm::isnan(computedValue));
// update state for next time
_lastError = error;
@ -75,4 +75,4 @@ void PIDController::reportHistory() {
qCDebug(shared) << "Limits: setpoint" << getMeasuredValueSetpoint() << "accumulate" << getAccumulatedValueLowLimit() << getAccumulatedValueHighLimit() <<
"controlled" << getControlledValueLowLimit() << getControlledValueHighLimit() <<
"kp/ki/kd" << getKP() << getKI() << getKD();
}
}

View file

@ -128,7 +128,7 @@ void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) {
vec3.z = object.property("z").toVariant().toFloat();
}
QVariant vec3toVariant(const glm::vec3 &vec3) {
QVariant vec3toVariant(const glm::vec3& vec3) {
if (vec3.x != vec3.x || vec3.y != vec3.y || vec3.z != vec3.z) {
// if vec3 contains a NaN don't try to convert it
return QVariant();
@ -140,6 +140,18 @@ QVariant vec3toVariant(const glm::vec3 &vec3) {
return result;
}
QVariant vec4toVariant(const glm::vec4& vec4) {
if (isNaN(vec4.x) || isNaN(vec4.y) || isNaN(vec4.z) || isNaN(vec4.w)) {
// if vec4 contains a NaN don't try to convert it
return QVariant();
}
QVariantMap result;
result["x"] = vec4.x;
result["y"] = vec4.y;
result["z"] = vec4.z;
result["w"] = vec4.w;
return result;
}
QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector<glm::vec3>& vector) {
QScriptValue array = engine->newArray();
@ -150,7 +162,7 @@ QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector<glm::
}
glm::vec3 vec3FromVariant(const QVariant &object, bool& valid) {
glm::vec3 vec3FromVariant(const QVariant& object, bool& valid) {
glm::vec3 v;
valid = false;
if (!object.isValid() || object.isNull()) {
@ -189,12 +201,49 @@ glm::vec3 vec3FromVariant(const QVariant &object, bool& valid) {
return v;
}
glm::vec3 vec3FromVariant(const QVariant &object) {
glm::vec3 vec3FromVariant(const QVariant& object) {
bool valid = false;
return vec3FromVariant(object, valid);
}
QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat &quat) {
glm::vec4 vec4FromVariant(const QVariant& object, bool& valid) {
glm::vec4 v;
valid = false;
if (!object.isValid() || object.isNull()) {
return v;
} else if (object.canConvert<float>()) {
v = glm::vec4(object.toFloat());
valid = true;
} else if (object.canConvert<QVector4D>()) {
auto qvec4 = qvariant_cast<QVector4D>(object);
v.x = qvec4.x();
v.y = qvec4.y();
v.z = qvec4.z();
v.w = qvec4.w();
valid = true;
} else {
auto map = object.toMap();
auto x = map["x"];
auto y = map["y"];
auto z = map["z"];
auto w = map["w"];
if (x.canConvert<float>() && y.canConvert<float>() && z.canConvert<float>() && w.canConvert<float>()) {
v.x = x.toFloat();
v.y = y.toFloat();
v.z = z.toFloat();
v.w = w.toFloat();
valid = true;
}
}
return v;
}
glm::vec4 vec4FromVariant(const QVariant& object) {
bool valid = false;
return vec4FromVariant(object, valid);
}
QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat) {
QScriptValue obj = engine->newObject();
if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) {
// if quat contains a NaN don't try to convert it
@ -207,7 +256,7 @@ QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat &quat) {
return obj;
}
void quatFromScriptValue(const QScriptValue &object, glm::quat &quat) {
void quatFromScriptValue(const QScriptValue& object, glm::quat &quat) {
quat.x = object.property("x").toVariant().toFloat();
quat.y = object.property("y").toVariant().toFloat();
quat.z = object.property("z").toVariant().toFloat();
@ -245,12 +294,12 @@ glm::quat quatFromVariant(const QVariant &object, bool& isValid) {
return q;
}
glm::quat quatFromVariant(const QVariant &object) {
glm::quat quatFromVariant(const QVariant& object) {
bool valid = false;
return quatFromVariant(object, valid);
}
QVariant quatToVariant(const glm::quat &quat) {
QVariant quatToVariant(const glm::quat& quat) {
if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z) {
// if vec3 contains a NaN don't try to convert it
return QVariant();

View file

@ -43,12 +43,15 @@ void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
// Vec4
QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4);
void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4);
QVariant vec4toVariant(const glm::vec4& vec4);
glm::vec4 vec4FromVariant(const QVariant &object, bool& valid);
glm::vec4 vec4FromVariant(const QVariant &object);
// Vec3
QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3);
void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3);
QVariant vec3toVariant(const glm::vec3 &vec3);
QVariant vec3toVariant(const glm::vec3& vec3);
glm::vec3 vec3FromVariant(const QVariant &object, bool& valid);
glm::vec3 vec3FromVariant(const QVariant &object);
@ -71,9 +74,10 @@ glm::quat quatFromVariant(const QVariant &object);
// Rect
QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect);
void qRectFromScriptValue(const QScriptValue& object, QRect& rect);
QVariant qRectToVariant(const QRect& rect);
QRect qRectFromVariant(const QVariant& object, bool& isValid);
QRect qRectFromVariant(const QVariant& object);
QVariant qRectToVariant(const QRect& rect);
// xColor
QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color);

View file

@ -18,7 +18,7 @@ void Settings::getFloatValueIfValid(const QString& name, float& floatValue) {
const QVariant badDefaultValue = NAN;
bool ok = true;
float tempFloat = value(name, badDefaultValue).toFloat(&ok);
if (ok && !isnan(tempFloat)) {
if (ok && !glm::isnan(tempFloat)) {
floatValue = tempFloat;
}
}
@ -47,7 +47,7 @@ void Settings::getVec3ValueIfValid(const QString& name, glm::vec3& vecValue) {
float x = value(QString("x"), badDefaultValue).toFloat(&ok);
float y = value(QString("y"), badDefaultValue).toFloat(&ok);
float z = value(QString("z"), badDefaultValue).toFloat(&ok);
if (ok && (!isnan(x) && !isnan(y) && !isnan(z))) {
if (ok && (!glm::isnan(x) && !glm::isnan(y) && !glm::isnan(z))) {
vecValue = glm::vec3(x, y, z);
}
}
@ -74,7 +74,7 @@ void Settings::getQuatValueIfValid(const QString& name, glm::quat& quatValue) {
float y = value(QString("y"), badDefaultValue).toFloat(&ok);
float z = value(QString("z"), badDefaultValue).toFloat(&ok);
float w = value(QString("w"), badDefaultValue).toFloat(&ok);
if (ok && (!isnan(x) && !isnan(y) && !isnan(z) && !isnan(w))) {
if (ok && (!glm::isnan(x) && !glm::isnan(y) && !glm::isnan(z) && !glm::isnan(w))) {
quatValue = glm::quat(w, x, y, z);
}
}