mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge branch 'master' of github.com:highfidelity/hifi into discovery_appUi
This commit is contained in:
commit
9e50904376
67 changed files with 1103 additions and 853 deletions
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
@ -218,6 +219,10 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node
|
|||
}
|
||||
|
||||
void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) {
|
||||
ignoreOther(self.data(), other.data());
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) {
|
||||
if (!isRadiusIgnoring(other->getUUID())) {
|
||||
addToRadiusIgnoringSet(other->getUUID());
|
||||
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
|
||||
|
@ -235,9 +240,20 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) {
|
||||
if (isRadiusIgnoring(other)) {
|
||||
_radiusIgnoredOthers.erase(other);
|
||||
bool AvatarMixerClientData::isRadiusIgnoring(const QUuid& other) const {
|
||||
return std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other) != _radiusIgnoredOthers.cend();
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::addToRadiusIgnoringSet(const QUuid& other) {
|
||||
if (!isRadiusIgnoring(other)) {
|
||||
_radiusIgnoredOthers.push_back(other);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) {
|
||||
auto ignoredOtherIter = std::find(_radiusIgnoredOthers.cbegin(), _radiusIgnoredOthers.cend(), other);
|
||||
if (ignoredOtherIter != _radiusIgnoredOthers.cend()) {
|
||||
_radiusIgnoredOthers.erase(ignoredOtherIter);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
@ -45,6 +45,7 @@ public:
|
|||
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
AvatarData& getAvatar() { return *_avatar; }
|
||||
const AvatarData& getAvatar() const { return *_avatar; }
|
||||
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
|
||||
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
||||
|
||||
|
@ -90,11 +91,11 @@ public:
|
|||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
|
||||
glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); }
|
||||
glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
|
||||
bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
|
||||
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
|
||||
void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other);
|
||||
bool isRadiusIgnoring(const QUuid& other) const;
|
||||
void addToRadiusIgnoringSet(const QUuid& other);
|
||||
void removeFromRadiusIgnoringSet(const QUuid& other);
|
||||
void ignoreOther(SharedNodePointer self, SharedNodePointer other);
|
||||
void ignoreOther(const Node* self, const Node* other);
|
||||
|
||||
void readViewFrustumPacket(const QByteArray& message);
|
||||
|
||||
|
@ -166,7 +167,7 @@ private:
|
|||
int _numOutOfOrderSends = 0;
|
||||
|
||||
SimpleMovingAverage _avgOtherAvatarDataRate;
|
||||
std::unordered_set<QUuid> _radiusIgnoredOthers;
|
||||
std::vector<QUuid> _radiusIgnoredOthers;
|
||||
ConicalViewFrustums _currentViewFrustums;
|
||||
|
||||
int _recentOtherAvatarsInView { 0 };
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
|
@ -33,6 +34,8 @@
|
|||
#include "AvatarMixer.h"
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
namespace chrono = std::chrono;
|
||||
|
||||
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
|
@ -209,7 +212,18 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
_stats.jobElapsedTime += (end - start);
|
||||
}
|
||||
|
||||
AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) {
|
||||
AABox box = avatar.getGlobalBoundingBox();
|
||||
glm::vec3 scale = box.getScale();
|
||||
scale *= bubbleExpansionFactor;
|
||||
const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3);
|
||||
scale = glm::max(scale, MIN_BUBBLE_SCALE);
|
||||
box.setScaleStayCentered(glm::max(scale, MIN_BUBBLE_SCALE));
|
||||
return box;
|
||||
}
|
||||
|
||||
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
|
||||
const Node* destinationNode = node.data();
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -220,7 +234,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
_stats.nodesBroadcastedTo++;
|
||||
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
|
||||
|
||||
nodeData->resetInViewStats();
|
||||
|
||||
|
@ -242,12 +256,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
int traitBytesSent = 0;
|
||||
|
||||
// max number of avatarBytes per frame
|
||||
auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
||||
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||
|
||||
// FIXME - find a way to not send the sessionID for every avatar
|
||||
int minimumBytesPerAvatar = AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID;
|
||||
|
||||
int overBudgetAvatars = 0;
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
@ -260,66 +270,38 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
bool PALIsOpen = nodeData->getRequestsDomainListData();
|
||||
|
||||
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
|
||||
bool getsAnyIgnored = PALIsOpen && node->getCanKick();
|
||||
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
|
||||
|
||||
if (PALIsOpen) {
|
||||
// Increase minimumBytesPerAvatar if the PAL is open
|
||||
minimumBytesPerAvatar += sizeof(AvatarDataPacket::AvatarGlobalPosition) +
|
||||
sizeof(AvatarDataPacket::AudioLoudness);
|
||||
}
|
||||
// Bandwidth allowance for data that must be sent.
|
||||
int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID +
|
||||
sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0;
|
||||
|
||||
// setup a PacketList for the avatarPackets
|
||||
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
|
||||
static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
||||
|
||||
// Define the minimum bubble size
|
||||
static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f);
|
||||
// Define the scale of the box for the current node
|
||||
glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale();
|
||||
// Set up the bounding box for the current node
|
||||
AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale);
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) {
|
||||
nodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Quadruple the scale of both bounding boxes
|
||||
nodeBox.embiggen(4.0f);
|
||||
|
||||
|
||||
// setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes
|
||||
std::vector<AvatarSharedPointer> avatarsToSort;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
std::unordered_map<QUuid, uint64_t> avatarEncodeTimes;
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||
// make sure this is an agent that we have avatar data for before considering it for inclusion
|
||||
if (otherNode->getType() == NodeType::Agent
|
||||
&& otherNode->getLinkedData()) {
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
|
||||
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
||||
avatarsToSort.push_back(otherAvatar);
|
||||
avatarDataToNodes[otherAvatar] = otherNode;
|
||||
QUuid id = otherAvatar->getSessionUUID();
|
||||
avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id);
|
||||
}
|
||||
});
|
||||
// compute node bounding box
|
||||
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
|
||||
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime)
|
||||
: _avatar(avatar), _lastEncodeTime(lastEncodeTime) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
|
||||
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
|
||||
float getRadius() const override {
|
||||
glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale());
|
||||
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
|
||||
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
|
||||
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
|
||||
}
|
||||
uint64_t getTimestamp() const override {
|
||||
return _lastEncodeTime;
|
||||
}
|
||||
AvatarSharedPointer getAvatar() const { return _avatar; }
|
||||
const Node* getNode() const { return _node; }
|
||||
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
const AvatarData* _avatar;
|
||||
const Node* _node;
|
||||
uint64_t _lastEncodeTime;
|
||||
};
|
||||
|
||||
|
@ -329,16 +311,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
sortedAvatars.reserve(avatarsToSort.size());
|
||||
sortedAvatars.reserve(_end - _begin);
|
||||
|
||||
// ignore or sort
|
||||
const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
for (const auto& avatar : avatarsToSort) {
|
||||
if (avatar == thisAvatar) {
|
||||
// don't echo updates to self
|
||||
for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
|
||||
Node* otherNodeRaw = (*listedNode).data();
|
||||
if (otherNodeRaw->getType() != NodeType::Agent
|
||||
|| !otherNodeRaw->getLinkedData()
|
||||
|| otherNodeRaw == destinationNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto avatarNode = otherNodeRaw;
|
||||
|
||||
bool shouldIgnore = false;
|
||||
// We ignore other nodes for a couple of reasons:
|
||||
// 1) ignore bubbles and ignore specific node
|
||||
|
@ -346,53 +330,39 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// happen if for example the avatar is connected on a desktop and sending
|
||||
// updates at ~30hz. So every 3 frames we skip a frame.
|
||||
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
const AvatarMixerClientData* avatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data
|
||||
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data
|
||||
quint64 startIgnoreCalculation = usecTimestampNow();
|
||||
|
||||
// make sure we have data for this avatar, that it isn't the same node,
|
||||
// and isn't an avatar that the viewing node has ignored
|
||||
// or that has ignored the viewing node
|
||||
if (!avatarNode->getLinkedData()
|
||||
|| avatarNode->getUUID() == node->getUUID()
|
||||
|| (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
|
||||
|| (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
|
||||
if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen)
|
||||
|| (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
|
||||
shouldIgnore = true;
|
||||
} else {
|
||||
// Check to see if the space bubble is enabled
|
||||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||
if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale();
|
||||
// Define the scale of the box for the current other node
|
||||
glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale;
|
||||
// Set up the bounding box for the current other node
|
||||
AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
// Clamp the size of the bounding box to a minimum scale
|
||||
if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) {
|
||||
otherNodeBox.setScaleStayCentered(minBubbleSize);
|
||||
}
|
||||
// Change the scale of both bounding boxes
|
||||
// (This is an arbitrary number determined empirically)
|
||||
otherNodeBox.embiggen(2.4f);
|
||||
|
||||
if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
// Perform the collision check between the two bounding boxes
|
||||
const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically
|
||||
AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
nodeData->ignoreOther(node, avatarNode);
|
||||
nodeData->ignoreOther(destinationNode, avatarNode);
|
||||
shouldIgnore = !getsAnyIgnored;
|
||||
}
|
||||
}
|
||||
// Not close enough to ignore
|
||||
if (!shouldIgnore) {
|
||||
nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID());
|
||||
nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldIgnore) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID());
|
||||
AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber();
|
||||
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber();
|
||||
|
||||
// FIXME - This code does appear to be working. But it seems brittle.
|
||||
// It supports determining if the frame of data for this "other"
|
||||
|
@ -417,12 +387,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
if (!shouldIgnore) {
|
||||
// sort this one for later
|
||||
uint64_t lastEncodeTime = 0;
|
||||
std::unordered_map<QUuid, uint64_t>::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID());
|
||||
if (itr != avatarEncodeTimes.end()) {
|
||||
lastEncodeTime = itr->second;
|
||||
}
|
||||
sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime));
|
||||
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData();
|
||||
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID());
|
||||
|
||||
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,19 +398,31 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||
|
||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector();
|
||||
for (const auto& sortedAvatar : sortedAvatarVector) {
|
||||
const auto& avatarData = sortedAvatar.getAvatar();
|
||||
remainingAvatars--;
|
||||
const Node* otherNode = sortedAvatar.getNode();
|
||||
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
||||
|
||||
auto otherNode = avatarDataToNodes[avatarData];
|
||||
assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
// NOTE: Here's where we determine if we are over budget and drop to bare minimum data
|
||||
AvatarData::AvatarDataDetail detail = AvatarData::NoData;
|
||||
|
||||
// NOTE: Here's where we determine if we are over budget and drop remaining avatars,
|
||||
// or send minimal avatar data in uncommon case of PALIsOpen.
|
||||
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
|
||||
bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame;
|
||||
if (overBudget) {
|
||||
if (PALIsOpen) {
|
||||
_stats.overBudgetAvatars++;
|
||||
detail = AvatarData::PALMinimum;
|
||||
} else {
|
||||
_stats.overBudgetAvatars += remainingAvatars;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
quint64 startAvatarDataPacking = usecTimestampNow();
|
||||
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
|
||||
++numOtherAvatars;
|
||||
|
||||
|
@ -459,32 +439,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
// determine if avatar is in view which determines how much data to send
|
||||
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
|
||||
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale();
|
||||
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
|
||||
|
||||
// start a new segment in the PacketList for this avatar
|
||||
avatarPacketList->startSegment();
|
||||
|
||||
AvatarData::AvatarDataDetail detail;
|
||||
|
||||
if (overBudget) {
|
||||
overBudgetAvatars++;
|
||||
_stats.overBudgetAvatars++;
|
||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData;
|
||||
} else if (!isInView) {
|
||||
if (isLowerPriority) {
|
||||
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
|
||||
nodeData->incrementAvatarOutOfView();
|
||||
} else {
|
||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO
|
||||
? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
} else if (!overBudget) {
|
||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
nodeData->incrementAvatarInView();
|
||||
}
|
||||
|
||||
bool includeThisAvatar = true;
|
||||
auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID());
|
||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
|
||||
|
||||
lastSentJointsForOther.resize(otherAvatar->getJointCount());
|
||||
|
@ -494,14 +460,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray
|
||||
bool dropFaceTracking = false;
|
||||
|
||||
quint64 start = usecTimestampNow();
|
||||
auto startSerialize = chrono::high_resolution_clock::now();
|
||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition,
|
||||
&lastSentJointsForOther);
|
||||
quint64 end = usecTimestampNow();
|
||||
_stats.toByteArrayElapsedTime += (end - start);
|
||||
auto endSerialize = chrono::high_resolution_clock::now();
|
||||
_stats.toByteArrayElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||
|
||||
static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
||||
if (bytes.size() > maxAvatarDataBytes) {
|
||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
||||
|
@ -527,8 +493,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
}
|
||||
|
||||
if (includeThisAvatar) {
|
||||
// start a new segment in the PacketList for this avatar
|
||||
avatarPacketList->startSegment();
|
||||
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
|
||||
numAvatarDataBytes += avatarPacketList->write(bytes);
|
||||
avatarPacketList->endSegment();
|
||||
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
|
@ -546,15 +515,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// It would be nice if we could tweak its future sort priority to put it at the back of the list.
|
||||
}
|
||||
|
||||
avatarPacketList->endSegment();
|
||||
|
||||
quint64 endAvatarDataPacking = usecTimestampNow();
|
||||
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
||||
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
_stats.avatarDataPackingElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
|
||||
|
||||
// use helper to add any changed traits to our packet list
|
||||
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||
|
||||
traitsPacketList->getDataSize();
|
||||
remainingAvatars--;
|
||||
}
|
||||
|
||||
quint64 startPacketSending = usecTimestampNow();
|
||||
|
@ -566,7 +533,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
_stats.numBytesSent += numAvatarDataBytes;
|
||||
|
||||
// send the avatar data PacketList
|
||||
nodeList->sendPacketList(std::move(avatarPacketList), *node);
|
||||
nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode);
|
||||
|
||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
||||
|
@ -576,7 +543,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
if (traitsPacketList->getNumPackets() >= 1) {
|
||||
// send the traits packet list
|
||||
nodeList->sendPacketList(std::move(traitsPacketList), *node);
|
||||
nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode);
|
||||
}
|
||||
|
||||
// record the number of avatars held back this frame
|
||||
|
|
BIN
interface/resources/images/interstitialPage/goTo_button.png
Normal file
BIN
interface/resources/images/interstitialPage/goTo_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9 KiB |
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.fbx
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.fbx
Normal file
Binary file not shown.
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.png
Normal file
BIN
interface/resources/meshes/redirect/oopsDialog_timeout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
@ -346,7 +346,15 @@ Item {
|
|||
target: loginDialog
|
||||
onHandleLoginCompleted: {
|
||||
console.log("Login Succeeded, linking steam account")
|
||||
|
||||
var poppedUp = Settings.getValue("loginDialogPoppedUp", false);
|
||||
if (poppedUp) {
|
||||
console.log("[ENCOURAGELOGINDIALOG]: logging in")
|
||||
var data = {
|
||||
"action": "user logged in"
|
||||
};
|
||||
UserActivityLogger.logAction("encourageLoginDialog", data);
|
||||
Settings.setValue("loginDialogPoppedUp", false);
|
||||
}
|
||||
if (loginDialog.isSteamRunning()) {
|
||||
loginDialog.linkSteam()
|
||||
} else {
|
||||
|
@ -354,23 +362,20 @@ Item {
|
|||
bodyLoader.item.width = root.pane.width
|
||||
bodyLoader.item.height = root.pane.height
|
||||
}
|
||||
if (Settings.getValue("loginDialogPoppedUp", false)) {
|
||||
var data = {
|
||||
"action": "user logged in"
|
||||
};
|
||||
UserActivityLogger.logAction("encourageLoginDialog", data);
|
||||
}
|
||||
}
|
||||
onHandleLoginFailed: {
|
||||
console.log("Login Failed")
|
||||
mainTextContainer.visible = true
|
||||
toggleLoading(false)
|
||||
if (Settings.getValue("loginDialogPoppedUp", false)) {
|
||||
var poppedUp = Settings.getValue("loginDialogPoppedUp", false);
|
||||
if (poppedUp) {
|
||||
console.log("[ENCOURAGELOGINDIALOG]: failed logging in")
|
||||
var data = {
|
||||
"action": "user failed logging in"
|
||||
};
|
||||
UserActivityLogger.logAction("encourageLoginDialog", data);
|
||||
Settings.setValue("loginDialogPoppedUp", false);
|
||||
}
|
||||
mainTextContainer.visible = true
|
||||
toggleLoading(false)
|
||||
}
|
||||
onHandleLinkCompleted: {
|
||||
console.log("Link Succeeded")
|
||||
|
|
|
@ -3498,7 +3498,9 @@ bool Application::isServerlessMode() const {
|
|||
}
|
||||
|
||||
void Application::setIsInterstitialMode(bool interstitialMode) {
|
||||
if (_interstitialMode != interstitialMode) {
|
||||
Settings settings;
|
||||
bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool();
|
||||
if (_interstitialMode != interstitialMode && enableInterstitial) {
|
||||
_interstitialMode = interstitialMode;
|
||||
|
||||
DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode);
|
||||
|
@ -6346,6 +6348,7 @@ void Application::updateWindowTitle() const {
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto isInErrorState = nodeList->getDomainHandler().isInErrorState();
|
||||
|
||||
QString buildVersion = " - "
|
||||
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
|
||||
|
@ -6353,14 +6356,19 @@ void Application::updateWindowTitle() const {
|
|||
|
||||
QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)";
|
||||
|
||||
QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
|
||||
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
|
||||
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
|
||||
QString username = accountManager->getAccountInfo().getUsername();
|
||||
|
||||
setCrashAnnotation("username", username.toStdString());
|
||||
|
||||
QString currentPlaceName;
|
||||
if (isServerlessMode()) {
|
||||
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
|
||||
if (isInErrorState) {
|
||||
currentPlaceName = "serverless: " + nodeList->getDomainHandler().getErrorDomainURL().toString();
|
||||
} else {
|
||||
currentPlaceName = "serverless: " + DependencyManager::get<AddressManager>()->getDomainURL().toString();
|
||||
}
|
||||
} else {
|
||||
currentPlaceName = DependencyManager::get<AddressManager>()->getDomainURL().host();
|
||||
if (currentPlaceName.isEmpty()) {
|
||||
|
|
|
@ -45,10 +45,11 @@ const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.08f; // sec
|
|||
const float LOD_BATCH_TO_PRESENT_CUSHION_TIME = 3.0f; // msec
|
||||
|
||||
void LODManager::setRenderTimes(float presentTime, float engineRunTime, float batchTime, float gpuTime) {
|
||||
_presentTime = presentTime;
|
||||
_engineRunTime = engineRunTime;
|
||||
_batchTime = batchTime;
|
||||
_gpuTime = gpuTime;
|
||||
// Make sure the sampled time are positive values
|
||||
_presentTime = std::max(0.0f, presentTime);
|
||||
_engineRunTime = std::max(0.0f, engineRunTime);
|
||||
_batchTime = std::max(0.0f, batchTime);
|
||||
_gpuTime = std::max(0.0f, gpuTime);
|
||||
}
|
||||
|
||||
void LODManager::autoAdjustLOD(float realTimeDelta) {
|
||||
|
@ -64,16 +65,29 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
|
|||
auto presentTime = (_presentTime > _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME ? _batchTime + LOD_BATCH_TO_PRESENT_CUSHION_TIME : _presentTime);
|
||||
float maxRenderTime = glm::max(glm::max(presentTime, _engineRunTime), _gpuTime);
|
||||
|
||||
// compute time-weighted running average maxRenderTime
|
||||
// Note: we MUST clamp the blend to 1.0 for stability
|
||||
// maxRenderTime must be a realistic valid duration in order for the regulation to work correctly.
|
||||
// We make sure it s a non zero positive value (1.0ms) under 1 sec
|
||||
maxRenderTime = std::max(1.0f, std::min(maxRenderTime, (float)MSECS_PER_SECOND));
|
||||
|
||||
// realTimeDelta must be a realistic valid duration in order for the regulation to work correctly.
|
||||
// We make sure it a positive value under 1 sec
|
||||
// note that if real time delta is very small we will early exit to avoid division by zero
|
||||
realTimeDelta = std::max(0.0f, std::min(realTimeDelta, 1.0f));
|
||||
|
||||
// compute time-weighted running average render time (now and smooth)
|
||||
// We MUST clamp the blend between 0.0 and 1.0 for stability
|
||||
float nowBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE) ? realTimeDelta / LOD_ADJUST_RUNNING_AVG_TIMESCALE : 1.0f;
|
||||
_nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec
|
||||
|
||||
float smoothBlend = (realTimeDelta < LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) ? realTimeDelta / (LOD_ADJUST_RUNNING_AVG_TIMESCALE * _smoothScale) : 1.0f;
|
||||
_smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec
|
||||
|
||||
if (!_automaticLODAdjust || _nowRenderTime == 0.0f || _smoothRenderTime == 0.0f) {
|
||||
// early exit
|
||||
//Evaluate the running averages for the render time
|
||||
// We must sanity check for the output average evaluated to be in a valid range to avoid issues
|
||||
_nowRenderTime = (1.0f - nowBlend) * _nowRenderTime + nowBlend * maxRenderTime; // msec
|
||||
_nowRenderTime = std::max(0.0f, std::min(_nowRenderTime, (float)MSECS_PER_SECOND));
|
||||
_smoothRenderTime = (1.0f - smoothBlend) * _smoothRenderTime + smoothBlend * maxRenderTime; // msec
|
||||
_smoothRenderTime = std::max(0.0f, std::min(_smoothRenderTime, (float)MSECS_PER_SECOND));
|
||||
|
||||
// Early exit if not regulating or if the simulation or render times don't matter
|
||||
if (!_automaticLODAdjust || realTimeDelta <= 0.0f || _nowRenderTime <= 0.0f || _smoothRenderTime <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -130,7 +144,8 @@ void LODManager::autoAdjustLOD(float realTimeDelta) {
|
|||
glm::clamp(integral, -1.0f, 1.0f);
|
||||
|
||||
// Compute derivative
|
||||
auto derivative = (error - previous_error) / dt;
|
||||
// dt is never zero because realTimeDelta would have early exit above, but if it ever was let's zero the derivative term
|
||||
auto derivative = (dt <= 0.0f ? 0.0f : (error - previous_error) / dt);
|
||||
|
||||
// remember history
|
||||
_pidHistory.x = error;
|
||||
|
|
|
@ -176,29 +176,29 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri
|
|||
}
|
||||
|
||||
void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||
// lock the hash for read to check the size
|
||||
QReadLocker lock(&_hashLock);
|
||||
if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) {
|
||||
return;
|
||||
{
|
||||
// lock the hash for read to check the size
|
||||
QReadLocker lock(&_hashLock);
|
||||
if (_avatarHash.size() < 2 && _avatarsToFade.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
PerformanceTimer perfTimer("otherAvatars");
|
||||
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
|
||||
SortableAvatar(const std::shared_ptr<Avatar>& avatar) : _avatar(avatar) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
float getRadius() const override { return std::static_pointer_cast<Avatar>(_avatar)->getBoundingRadius(); }
|
||||
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_avatar)->getLastRenderUpdateTime(); }
|
||||
AvatarSharedPointer getAvatar() const { return _avatar; }
|
||||
float getRadius() const override { return _avatar->getBoundingRadius(); }
|
||||
uint64_t getTimestamp() const override { return _avatar->getLastRenderUpdateTime(); }
|
||||
std::shared_ptr<Avatar> getAvatar() const { return _avatar; }
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
std::shared_ptr<Avatar> _avatar;
|
||||
};
|
||||
|
||||
auto avatarMap = getHashCopy();
|
||||
AvatarHash::iterator itr = avatarMap.begin();
|
||||
|
||||
const auto& views = qApp->getConicalViews();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(views,
|
||||
|
@ -207,22 +207,24 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
AvatarData::_avatarSortCoefficientAge);
|
||||
sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar
|
||||
|
||||
// sort
|
||||
// Build vector and compute priorities
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
AvatarHash::iterator itr = avatarMap.begin();
|
||||
while (itr != avatarMap.end()) {
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
|
||||
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
||||
// DO NOT update or fade out uninitialized Avatars
|
||||
if (avatar != _myAvatar && avatar->isInitialized()) {
|
||||
if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) {
|
||||
sortedAvatars.push(SortableAvatar(avatar));
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
// Sort
|
||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector();
|
||||
|
||||
// process in sorted order
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
const uint64_t UPDATE_BUDGET = 2000; // usec
|
||||
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
|
||||
uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET;
|
||||
int numAvatarsUpdated = 0;
|
||||
int numAVatarsNotUpdated = 0;
|
||||
|
||||
|
@ -241,18 +243,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
avatar->updateOrbPosition();
|
||||
}
|
||||
|
||||
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||
if (ignoring) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// for ALL avatars...
|
||||
if (_shouldRender) {
|
||||
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
||||
}
|
||||
avatar->animateScaleChanges(deltaTime);
|
||||
|
||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now < updateExpiry) {
|
||||
// we're within budget
|
||||
|
@ -273,7 +269,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
// no time to simulate, but we take the time to count how many were tragically missed
|
||||
while (it != sortedAvatarVector.end()) {
|
||||
const SortableAvatar& newSortData = *it;
|
||||
const auto newAvatar = std::static_pointer_cast<Avatar>(newSortData.getAvatar());
|
||||
const auto& newAvatar = newSortData.getAvatar();
|
||||
bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
|
||||
// Once we reach an avatar that's not in view, all avatars after it will also be out of view
|
||||
if (!inView) {
|
||||
|
@ -443,6 +439,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
avatar->die();
|
||||
queuePhysicsChange(avatar);
|
||||
|
||||
// remove this avatar's entities from the tree now, if we wait (as we did previously) for this Avatar's destructor
|
||||
// it might not fire until after we create a new instance for the same remote avatar, which creates a race
|
||||
// on the creation of entities for that avatar instance and the deletion of entities for this instance
|
||||
avatar->removeAvatarEntitiesFromTree();
|
||||
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
|
||||
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
|
||||
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
|
||||
|
|
|
@ -66,13 +66,18 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
|
|||
Locker lock(_lock);
|
||||
EntityItemPointer entity = _entityTree->findEntityByID(entityID);
|
||||
|
||||
_trackedEntities.emplace(entityID, entity);
|
||||
int trackedEntityCount = (int)_trackedEntities.size();
|
||||
if (entity) {
|
||||
|
||||
if (trackedEntityCount > _maxTrackedEntityCount) {
|
||||
_maxTrackedEntityCount = trackedEntityCount;
|
||||
_trackedEntities.emplace(entityID, entity);
|
||||
int trackedEntityCount = (int)_trackedEntities.size();
|
||||
|
||||
if (trackedEntityCount > _maxTrackedEntityCount) {
|
||||
_maxTrackedEntityCount = trackedEntityCount;
|
||||
}
|
||||
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
|
||||
}
|
||||
qCDebug(interfaceapp) << "Safe Landing: Tracking entity " << entity->getItemName();
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Safe Landing: Null Entity: " << entityID;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +151,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
|
|||
bool hasAABox;
|
||||
entity->getAABox(hasAABox);
|
||||
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
|
||||
return entity->isReadyToComputeShape();
|
||||
return (!entity->shouldBePhysical() || entity->isReadyToComputeShape());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,12 +161,23 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
|
|||
|
||||
bool SafeLanding::isEntityLoadingComplete() {
|
||||
Locker lock(_lock);
|
||||
|
||||
|
||||
auto entityTree = qApp->getEntities();
|
||||
auto entityMapIter = _trackedEntities.begin();
|
||||
|
||||
while (entityMapIter != _trackedEntities.end()) {
|
||||
auto entity = entityMapIter->second;
|
||||
bool isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first));
|
||||
|
||||
bool isVisuallyReady = true;
|
||||
|
||||
Settings settings;
|
||||
bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool();
|
||||
|
||||
if (enableInterstitial) {
|
||||
isVisuallyReady = (entity->isVisuallyReady() || !entityTree->renderableForEntityId(entityMapIter->first));
|
||||
}
|
||||
|
||||
if (isEntityPhysicsReady(entity) && isVisuallyReady) {
|
||||
entityMapIter = _trackedEntities.erase(entityMapIter);
|
||||
} else {
|
||||
|
|
|
@ -131,16 +131,6 @@ Avatar::Avatar(QThread* thread) :
|
|||
}
|
||||
|
||||
Avatar::~Avatar() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (geometryCache) {
|
||||
geometryCache->releaseID(_nameRectGeometryID);
|
||||
|
@ -385,6 +375,19 @@ void Avatar::updateAvatarEntities() {
|
|||
setAvatarEntityDataChanged(false);
|
||||
}
|
||||
|
||||
void Avatar::removeAvatarEntitiesFromTree() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::relayJointDataToChildren() {
|
||||
forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
|
|
|
@ -73,6 +73,7 @@ public:
|
|||
|
||||
void init();
|
||||
void updateAvatarEntities();
|
||||
void removeAvatarEntitiesFromTree();
|
||||
void simulate(float deltaTime, bool inView);
|
||||
virtual void simulateAttachments(float deltaTime);
|
||||
|
||||
|
|
|
@ -363,13 +363,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
|
||||
destinationBuffer += sizeof(packetStateFlags);
|
||||
|
||||
#define AVATAR_MEMCPY(src) \
|
||||
memcpy(destinationBuffer, &(src), sizeof(src)); \
|
||||
destinationBuffer += sizeof(src);
|
||||
|
||||
if (hasAvatarGlobalPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AvatarGlobalPosition*>(destinationBuffer);
|
||||
data->globalPosition[0] = _globalPosition.x;
|
||||
data->globalPosition[1] = _globalPosition.y;
|
||||
data->globalPosition[2] = _globalPosition.z;
|
||||
destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||
AVATAR_MEMCPY(_globalPosition);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
|
||||
|
@ -380,17 +380,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
if (hasAvatarBoundingBox) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AvatarBoundingBox*>(destinationBuffer);
|
||||
|
||||
data->avatarDimensions[0] = _globalBoundingBoxDimensions.x;
|
||||
data->avatarDimensions[1] = _globalBoundingBoxDimensions.y;
|
||||
data->avatarDimensions[2] = _globalBoundingBoxDimensions.z;
|
||||
|
||||
data->boundOriginOffset[0] = _globalBoundingBoxOffset.x;
|
||||
data->boundOriginOffset[1] = _globalBoundingBoxOffset.y;
|
||||
data->boundOriginOffset[2] = _globalBoundingBoxOffset.z;
|
||||
|
||||
destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||
AVATAR_MEMCPY(_globalBoundingBoxDimensions);
|
||||
AVATAR_MEMCPY(_globalBoundingBoxOffset);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
if (outboundDataRateOut) {
|
||||
|
@ -424,13 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
if (hasLookAtPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::LookAtPosition*>(destinationBuffer);
|
||||
auto lookAt = _headData->getLookAtPosition();
|
||||
data->lookAtPosition[0] = lookAt.x;
|
||||
data->lookAtPosition[1] = lookAt.y;
|
||||
data->lookAtPosition[2] = lookAt.z;
|
||||
destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition);
|
||||
|
||||
AVATAR_MEMCPY(_headData->getLookAtPosition());
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
if (outboundDataRateOut) {
|
||||
outboundDataRateOut->lookAtPositionRate.increment(numBytes);
|
||||
|
@ -531,12 +516,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
if (hasAvatarLocalPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AvatarLocalPosition*>(destinationBuffer);
|
||||
auto localPosition = getLocalPosition();
|
||||
data->localPosition[0] = localPosition.x;
|
||||
data->localPosition[1] = localPosition.y;
|
||||
data->localPosition[2] = localPosition.z;
|
||||
destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition);
|
||||
const auto localPosition = getLocalPosition();
|
||||
AVATAR_MEMCPY(localPosition);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
if (outboundDataRateOut) {
|
||||
|
@ -567,19 +548,24 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
QVector<JointData> jointData;
|
||||
if (hasJointData || hasJointDefaultPoseFlags) {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
jointData = _jointData;
|
||||
}
|
||||
|
||||
// If it is connected, pack up the data
|
||||
if (hasJointData) {
|
||||
auto startSection = destinationBuffer;
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
|
||||
// joint rotation data
|
||||
int numJoints = _jointData.size();
|
||||
int numJoints = jointData.size();
|
||||
*destinationBuffer++ = (uint8_t)numJoints;
|
||||
|
||||
unsigned char* validityPosition = destinationBuffer;
|
||||
unsigned char validity = 0;
|
||||
int validityBit = 0;
|
||||
int numValidityBytes = (int)std::ceil(numJoints / (float)BITS_IN_BYTE);
|
||||
int numValidityBytes = calcBitVectorSize(numJoints);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
int rotationSentCount = 0;
|
||||
|
@ -589,43 +575,37 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
|
||||
|
||||
// sentJointDataOut and lastSentJointData might be the same vector
|
||||
// build sentJointDataOut locally and then swap it at the end.
|
||||
QVector<JointData> localSentJointDataOut;
|
||||
if (sentJointDataOut) {
|
||||
localSentJointDataOut.resize(numJoints); // Make sure the destination is resized before using it
|
||||
sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it
|
||||
}
|
||||
|
||||
float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition);
|
||||
float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT;
|
||||
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData[i];
|
||||
for (int i = 0; i < jointData.size(); i++) {
|
||||
const JointData& data = jointData[i];
|
||||
const JointData& last = lastSentJointData[i];
|
||||
|
||||
if (!data.rotationIsDefaultPose) {
|
||||
bool mustSend = sendAll || last.rotationIsDefaultPose;
|
||||
if (mustSend || last.rotation != data.rotation) {
|
||||
|
||||
bool largeEnoughRotation = true;
|
||||
if (cullSmallChanges) {
|
||||
// The dot product for smaller rotations is a smaller number.
|
||||
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
|
||||
largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT;
|
||||
}
|
||||
|
||||
if (mustSend || !cullSmallChanges || largeEnoughRotation) {
|
||||
validity |= (1 << validityBit);
|
||||
// The dot product for larger rotations is a lower number.
|
||||
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
|
||||
if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation)
|
||||
|| (cullSmallChanges && glm::dot(last.rotation, data.rotation) < minRotationDOT) ) {
|
||||
validity |= (1 << validityBit);
|
||||
#ifdef WANT_DEBUG
|
||||
rotationSentCount++;
|
||||
rotationSentCount++;
|
||||
#endif
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
||||
|
||||
if (sentJointDataOut) {
|
||||
localSentJointDataOut[i].rotation = data.rotation;
|
||||
localSentJointDataOut[i].rotationIsDefaultPose = false;
|
||||
}
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].rotation = data.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose;
|
||||
}
|
||||
|
||||
if (++validityBit == BITS_IN_BYTE) {
|
||||
*validityPosition++ = validity;
|
||||
validityBit = validity = 0;
|
||||
|
@ -647,35 +627,38 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
|
||||
|
||||
float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition);
|
||||
float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION;
|
||||
|
||||
float maxTranslationDimension = 0.0;
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData[i];
|
||||
for (int i = 0; i < jointData.size(); i++) {
|
||||
const JointData& data = jointData[i];
|
||||
const JointData& last = lastSentJointData[i];
|
||||
|
||||
if (!data.translationIsDefaultPose) {
|
||||
bool mustSend = sendAll || last.translationIsDefaultPose;
|
||||
if (mustSend || last.translation != data.translation) {
|
||||
if (mustSend || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
|
||||
validity |= (1 << validityBit);
|
||||
if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation)
|
||||
|| (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) {
|
||||
|
||||
validity |= (1 << validityBit);
|
||||
#ifdef WANT_DEBUG
|
||||
translationSentCount++;
|
||||
translationSentCount++;
|
||||
#endif
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
||||
|
||||
destinationBuffer +=
|
||||
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
|
||||
destinationBuffer +=
|
||||
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
|
||||
|
||||
if (sentJointDataOut) {
|
||||
localSentJointDataOut[i].translation = data.translation;
|
||||
localSentJointDataOut[i].translationIsDefaultPose = false;
|
||||
}
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].translation = data.translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose;
|
||||
}
|
||||
|
||||
if (++validityBit == BITS_IN_BYTE) {
|
||||
*validityPosition++ = validity;
|
||||
validityBit = validity = 0;
|
||||
|
@ -691,6 +674,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(),
|
||||
TRANSLATION_COMPRESSION_RADIX);
|
||||
|
||||
Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix());
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation());
|
||||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(),
|
||||
|
@ -707,34 +691,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
glm::vec3 mouseFarGrabPosition = extractTranslation(mouseFarGrabMatrix);
|
||||
glm::quat mouseFarGrabRotation = extractRotation(mouseFarGrabMatrix);
|
||||
|
||||
data->leftFarGrabPosition[0] = leftFarGrabPosition.x;
|
||||
data->leftFarGrabPosition[1] = leftFarGrabPosition.y;
|
||||
data->leftFarGrabPosition[2] = leftFarGrabPosition.z;
|
||||
|
||||
AVATAR_MEMCPY(leftFarGrabPosition);
|
||||
// Can't do block copy as struct order is x, y, z, w.
|
||||
data->leftFarGrabRotation[0] = leftFarGrabRotation.w;
|
||||
data->leftFarGrabRotation[1] = leftFarGrabRotation.x;
|
||||
data->leftFarGrabRotation[2] = leftFarGrabRotation.y;
|
||||
data->leftFarGrabRotation[3] = leftFarGrabRotation.z;
|
||||
destinationBuffer += sizeof(data->leftFarGrabPosition);
|
||||
|
||||
data->rightFarGrabPosition[0] = rightFarGrabPosition.x;
|
||||
data->rightFarGrabPosition[1] = rightFarGrabPosition.y;
|
||||
data->rightFarGrabPosition[2] = rightFarGrabPosition.z;
|
||||
|
||||
AVATAR_MEMCPY(rightFarGrabPosition);
|
||||
data->rightFarGrabRotation[0] = rightFarGrabRotation.w;
|
||||
data->rightFarGrabRotation[1] = rightFarGrabRotation.x;
|
||||
data->rightFarGrabRotation[2] = rightFarGrabRotation.y;
|
||||
data->rightFarGrabRotation[3] = rightFarGrabRotation.z;
|
||||
destinationBuffer += sizeof(data->rightFarGrabRotation);
|
||||
|
||||
data->mouseFarGrabPosition[0] = mouseFarGrabPosition.x;
|
||||
data->mouseFarGrabPosition[1] = mouseFarGrabPosition.y;
|
||||
data->mouseFarGrabPosition[2] = mouseFarGrabPosition.z;
|
||||
|
||||
AVATAR_MEMCPY(mouseFarGrabPosition);
|
||||
data->mouseFarGrabRotation[0] = mouseFarGrabRotation.w;
|
||||
data->mouseFarGrabRotation[1] = mouseFarGrabRotation.x;
|
||||
data->mouseFarGrabRotation[2] = mouseFarGrabRotation.y;
|
||||
data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z;
|
||||
|
||||
destinationBuffer += sizeof(AvatarDataPacket::FarGrabJoints);
|
||||
destinationBuffer += sizeof(data->mouseFarGrabRotation);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
|
||||
|
@ -761,41 +738,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
outboundDataRateOut->jointDataRate.increment(numBytes);
|
||||
}
|
||||
|
||||
if (sentJointDataOut) {
|
||||
|
||||
// Mark default poses in lastSentJointData, so when they become non-default we send them.
|
||||
for (int i = 0; i < _jointData.size(); i++) {
|
||||
const JointData& data = _jointData[i];
|
||||
JointData& local = localSentJointDataOut[i];
|
||||
if (data.rotationIsDefaultPose) {
|
||||
local.rotationIsDefaultPose = true;
|
||||
}
|
||||
if (data.translationIsDefaultPose) {
|
||||
local.translationIsDefaultPose = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Push new sent joint data to sentJointDataOut
|
||||
sentJointDataOut->swap(localSentJointDataOut);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasJointDefaultPoseFlags) {
|
||||
auto startSection = destinationBuffer;
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
|
||||
// write numJoints
|
||||
int numJoints = _jointData.size();
|
||||
int numJoints = jointData.size();
|
||||
*destinationBuffer++ = (uint8_t)numJoints;
|
||||
|
||||
// write rotationIsDefaultPose bits
|
||||
destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) {
|
||||
return _jointData[i].rotationIsDefaultPose;
|
||||
return jointData[i].rotationIsDefaultPose;
|
||||
});
|
||||
|
||||
// write translationIsDefaultPose bits
|
||||
destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) {
|
||||
return _jointData[i].translationIsDefaultPose;
|
||||
return jointData[i].translationIsDefaultPose;
|
||||
});
|
||||
|
||||
if (outboundDataRateOut) {
|
||||
|
@ -880,7 +839,6 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa
|
|||
|
||||
// read data in packet starting at byte offset and return number of bytes parsed
|
||||
int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
||||
|
||||
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
||||
lazyInitHeadData();
|
||||
|
||||
|
@ -932,7 +890,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||
if (_globalPosition != newValue) {
|
||||
_globalPosition = newValue;
|
||||
_globalPositionChanged = usecTimestampNow();
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
|
@ -956,11 +914,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
|
||||
if (_globalBoundingBoxDimensions != newDimensions) {
|
||||
_globalBoundingBoxDimensions = newDimensions;
|
||||
_avatarBoundingBoxChanged = usecTimestampNow();
|
||||
_avatarBoundingBoxChanged = now;
|
||||
}
|
||||
if (_globalBoundingBoxOffset != newOffset) {
|
||||
_globalBoundingBoxOffset = newOffset;
|
||||
_avatarBoundingBoxChanged = usecTimestampNow();
|
||||
_avatarBoundingBoxChanged = now;
|
||||
}
|
||||
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||
|
@ -1061,7 +1019,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
glm::mat4 sensorToWorldMatrix = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), sensorToWorldQuat, sensorToWorldTrans);
|
||||
if (_sensorToWorldMatrixCache.get() != sensorToWorldMatrix) {
|
||||
_sensorToWorldMatrixCache.set(sensorToWorldMatrix);
|
||||
_sensorToWorldMatrixChanged = usecTimestampNow();
|
||||
_sensorToWorldMatrixChanged = now;
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
|
@ -1118,7 +1076,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags);
|
||||
|
||||
if (somethingChanged) {
|
||||
_additionalFlagsChanged = usecTimestampNow();
|
||||
_additionalFlagsChanged = now;
|
||||
}
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_additionalFlagsRate.increment(numBytesRead);
|
||||
|
@ -1138,7 +1096,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
if ((getParentID() != newParentID) || (getParentJointIndex() != parentInfo->parentJointIndex)) {
|
||||
SpatiallyNestable::setParentID(newParentID);
|
||||
SpatiallyNestable::setParentJointIndex(parentInfo->parentJointIndex);
|
||||
_parentChanged = usecTimestampNow();
|
||||
_parentChanged = now;
|
||||
}
|
||||
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
|
@ -1187,8 +1145,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
int numBytesRead = sourceBuffer - startSection;
|
||||
_faceTrackerRate.increment(numBytesRead);
|
||||
_faceTrackerUpdateRate.increment();
|
||||
} else {
|
||||
_headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size());
|
||||
}
|
||||
|
||||
if (hasJointData) {
|
||||
|
@ -1861,9 +1817,7 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice
|
|||
}
|
||||
|
||||
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion,
|
||||
AvatarTraits::TraitInstanceID wireInstanceID) {
|
||||
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||
qint64 bytesWritten = 0;
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
|
@ -1872,11 +1826,7 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr
|
|||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
|
||||
if (!wireInstanceID.isNull()) {
|
||||
bytesWritten += destination.write(wireInstanceID.toRfc4122());
|
||||
} else {
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
}
|
||||
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||
|
||||
if (traitType == AvatarTraits::AvatarEntity) {
|
||||
// grab a read lock on the avatar entities and check for entity data for the given ID
|
||||
|
@ -2879,10 +2829,8 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
value.extraInfo = object.property("extraInfo").toVariant().toMap();
|
||||
}
|
||||
|
||||
const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
|
||||
float AvatarData::_avatarSortCoefficientSize { 1.0f };
|
||||
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
|
||||
float AvatarData::_avatarSortCoefficientSize { 8.0f };
|
||||
float AvatarData::_avatarSortCoefficientCenter { 4.0f };
|
||||
float AvatarData::_avatarSortCoefficientAge { 1.0f };
|
||||
|
||||
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
|
||||
|
|
|
@ -962,8 +962,7 @@ public:
|
|||
qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||
AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||
qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID,
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION,
|
||||
AvatarTraits::TraitInstanceID wireInstanceID = AvatarTraits::TraitInstanceID());
|
||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||
|
||||
void prepareResetTraitInstances();
|
||||
|
||||
|
@ -1098,7 +1097,7 @@ public:
|
|||
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
|
||||
|
||||
glm::vec3 getClientGlobalPosition() const { return _globalPosition; }
|
||||
glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; }
|
||||
AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); }
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getAvatarEntityData
|
||||
|
@ -1170,8 +1169,6 @@ public:
|
|||
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
|
||||
virtual glm::quat getOrientationOutbound() const;
|
||||
|
||||
static const float OUT_OF_VIEW_PENALTY;
|
||||
|
||||
// TODO: remove this HACK once we settle on optimal sort coefficients
|
||||
// These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline.
|
||||
static float _avatarSortCoefficientSize;
|
||||
|
@ -1193,9 +1190,6 @@ public:
|
|||
void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; }
|
||||
int getReplicaIndex() { return _replicaIndex; }
|
||||
|
||||
const AvatarTraits::TraitInstanceID getTraitInstanceXORID() const { return _traitInstanceXORID; }
|
||||
void cycleTraitInstanceXORID() { _traitInstanceXORID = QUuid::createUuid(); }
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1502,8 +1496,6 @@ private:
|
|||
// privatize the copy constructor and assignment operator so they cannot be called
|
||||
AvatarData(const AvatarData&);
|
||||
AvatarData& operator= (const AvatarData&);
|
||||
|
||||
AvatarTraits::TraitInstanceID _traitInstanceXORID { QUuid::createUuid() };
|
||||
};
|
||||
Q_DECLARE_METATYPE(AvatarData*)
|
||||
|
||||
|
|
|
@ -86,8 +86,7 @@ void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTr
|
|||
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||
auto &replicas = _replicasMap[parentID];
|
||||
for (auto avatar : replicas) {
|
||||
avatar->processDeletedTraitInstance(traitType,
|
||||
AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID()));
|
||||
avatar->processDeletedTraitInstance(traitType, instanceID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,9 +95,7 @@ void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::T
|
|||
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||
auto &replicas = _replicasMap[parentID];
|
||||
for (auto avatar : replicas) {
|
||||
avatar->processTraitInstance(traitType,
|
||||
AvatarTraits::xoredInstanceID(instanceID, avatar->getTraitInstanceXORID()),
|
||||
traitBinaryData);
|
||||
avatar->processTraitInstance(traitType, instanceID, traitBinaryData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -346,28 +343,16 @@ void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> mess
|
|||
AvatarTraits::TraitInstanceID traitInstanceID =
|
||||
QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
// XOR the incoming trait instance ID with this avatar object's personal XOR ID
|
||||
|
||||
// this ensures that we have separate entity instances in the local tree
|
||||
// if we briefly end up with two Avatar objects for this node
|
||||
|
||||
// (which can occur if the shared pointer for the
|
||||
// previous instance of an avatar hasn't yet gone out of scope before the
|
||||
// new instance is created)
|
||||
|
||||
auto xoredInstanceID = AvatarTraits::xoredInstanceID(traitInstanceID, avatar->getTraitInstanceXORID());
|
||||
|
||||
message->readPrimitive(&traitBinarySize);
|
||||
|
||||
auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID);
|
||||
if (packetTraitVersion > processedInstanceVersion) {
|
||||
// in order to handle re-connections to the avatar mixer when the other
|
||||
if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
||||
avatar->processDeletedTraitInstance(traitType, xoredInstanceID);
|
||||
avatar->processDeletedTraitInstance(traitType, traitInstanceID);
|
||||
_replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID);
|
||||
} else {
|
||||
auto traitData = message->read(traitBinarySize);
|
||||
avatar->processTraitInstance(traitType, xoredInstanceID, traitData);
|
||||
avatar->processTraitInstance(traitType, traitInstanceID, traitData);
|
||||
_replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData);
|
||||
}
|
||||
processedInstanceVersion = packetTraitVersion;
|
||||
|
|
|
@ -41,8 +41,7 @@ namespace AvatarTraits {
|
|||
const TraitWireSize DELETED_TRAIT_SIZE = -1;
|
||||
|
||||
inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
|
||||
TraitVersion traitVersion = NULL_TRAIT_VERSION,
|
||||
TraitInstanceID xoredInstanceID = TraitInstanceID()) {
|
||||
TraitVersion traitVersion = NULL_TRAIT_VERSION) {
|
||||
qint64 bytesWritten = 0;
|
||||
|
||||
bytesWritten += destination.writePrimitive(traitType);
|
||||
|
@ -51,28 +50,12 @@ namespace AvatarTraits {
|
|||
bytesWritten += destination.writePrimitive(traitVersion);
|
||||
}
|
||||
|
||||
if (xoredInstanceID.isNull()) {
|
||||
bytesWritten += destination.write(instanceID.toRfc4122());
|
||||
} else {
|
||||
bytesWritten += destination.write(xoredInstanceID.toRfc4122());
|
||||
}
|
||||
bytesWritten += destination.write(instanceID.toRfc4122());
|
||||
|
||||
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
inline TraitInstanceID xoredInstanceID(TraitInstanceID localInstanceID, TraitInstanceID xorKeyID) {
|
||||
QByteArray xoredInstanceID { NUM_BYTES_RFC4122_UUID, 0 };
|
||||
auto xorKeyIDBytes = xorKeyID.toRfc4122();
|
||||
auto localInstanceIDBytes = localInstanceID.toRfc4122();
|
||||
|
||||
for (auto i = 0; i < localInstanceIDBytes.size(); ++i) {
|
||||
xoredInstanceID[i] = localInstanceIDBytes[i] ^ xorKeyIDBytes[i];
|
||||
}
|
||||
|
||||
return QUuid::fromRfc4122(xoredInstanceID);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarTraits_h
|
||||
|
|
|
@ -43,9 +43,6 @@ void ClientTraitsHandler::resetForNewMixer() {
|
|||
|
||||
// pre-fill the instanced statuses that we will need to send next frame
|
||||
_owningAvatar->prepareResetTraitInstances();
|
||||
|
||||
// reset the trait XOR ID since we're resetting for a new avatar mixer
|
||||
_owningAvatar->cycleTraitInstanceXORID();
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
||||
|
@ -96,19 +93,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
|| instanceIDValuePair.value == Updated) {
|
||||
// this is a changed trait we need to send or we haven't send out trait information yet
|
||||
// ask the owning avatar to pack it
|
||||
|
||||
// since this is going to the mixer, use the XORed instance ID (to anonymize trait instance IDs
|
||||
// that would typically persist across sessions)
|
||||
_owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList,
|
||||
AvatarTraits::NULL_TRAIT_VERSION,
|
||||
AvatarTraits::xoredInstanceID(instanceIDValuePair.id,
|
||||
_owningAvatar->getTraitInstanceXORID()));
|
||||
_owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
|
||||
} else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) {
|
||||
// pack delete for this trait instance
|
||||
AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
|
||||
*traitsPacketList, AvatarTraits::NULL_TRAIT_VERSION,
|
||||
AvatarTraits::xoredInstanceID(instanceIDValuePair.id,
|
||||
_owningAvatar->getTraitInstanceXORID()));
|
||||
*traitsPacketList);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ public:
|
|||
|
||||
void markTraitUpdated(AvatarTraits::TraitType updatedTrait)
|
||||
{ _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; }
|
||||
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID updatedInstanceID)
|
||||
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID)
|
||||
{ _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; }
|
||||
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID deleteInstanceID)
|
||||
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID)
|
||||
{ _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; }
|
||||
|
||||
void resetForNewMixer();
|
||||
|
|
|
@ -240,7 +240,6 @@ public:
|
|||
QVector<glm::vec3> vertices;
|
||||
QVector<glm::vec3> normals;
|
||||
QVector<glm::vec3> tangents;
|
||||
mutable QVector<NormalType> normalsAndTangents; // Populated later if needed for blendshapes
|
||||
QVector<glm::vec3> colors;
|
||||
QVector<glm::vec2> texCoords;
|
||||
QVector<glm::vec2> texCoords1;
|
||||
|
|
|
@ -201,9 +201,10 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
|
|||
{
|
||||
Vec2u outputSize{ 1,1 };
|
||||
|
||||
if (_output._framebuffer) {
|
||||
outputSize.x = _output._framebuffer->getWidth();
|
||||
outputSize.y = _output._framebuffer->getHeight();
|
||||
auto framebuffer = acquire(_output._framebuffer);
|
||||
if (framebuffer) {
|
||||
outputSize.x = framebuffer->getWidth();
|
||||
outputSize.y = framebuffer->getHeight();
|
||||
} else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) {
|
||||
qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set";
|
||||
}
|
||||
|
@ -220,6 +221,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
|
|||
_stereo._contextDisable = false;
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_setFramebuffer:
|
||||
case Batch::COMMAND_setViewportTransform:
|
||||
case Batch::COMMAND_setViewTransform:
|
||||
case Batch::COMMAND_setProjectionTransform:
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include "GLShared.h"
|
||||
|
||||
|
||||
// Different versions for the stereo drawcall
|
||||
// Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only
|
||||
#if defined(USE_GLES)
|
||||
|
@ -40,7 +39,6 @@
|
|||
#define GPU_STEREO_TECHNIQUE_INSTANCED
|
||||
#endif
|
||||
|
||||
|
||||
// Let these be configured by the one define picked above
|
||||
#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
|
||||
#define GPU_STEREO_DRAWCALL_DOUBLED
|
||||
|
@ -56,8 +54,153 @@
|
|||
#define GPU_STEREO_CAMERA_BUFFER
|
||||
#endif
|
||||
|
||||
//
|
||||
// GL Backend pointer storage mechanism
|
||||
// One of the following three defines must be defined.
|
||||
// GPU_POINTER_STORAGE_SHARED
|
||||
|
||||
// The platonic ideal, use references to smart pointers.
|
||||
// However, this produces artifacts because there are too many places in the code right now that
|
||||
// create temporary values (undesirable smart pointer duplications) and then those temp variables
|
||||
// get passed on and have their reference taken, and then invalidated
|
||||
// GPU_POINTER_STORAGE_REF
|
||||
|
||||
// Raw pointer manipulation. Seems more dangerous than the reference wrappers,
|
||||
// but in practice, the danger of grabbing a reference to a temporary variable
|
||||
// is causing issues
|
||||
// GPU_POINTER_STORAGE_RAW
|
||||
|
||||
#if defined(USE_GLES)
|
||||
#define GPU_POINTER_STORAGE_SHARED
|
||||
#else
|
||||
#define GPU_POINTER_STORAGE_RAW
|
||||
#endif
|
||||
|
||||
namespace gpu { namespace gl {
|
||||
|
||||
#if defined(GPU_POINTER_STORAGE_SHARED)
|
||||
template <typename T>
|
||||
static inline bool compare(const std::shared_ptr<T>& a, const std::shared_ptr<T>& b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* acquire(const std::shared_ptr<T>& pointer) {
|
||||
return pointer.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void reset(std::shared_ptr<T>& pointer) {
|
||||
return pointer.reset();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline bool valid(const std::shared_ptr<T>& pointer) {
|
||||
return pointer.operator bool();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void assign(std::shared_ptr<T>& pointer, const std::shared_ptr<T>& source) {
|
||||
pointer = source;
|
||||
}
|
||||
|
||||
using BufferReference = BufferPointer;
|
||||
using TextureReference = TexturePointer;
|
||||
using FramebufferReference = FramebufferPointer;
|
||||
using FormatReference = Stream::FormatPointer;
|
||||
using PipelineReference = PipelinePointer;
|
||||
|
||||
#define GPU_REFERENCE_INIT_VALUE nullptr
|
||||
|
||||
#elif defined(GPU_POINTER_STORAGE_REF)
|
||||
|
||||
template <typename T>
|
||||
class PointerReferenceWrapper : public std::reference_wrapper<const std::shared_ptr<T>> {
|
||||
using Parent = std::reference_wrapper<const std::shared_ptr<T>>;
|
||||
|
||||
public:
|
||||
using Pointer = std::shared_ptr<T>;
|
||||
PointerReferenceWrapper() : Parent(EMPTY()) {}
|
||||
PointerReferenceWrapper(const Pointer& pointer) : Parent(pointer) {}
|
||||
void clear() { *this = EMPTY(); }
|
||||
|
||||
private:
|
||||
static const Pointer& EMPTY() {
|
||||
static const Pointer EMPTY_VALUE;
|
||||
return EMPTY_VALUE;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static bool compare(const PointerReferenceWrapper<T>& reference, const std::shared_ptr<T>& pointer) {
|
||||
return reference.get() == pointer;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* acquire(const PointerReferenceWrapper<T>& reference) {
|
||||
return reference.get().get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void assign(PointerReferenceWrapper<T>& reference, const std::shared_ptr<T>& pointer) {
|
||||
reference = pointer;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool valid(const PointerReferenceWrapper<T>& reference) {
|
||||
return reference.get().operator bool();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void reset(PointerReferenceWrapper<T>& reference) {
|
||||
return reference.clear();
|
||||
}
|
||||
|
||||
using BufferReference = PointerReferenceWrapper<Buffer>;
|
||||
using TextureReference = PointerReferenceWrapper<Texture>;
|
||||
using FramebufferReference = PointerReferenceWrapper<Framebuffer>;
|
||||
using FormatReference = PointerReferenceWrapper<Stream::Format>;
|
||||
using PipelineReference = PointerReferenceWrapper<Pipeline>;
|
||||
|
||||
#define GPU_REFERENCE_INIT_VALUE
|
||||
|
||||
#elif defined(GPU_POINTER_STORAGE_RAW)
|
||||
|
||||
template <typename T>
|
||||
static bool compare(const T* const& rawPointer, const std::shared_ptr<T>& pointer) {
|
||||
return rawPointer == pointer.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* acquire(T*& rawPointer) {
|
||||
return rawPointer;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline bool valid(const T* const& rawPointer) {
|
||||
return rawPointer;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void reset(T*& rawPointer) {
|
||||
rawPointer = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline void assign(T*& rawPointer, const std::shared_ptr<T>& pointer) {
|
||||
rawPointer = pointer.get();
|
||||
}
|
||||
|
||||
using BufferReference = Buffer*;
|
||||
using TextureReference = Texture*;
|
||||
using FramebufferReference = Framebuffer*;
|
||||
using FormatReference = Stream::Format*;
|
||||
using PipelineReference = Pipeline*;
|
||||
|
||||
#define GPU_REFERENCE_INIT_VALUE nullptr
|
||||
|
||||
#endif
|
||||
|
||||
class GLBackend : public Backend, public std::enable_shared_from_this<GLBackend> {
|
||||
// Context Backend static interface required
|
||||
friend class gpu::Context;
|
||||
|
@ -67,8 +210,8 @@ class GLBackend : public Backend, public std::enable_shared_from_this<GLBackend>
|
|||
protected:
|
||||
explicit GLBackend(bool syncCache);
|
||||
GLBackend();
|
||||
public:
|
||||
|
||||
public:
|
||||
#if defined(USE_GLES)
|
||||
// https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glGet.xhtml
|
||||
static const GLint MIN_REQUIRED_TEXTURE_IMAGE_UNITS = 16;
|
||||
|
@ -109,8 +252,8 @@ public:
|
|||
// This is the ugly "download the pixels to sysmem for taking a snapshot"
|
||||
// Just avoid using it, it's ugly and will break performances
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer,
|
||||
const Vec4i& region, QImage& destImage) final override;
|
||||
|
||||
const Vec4i& region,
|
||||
QImage& destImage) final override;
|
||||
|
||||
// this is the maximum numeber of available input buffers
|
||||
size_t getNumInputBuffers() const { return _input._invalidBuffers.size(); }
|
||||
|
@ -131,7 +274,6 @@ public:
|
|||
static const int MAX_NUM_RESOURCE_TABLE_TEXTURES = 2;
|
||||
size_t getMaxNumResourceTextureTables() const { return MAX_NUM_RESOURCE_TABLE_TEXTURES; }
|
||||
|
||||
|
||||
// Draw Stage
|
||||
virtual void do_draw(const Batch& batch, size_t paramOffset) = 0;
|
||||
virtual void do_drawIndexed(const Batch& batch, size_t paramOffset) = 0;
|
||||
|
@ -183,7 +325,6 @@ public:
|
|||
// Reset stages
|
||||
virtual void do_resetStages(const Batch& batch, size_t paramOffset) final;
|
||||
|
||||
|
||||
virtual void do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) final;
|
||||
|
||||
|
@ -203,7 +344,7 @@ public:
|
|||
virtual void do_popProfileRange(const Batch& batch, size_t paramOffset) final;
|
||||
|
||||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final;
|
||||
|
@ -228,7 +369,9 @@ public:
|
|||
virtual void do_setStateAntialiasedLineEnable(bool enable) final;
|
||||
virtual void do_setStateDepthBias(Vec2 bias) final;
|
||||
virtual void do_setStateDepthTest(State::DepthTest test) final;
|
||||
virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final;
|
||||
virtual void do_setStateStencil(State::StencilActivation activation,
|
||||
State::StencilTest frontTest,
|
||||
State::StencilTest backTest) final;
|
||||
virtual void do_setStateAlphaToCoverageEnable(bool enable) final;
|
||||
virtual void do_setStateSampleMask(uint32 mask) final;
|
||||
virtual void do_setStateBlend(State::BlendFunction blendFunction) final;
|
||||
|
@ -257,7 +400,9 @@ public:
|
|||
virtual void releaseQuery(GLuint id) const;
|
||||
virtual void queueLambda(const std::function<void()> lambda) const;
|
||||
|
||||
bool isTextureManagementSparseEnabled() const override { return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures()); }
|
||||
bool isTextureManagementSparseEnabled() const override {
|
||||
return (_textureManagement._sparseCapable && Texture::getEnableSparseTextures());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual GLint getRealUniformLocation(GLint location) const;
|
||||
|
@ -266,11 +411,11 @@ protected:
|
|||
|
||||
// FIXME instead of a single flag, create a features struct similar to
|
||||
// https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkPhysicalDeviceFeatures.html
|
||||
virtual bool supportsBindless() const { return false; }
|
||||
virtual bool supportsBindless() const { return false; }
|
||||
|
||||
static const size_t INVALID_OFFSET = (size_t)-1;
|
||||
bool _inRenderTransferPass { false };
|
||||
int _currentDraw { -1 };
|
||||
bool _inRenderTransferPass{ false };
|
||||
int _currentDraw{ -1 };
|
||||
|
||||
std::list<std::string> profileRanges;
|
||||
mutable Mutex _trashMutex;
|
||||
|
@ -299,46 +444,36 @@ protected:
|
|||
virtual void updateInput() = 0;
|
||||
|
||||
struct InputStageState {
|
||||
bool _invalidFormat { true };
|
||||
bool _lastUpdateStereoState{ false };
|
||||
bool _invalidFormat{ true };
|
||||
bool _lastUpdateStereoState{ false };
|
||||
bool _hadColorAttribute{ true };
|
||||
Stream::FormatPointer _format;
|
||||
FormatReference _format{ GPU_REFERENCE_INIT_VALUE };
|
||||
std::string _formatKey;
|
||||
|
||||
typedef std::bitset<MAX_NUM_ATTRIBUTES> ActivationCache;
|
||||
ActivationCache _attributeActivation { 0 };
|
||||
ActivationCache _attributeActivation{ 0 };
|
||||
|
||||
typedef std::bitset<MAX_NUM_INPUT_BUFFERS> BuffersState;
|
||||
|
||||
BuffersState _invalidBuffers{ 0 };
|
||||
BuffersState _attribBindingBuffers{ 0 };
|
||||
|
||||
Buffers _buffers;
|
||||
Offsets _bufferOffsets;
|
||||
Offsets _bufferStrides;
|
||||
std::vector<GLuint> _bufferVBOs;
|
||||
std::array<BufferReference, MAX_NUM_INPUT_BUFFERS> _buffers{};
|
||||
std::array<Offset, MAX_NUM_INPUT_BUFFERS> _bufferOffsets{};
|
||||
std::array<Offset, MAX_NUM_INPUT_BUFFERS> _bufferStrides{};
|
||||
std::array<GLuint, MAX_NUM_INPUT_BUFFERS> _bufferVBOs{};
|
||||
|
||||
glm::vec4 _colorAttribute{ 0.0f };
|
||||
|
||||
BufferPointer _indexBuffer;
|
||||
Offset _indexBufferOffset { 0 };
|
||||
Type _indexBufferType { UINT32 };
|
||||
|
||||
BufferPointer _indirectBuffer;
|
||||
BufferReference _indexBuffer{};
|
||||
Offset _indexBufferOffset{ 0 };
|
||||
Type _indexBufferType{ UINT32 };
|
||||
|
||||
BufferReference _indirectBuffer{};
|
||||
Offset _indirectBufferOffset{ 0 };
|
||||
Offset _indirectBufferStride{ 0 };
|
||||
|
||||
GLuint _defaultVAO { 0 };
|
||||
|
||||
InputStageState() :
|
||||
_invalidFormat(true),
|
||||
_format(0),
|
||||
_formatKey(),
|
||||
_attributeActivation(0),
|
||||
_buffers(_invalidBuffers.size(), BufferPointer(0)),
|
||||
_bufferOffsets(_invalidBuffers.size(), 0),
|
||||
_bufferStrides(_invalidBuffers.size(), 0),
|
||||
_bufferVBOs(_invalidBuffers.size(), 0) {}
|
||||
GLuint _defaultVAO{ 0 };
|
||||
} _input;
|
||||
|
||||
virtual void initTransform() = 0;
|
||||
|
@ -349,7 +484,7 @@ protected:
|
|||
virtual void resetTransformStage();
|
||||
|
||||
// Allows for correction of the camera pose to account for changes
|
||||
// between the time when a was recorded and the time(s) when it is
|
||||
// between the time when a was recorded and the time(s) when it is
|
||||
// executed
|
||||
// Prev is the previous correction used at previous frame
|
||||
struct CameraCorrection {
|
||||
|
@ -364,9 +499,12 @@ protected:
|
|||
struct Cameras {
|
||||
TransformCamera _cams[2];
|
||||
|
||||
Cameras() {};
|
||||
Cameras(){};
|
||||
Cameras(const TransformCamera& cam) { memcpy(_cams, &cam, sizeof(TransformCamera)); };
|
||||
Cameras(const TransformCamera& camL, const TransformCamera& camR) { memcpy(_cams, &camL, sizeof(TransformCamera)); memcpy(_cams + 1, &camR, sizeof(TransformCamera)); };
|
||||
Cameras(const TransformCamera& camL, const TransformCamera& camR) {
|
||||
memcpy(_cams, &camL, sizeof(TransformCamera));
|
||||
memcpy(_cams + 1, &camR, sizeof(TransformCamera));
|
||||
};
|
||||
};
|
||||
|
||||
using CameraBufferElement = Cameras;
|
||||
|
@ -380,25 +518,24 @@ protected:
|
|||
|
||||
mutable std::map<std::string, GLvoid*> _drawCallInfoOffsets;
|
||||
|
||||
GLuint _objectBuffer { 0 };
|
||||
GLuint _cameraBuffer { 0 };
|
||||
GLuint _drawCallInfoBuffer { 0 };
|
||||
GLuint _objectBufferTexture { 0 };
|
||||
size_t _cameraUboSize { 0 };
|
||||
GLuint _objectBuffer{ 0 };
|
||||
GLuint _cameraBuffer{ 0 };
|
||||
GLuint _drawCallInfoBuffer{ 0 };
|
||||
GLuint _objectBufferTexture{ 0 };
|
||||
size_t _cameraUboSize{ 0 };
|
||||
bool _viewIsCamera{ false };
|
||||
bool _skybox { false };
|
||||
bool _skybox{ false };
|
||||
Transform _view;
|
||||
CameraCorrection _correction;
|
||||
bool _viewCorrectionEnabled{ true };
|
||||
|
||||
|
||||
Mat4 _projection;
|
||||
Vec4i _viewport { 0, 0, 1, 1 };
|
||||
Vec2 _depthRange { 0.0f, 1.0f };
|
||||
Vec4i _viewport{ 0, 0, 1, 1 };
|
||||
Vec2 _depthRange{ 0.0f, 1.0f };
|
||||
Vec2 _projectionJitter{ 0.0f, 0.0f };
|
||||
bool _invalidView { false };
|
||||
bool _invalidProj { false };
|
||||
bool _invalidViewport { false };
|
||||
bool _invalidView{ false };
|
||||
bool _invalidProj{ false };
|
||||
bool _invalidViewport{ false };
|
||||
|
||||
bool _enabledDrawcallInfoBuffer{ false };
|
||||
|
||||
|
@ -417,45 +554,54 @@ protected:
|
|||
|
||||
struct UniformStageState {
|
||||
struct BufferState {
|
||||
BufferPointer buffer;
|
||||
BufferReference buffer{};
|
||||
GLintptr offset{ 0 };
|
||||
GLsizeiptr size{ 0 };
|
||||
BufferState(const BufferPointer& buffer = nullptr, GLintptr offset = 0, GLsizeiptr size = 0);
|
||||
bool operator ==(BufferState& other) const {
|
||||
return offset == other.offset && size == other.size && buffer == other.buffer;
|
||||
|
||||
BufferState& operator=(const BufferState& other) = delete;
|
||||
void reset() {
|
||||
gpu::gl::reset(buffer);
|
||||
offset = 0;
|
||||
size = 0;
|
||||
}
|
||||
bool compare(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size) {
|
||||
const auto& self = *this;
|
||||
return (self.offset == offset && self.size == size && gpu::gl::compare(self.buffer, buffer));
|
||||
}
|
||||
};
|
||||
|
||||
// MAX_NUM_UNIFORM_BUFFERS-1 is the max uniform index BATCHES are allowed to set, but
|
||||
// MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some
|
||||
// internal UBOs for things like camera correction
|
||||
// MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS is used here because the backend sets some
|
||||
// internal UBOs for things like camera correction
|
||||
std::array<BufferState, MIN_REQUIRED_UNIFORM_BUFFER_BINDINGS> _buffers;
|
||||
} _uniform;
|
||||
|
||||
// Helper function that provides common code
|
||||
// Helper function that provides common code
|
||||
void bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GLintptr offset = 0, GLsizeiptr size = 0);
|
||||
void releaseUniformBuffer(uint32_t slot);
|
||||
void resetUniformStage();
|
||||
|
||||
// update resource cache and do the gl bind/unbind call with the current gpu::Buffer cached at slot s
|
||||
// This is using different gl object depending on the gl version
|
||||
virtual bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) = 0;
|
||||
virtual bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) = 0;
|
||||
virtual void releaseResourceBuffer(uint32_t slot) = 0;
|
||||
|
||||
// Helper function that provides common code used by do_setResourceTexture and
|
||||
// Helper function that provides common code used by do_setResourceTexture and
|
||||
// do_setResourceTextureTable (in non-bindless mode)
|
||||
void bindResourceTexture(uint32_t slot, const TexturePointer& texture);
|
||||
|
||||
|
||||
// update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s
|
||||
void releaseResourceTexture(uint32_t slot);
|
||||
|
||||
void resetResourceStage();
|
||||
|
||||
struct ResourceStageState {
|
||||
std::array<BufferPointer, MAX_NUM_RESOURCE_BUFFERS> _buffers;
|
||||
std::array<TexturePointer, MAX_NUM_RESOURCE_TEXTURES> _textures;
|
||||
//Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } };
|
||||
struct TextureState {
|
||||
TextureReference _texture{};
|
||||
GLenum _target;
|
||||
};
|
||||
std::array<BufferReference, MAX_NUM_RESOURCE_BUFFERS> _buffers{};
|
||||
std::array<TextureState, MAX_NUM_RESOURCE_TEXTURES> _textures{};
|
||||
int findEmptyTextureSlot() const;
|
||||
} _resource;
|
||||
|
||||
|
@ -470,21 +616,22 @@ protected:
|
|||
void resetPipelineStage();
|
||||
|
||||
struct PipelineStageState {
|
||||
PipelinePointer _pipeline;
|
||||
PipelineReference _pipeline{};
|
||||
|
||||
GLuint _program { 0 };
|
||||
bool _cameraCorrection { false };
|
||||
GLShader* _programShader { nullptr };
|
||||
bool _invalidProgram { false };
|
||||
GLuint _program{ 0 };
|
||||
bool _cameraCorrection{ false };
|
||||
GLShader* _programShader{ nullptr };
|
||||
bool _invalidProgram{ false };
|
||||
|
||||
BufferView _cameraCorrectionBuffer { gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(CameraCorrection), nullptr )) };
|
||||
BufferView _cameraCorrectionBufferIdentity { gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(CameraCorrection), nullptr )) };
|
||||
BufferView _cameraCorrectionBuffer{ gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(CameraCorrection), nullptr)) };
|
||||
BufferView _cameraCorrectionBufferIdentity{ gpu::BufferView(
|
||||
std::make_shared<gpu::Buffer>(sizeof(CameraCorrection), nullptr)) };
|
||||
|
||||
State::Data _stateCache{ State::DEFAULT };
|
||||
State::Signature _stateSignatureCache { 0 };
|
||||
State::Signature _stateSignatureCache{ 0 };
|
||||
|
||||
GLState* _state { nullptr };
|
||||
bool _invalidState { false };
|
||||
GLState* _state{ nullptr };
|
||||
bool _invalidState{ false };
|
||||
|
||||
PipelineStageState() {
|
||||
_cameraCorrectionBuffer.edit<CameraCorrection>() = CameraCorrection();
|
||||
|
@ -498,9 +645,9 @@ protected:
|
|||
virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler);
|
||||
virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler);
|
||||
virtual std::string getBackendShaderHeader() const = 0;
|
||||
// For a program, this will return a string containing all the source files (without any
|
||||
// backend headers or defines). For a vertex, fragment or geometry shader, this will
|
||||
// return the fully customized shader with all the version and backend specific
|
||||
// For a program, this will return a string containing all the source files (without any
|
||||
// backend headers or defines). For a vertex, fragment or geometry shader, this will
|
||||
// return the fully customized shader with all the version and backend specific
|
||||
// preprocessor directives
|
||||
// The program string returned can be used as a key for a cache of shader binaries
|
||||
// The shader strings can be reliably sent to the low level `compileShader` functions
|
||||
|
@ -516,22 +663,22 @@ protected:
|
|||
// Synchronize the state cache of this Backend with the actual real state of the GL Context
|
||||
void syncOutputStateCache();
|
||||
void resetOutputStage();
|
||||
|
||||
|
||||
struct OutputStageState {
|
||||
FramebufferPointer _framebuffer { nullptr };
|
||||
GLuint _drawFBO { 0 };
|
||||
FramebufferReference _framebuffer{};
|
||||
GLuint _drawFBO{ 0 };
|
||||
} _output;
|
||||
|
||||
void resetQueryStage();
|
||||
struct QueryStageState {
|
||||
uint32_t _rangeQueryDepth { 0 };
|
||||
uint32_t _rangeQueryDepth{ 0 };
|
||||
} _queryStage;
|
||||
|
||||
void resetStages();
|
||||
|
||||
// Stores cached binary versions of the shaders for quicker startup on subsequent runs
|
||||
// Note that shaders in the cache can still fail to load due to hardware or driver
|
||||
// changes that invalidate the cached binary, in which case we fall back on compiling
|
||||
// Note that shaders in the cache can still fail to load due to hardware or driver
|
||||
// changes that invalidate the cached binary, in which case we fall back on compiling
|
||||
// the source again
|
||||
struct ShaderBinaryCache {
|
||||
std::mutex _mutex;
|
||||
|
@ -543,7 +690,7 @@ protected:
|
|||
virtual void killShaderBinaryCache();
|
||||
|
||||
struct TextureManagementStageState {
|
||||
bool _sparseCapable { false };
|
||||
bool _sparseCapable{ false };
|
||||
GLTextureTransferEnginePointer _transferEngine;
|
||||
} _textureManagement;
|
||||
virtual void initTextureManagementStage();
|
||||
|
@ -556,6 +703,6 @@ protected:
|
|||
friend class GLShader;
|
||||
};
|
||||
|
||||
} }
|
||||
}} // namespace gpu::gl
|
||||
|
||||
#endif
|
||||
|
|
|
@ -17,10 +17,10 @@ using namespace gpu;
|
|||
using namespace gpu::gl;
|
||||
|
||||
void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) {
|
||||
Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint);
|
||||
if (format != _input._format) {
|
||||
_input._format = format;
|
||||
const auto& format = batch._streamFormats.get(batch._params[paramOffset]._uint);
|
||||
if (!compare(_input._format, format)) {
|
||||
if (format) {
|
||||
assign(_input._format, format);
|
||||
auto inputFormat = GLInputFormat::sync((*format));
|
||||
assert(inputFormat);
|
||||
if (_input._formatKey != inputFormat->key) {
|
||||
|
@ -28,6 +28,7 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) {
|
|||
_input._invalidFormat = true;
|
||||
}
|
||||
} else {
|
||||
reset(_input._format);
|
||||
_input._formatKey.clear();
|
||||
_input._invalidFormat = true;
|
||||
}
|
||||
|
@ -37,13 +38,13 @@ void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) {
|
|||
void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) {
|
||||
Offset stride = batch._params[paramOffset + 0]._uint;
|
||||
Offset offset = batch._params[paramOffset + 1]._uint;
|
||||
BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
const auto& buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
uint32 channel = batch._params[paramOffset + 3]._uint;
|
||||
|
||||
if (channel < getNumInputBuffers()) {
|
||||
bool isModified = false;
|
||||
if (_input._buffers[channel] != buffer) {
|
||||
_input._buffers[channel] = buffer;
|
||||
if (!compare(_input._buffers[channel], buffer)) {
|
||||
assign(_input._buffers[channel], buffer);
|
||||
_input._bufferVBOs[channel] = getBufferIDUnsynced((*buffer));
|
||||
isModified = true;
|
||||
}
|
||||
|
@ -94,18 +95,18 @@ void GLBackend::resetInputStage() {
|
|||
// Reset index buffer
|
||||
_input._indexBufferType = UINT32;
|
||||
_input._indexBufferOffset = 0;
|
||||
_input._indexBuffer.reset();
|
||||
reset(_input._indexBuffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
(void) CHECK_GL_ERROR();
|
||||
|
||||
// Reset vertex buffer and format
|
||||
_input._format.reset();
|
||||
reset(_input._format);
|
||||
_input._formatKey.clear();
|
||||
_input._invalidFormat = false;
|
||||
_input._attributeActivation.reset();
|
||||
|
||||
for (uint32_t i = 0; i < _input._buffers.size(); i++) {
|
||||
_input._buffers[i].reset();
|
||||
reset(_input._buffers[i]);
|
||||
_input._bufferOffsets[i] = 0;
|
||||
_input._bufferStrides[i] = 0;
|
||||
_input._bufferVBOs[i] = 0;
|
||||
|
@ -119,9 +120,9 @@ void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) {
|
|||
_input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint;
|
||||
_input._indexBufferOffset = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
BufferPointer indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint);
|
||||
if (indexBuffer != _input._indexBuffer) {
|
||||
_input._indexBuffer = indexBuffer;
|
||||
const auto& indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint);
|
||||
if (!compare(_input._indexBuffer, indexBuffer)) {
|
||||
assign(_input._indexBuffer, indexBuffer);
|
||||
if (indexBuffer) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferIDUnsynced(*indexBuffer));
|
||||
} else {
|
||||
|
@ -136,9 +137,9 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) {
|
|||
_input._indirectBufferOffset = batch._params[paramOffset + 1]._uint;
|
||||
_input._indirectBufferStride = batch._params[paramOffset + 2]._uint;
|
||||
|
||||
BufferPointer buffer = batch._buffers.get(batch._params[paramOffset]._uint);
|
||||
if (buffer != _input._indirectBuffer) {
|
||||
_input._indirectBuffer = buffer;
|
||||
const auto& buffer = batch._buffers.get(batch._params[paramOffset]._uint);
|
||||
if (!compare(_input._indirectBuffer, buffer)) {
|
||||
assign(_input._indirectBuffer, buffer);
|
||||
if (buffer) {
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferIDUnsynced(*buffer));
|
||||
} else {
|
||||
|
@ -152,7 +153,7 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) {
|
|||
|
||||
void GLBackend::updateInput() {
|
||||
bool isStereoNow = isStereo();
|
||||
// track stereo state change potentially happening wihtout changing the input format
|
||||
// track stereo state change potentially happening without changing the input format
|
||||
// this is a rare case requesting to invalid the format
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
_input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState);
|
||||
|
@ -163,13 +164,14 @@ void GLBackend::updateInput() {
|
|||
InputStageState::ActivationCache newActivation;
|
||||
|
||||
// Assign the vertex format required
|
||||
if (_input._format) {
|
||||
auto format = acquire(_input._format);
|
||||
if (format) {
|
||||
bool hasColorAttribute{ false };
|
||||
|
||||
_input._attribBindingBuffers.reset();
|
||||
|
||||
const Stream::Format::AttributeMap& attributes = _input._format->getAttributes();
|
||||
auto& inputChannels = _input._format->getChannels();
|
||||
const auto& attributes = format->getAttributes();
|
||||
const auto& inputChannels = format->getChannels();
|
||||
for (auto& channelIt : inputChannels) {
|
||||
auto bufferChannelNum = (channelIt).first;
|
||||
const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second;
|
||||
|
|
|
@ -27,12 +27,12 @@ void GLBackend::syncOutputStateCache() {
|
|||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO);
|
||||
|
||||
_output._drawFBO = currentFBO;
|
||||
_output._framebuffer.reset();
|
||||
reset(_output._framebuffer);
|
||||
}
|
||||
|
||||
void GLBackend::resetOutputStage() {
|
||||
if (_output._framebuffer) {
|
||||
_output._framebuffer.reset();
|
||||
if (valid(_output._framebuffer)) {
|
||||
reset(_output._framebuffer);
|
||||
_output._drawFBO = 0;
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ void GLBackend::resetOutputStage() {
|
|||
}
|
||||
|
||||
void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) {
|
||||
auto framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint);
|
||||
const auto& framebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint);
|
||||
setFramebuffer(framebuffer);
|
||||
}
|
||||
|
||||
|
@ -55,13 +55,13 @@ void GLBackend::do_setFramebufferSwapChain(const Batch& batch, size_t paramOffse
|
|||
}
|
||||
|
||||
void GLBackend::setFramebuffer(const FramebufferPointer& framebuffer) {
|
||||
if (_output._framebuffer != framebuffer) {
|
||||
if (!compare(_output._framebuffer, framebuffer)) {
|
||||
auto newFBO = getFramebufferID(framebuffer);
|
||||
if (_output._drawFBO != newFBO) {
|
||||
_output._drawFBO = newFBO;
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newFBO);
|
||||
}
|
||||
_output._framebuffer = framebuffer;
|
||||
assign(_output._framebuffer, framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,8 +114,9 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
std::vector<GLenum> drawBuffers;
|
||||
auto framebuffer = acquire(_output._framebuffer);
|
||||
if (masks & Framebuffer::BUFFER_COLORS) {
|
||||
if (_output._framebuffer) {
|
||||
if (framebuffer) {
|
||||
for (unsigned int i = 0; i < Framebuffer::MAX_NUM_RENDER_BUFFERS; i++) {
|
||||
if (masks & (1 << i)) {
|
||||
drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
|
||||
|
@ -163,8 +164,8 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
// Restore the color draw buffers only if a frmaebuffer is bound
|
||||
if (_output._framebuffer && !drawBuffers.empty()) {
|
||||
auto glFramebuffer = syncGPUObject(*_output._framebuffer);
|
||||
if (framebuffer && !drawBuffers.empty()) {
|
||||
auto glFramebuffer = syncGPUObject(*framebuffer);
|
||||
if (glFramebuffer) {
|
||||
glDrawBuffers((GLsizei)glFramebuffer->_colorBuffers.size(), glFramebuffer->_colorBuffers.data());
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ using namespace gpu;
|
|||
using namespace gpu::gl;
|
||||
|
||||
void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) {
|
||||
PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint);
|
||||
const auto& pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint);
|
||||
|
||||
if (_pipeline._pipeline == pipeline) {
|
||||
if (compare(_pipeline._pipeline, pipeline)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) {
|
|||
|
||||
// null pipeline == reset
|
||||
if (!pipeline) {
|
||||
_pipeline._pipeline.reset();
|
||||
reset(_pipeline._pipeline);
|
||||
|
||||
_pipeline._program = 0;
|
||||
_pipeline._cameraCorrection = false;
|
||||
|
@ -73,7 +73,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
// Remember the new pipeline
|
||||
_pipeline._pipeline = pipeline;
|
||||
assign(_pipeline._pipeline, pipeline);
|
||||
}
|
||||
|
||||
// THis should be done on Pipeline::update...
|
||||
|
@ -81,7 +81,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) {
|
|||
glUseProgram(_pipeline._program);
|
||||
if (_pipeline._cameraCorrection) {
|
||||
// Invalidate uniform buffer cache slot
|
||||
_uniform._buffers[gpu::slot::buffer::CameraCorrection] = {};
|
||||
_uniform._buffers[gpu::slot::buffer::CameraCorrection].reset();
|
||||
auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ?
|
||||
_pipeline._cameraCorrectionBuffer._buffer :
|
||||
_pipeline._cameraCorrectionBufferIdentity._buffer;
|
||||
|
@ -112,7 +112,7 @@ void GLBackend::updatePipeline() {
|
|||
_pipeline._stateSignatureCache |= _pipeline._state->_signature;
|
||||
|
||||
// And perform
|
||||
for (auto command : _pipeline._state->_commands) {
|
||||
for (const auto& command : _pipeline._state->_commands) {
|
||||
command->run(this);
|
||||
}
|
||||
} else {
|
||||
|
@ -134,23 +134,17 @@ void GLBackend::resetPipelineStage() {
|
|||
_pipeline._invalidProgram = false;
|
||||
_pipeline._program = 0;
|
||||
_pipeline._programShader = nullptr;
|
||||
_pipeline._pipeline.reset();
|
||||
reset(_pipeline._pipeline);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
GLBackend::UniformStageState::BufferState::BufferState(const BufferPointer& buffer, GLintptr offset, GLsizeiptr size)
|
||||
: buffer(buffer), offset(offset), size(size) {}
|
||||
|
||||
void GLBackend::releaseUniformBuffer(uint32_t slot) {
|
||||
auto& buf = _uniform._buffers[slot];
|
||||
if (buf.buffer) {
|
||||
auto* object = Backend::getGPUObject<GLBuffer>(*buf.buffer);
|
||||
if (object) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
buf = UniformStageState::BufferState();
|
||||
auto& bufferState = _uniform._buffers[slot];
|
||||
if (valid(bufferState.buffer)) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
bufferState.reset();
|
||||
}
|
||||
|
||||
void GLBackend::resetUniformStage() {
|
||||
|
@ -165,18 +159,20 @@ void GLBackend::bindUniformBuffer(uint32_t slot, const BufferPointer& buffer, GL
|
|||
return;
|
||||
}
|
||||
|
||||
UniformStageState::BufferState bufferState{ buffer, offset, size };
|
||||
|
||||
auto& currentBufferState = _uniform._buffers[slot];
|
||||
// check cache before thinking
|
||||
if (_uniform._buffers[slot] == bufferState) {
|
||||
if (currentBufferState.compare(buffer, offset, size)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the true gl Buffer object
|
||||
auto glBO = getBufferIDUnsynced(*buffer);
|
||||
if (glBO) {
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBO, bufferState.offset, bufferState.size);
|
||||
_uniform._buffers[slot] = bufferState;
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBO, offset, size);
|
||||
assign(currentBufferState.buffer, buffer);
|
||||
currentBufferState.offset = offset;
|
||||
currentBufferState.size = size;
|
||||
(void)CHECK_GL_ERROR();
|
||||
} else {
|
||||
releaseUniformBuffer(slot);
|
||||
|
@ -193,7 +189,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) {
|
|||
return;
|
||||
}
|
||||
|
||||
BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
const auto& uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
GLintptr rangeStart = batch._params[paramOffset + 1]._uint;
|
||||
GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
|
@ -201,16 +197,12 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) {
|
|||
}
|
||||
|
||||
void GLBackend::releaseResourceTexture(uint32_t slot) {
|
||||
auto& tex = _resource._textures[slot];
|
||||
if (tex) {
|
||||
auto* object = Backend::getGPUObject<GLTexture>(*tex);
|
||||
if (object) {
|
||||
GLuint target = object->_target;
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(target, 0); // RELEASE
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
tex.reset();
|
||||
auto& textureState = _resource._textures[slot];
|
||||
if (valid(textureState._texture)) {
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(textureState._target, 0); // RELEASE
|
||||
(void)CHECK_GL_ERROR();
|
||||
reset(textureState._texture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,14 +224,14 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint);
|
||||
const auto& resourceBuffer = batch._buffers.get(batch._params[paramOffset + 0]._uint);
|
||||
|
||||
if (!resourceBuffer) {
|
||||
releaseResourceBuffer(slot);
|
||||
return;
|
||||
}
|
||||
// check cache before thinking
|
||||
if (_resource._buffers[slot] == resourceBuffer) {
|
||||
if (compare(_resource._buffers[slot], resourceBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -248,7 +240,7 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) {
|
|||
|
||||
// If successful bind then cache it
|
||||
if (bindResourceBuffer(slot, resourceBuffer)) {
|
||||
_resource._buffers[slot] = resourceBuffer;
|
||||
assign(_resource._buffers[slot], resourceBuffer);
|
||||
} else { // else clear slot and cache
|
||||
releaseResourceBuffer(slot);
|
||||
return;
|
||||
|
@ -293,14 +285,15 @@ void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, si
|
|||
}
|
||||
auto index = batch._params[paramOffset + 2]._uint;
|
||||
auto renderBufferSlot = batch._params[paramOffset + 3]._uint;
|
||||
auto resourceFramebuffer = swapChain->get(index);
|
||||
auto resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot);
|
||||
const auto& resourceFramebuffer = swapChain->get(index);
|
||||
const auto& resourceTexture = resourceFramebuffer->getRenderBuffer(renderBufferSlot);
|
||||
setResourceTexture(slot, resourceTexture);
|
||||
}
|
||||
|
||||
void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& resourceTexture) {
|
||||
auto& textureState = _resource._textures[slot];
|
||||
// check cache before thinking
|
||||
if (_resource._textures[slot] == resourceTexture) {
|
||||
if (compare(textureState._texture, resourceTexture)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -310,15 +303,12 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso
|
|||
// Always make sure the GLObject is in sync
|
||||
GLTexture* object = syncGPUObject(resourceTexture);
|
||||
if (object) {
|
||||
assign(textureState._texture, resourceTexture);
|
||||
GLuint to = object->_texture;
|
||||
GLuint target = object->_target;
|
||||
textureState._target = object->_target;
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(target, to);
|
||||
|
||||
glBindTexture(textureState._target, to);
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_resource._textures[slot] = resourceTexture;
|
||||
|
||||
_stats._RSAmountTextureMemoryBounded += (int)object->size();
|
||||
|
||||
} else {
|
||||
|
@ -343,7 +333,7 @@ void GLBackend::do_setResourceTextureTable(const Batch& batch, size_t paramOffse
|
|||
int GLBackend::ResourceStageState::findEmptyTextureSlot() const {
|
||||
// start from the end of the slots, try to find an empty one that can be used
|
||||
for (auto i = MAX_NUM_RESOURCE_TEXTURES - 1; i > 0; i--) {
|
||||
if (!_textures[i]) {
|
||||
if (!valid(_textures[i]._texture)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer) {
|
|||
}
|
||||
|
||||
void GLBackend::do_generateTextureMips(const Batch& batch, size_t paramOffset) {
|
||||
TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
|
||||
const auto& resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint);
|
||||
if (!resourceTexture) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) {
|
|||
}
|
||||
|
||||
// No object allocated yet, let's see if it's worth it...
|
||||
ShaderPointer shader = pipeline.getProgram();
|
||||
const auto& shader = pipeline.getProgram();
|
||||
|
||||
// If this pipeline's shader has already failed to compile, don't try again
|
||||
if (shader->compilationHasFailed()) {
|
||||
|
@ -37,7 +37,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
StatePointer state = pipeline.getState();
|
||||
const auto& state = pipeline.getState();
|
||||
GLState* stateObject = GLState::sync(*state);
|
||||
if (stateObject == nullptr) {
|
||||
return nullptr;
|
||||
|
|
|
@ -161,7 +161,7 @@ protected:
|
|||
void updateTransform(const Batch& batch) override;
|
||||
|
||||
// Resource Stage
|
||||
bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override;
|
||||
bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override;
|
||||
void releaseResourceBuffer(uint32_t slot) override;
|
||||
|
||||
// Output stage
|
||||
|
|
|
@ -100,7 +100,7 @@ GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) {
|
|||
return GL41Buffer::sync<GL41Buffer>(*this, buffer);
|
||||
}
|
||||
|
||||
bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
||||
bool GL41Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) {
|
||||
GLuint texBuffer = GL41Backend::getResourceBufferID((*buffer));
|
||||
if (texBuffer) {
|
||||
glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot);
|
||||
|
@ -108,7 +108,7 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
|||
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_resource._buffers[slot] = buffer;
|
||||
assign(_resource._buffers[slot], buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -117,10 +117,11 @@ bool GL41Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
|||
}
|
||||
|
||||
void GL41Backend::releaseResourceBuffer(uint32_t slot) {
|
||||
auto& buf = _resource._buffers[slot];
|
||||
if (buf) {
|
||||
auto& bufferReference = _resource._buffers[slot];
|
||||
auto buffer = acquire(bufferReference);
|
||||
if (buffer) {
|
||||
glActiveTexture(GL_TEXTURE0 + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT + slot);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, 0);
|
||||
buf.reset();
|
||||
reset(bufferReference);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,14 +35,15 @@ void GL41Backend::updateInput() {
|
|||
|
||||
if (_input._invalidFormat || _input._invalidBuffers.any()) {
|
||||
|
||||
auto format = acquire(_input._format);
|
||||
if (_input._invalidFormat) {
|
||||
InputStageState::ActivationCache newActivation;
|
||||
|
||||
_stats._ISNumFormatChanges++;
|
||||
|
||||
// Check expected activation
|
||||
if (_input._format) {
|
||||
for (auto& it : _input._format->getAttributes()) {
|
||||
if (format) {
|
||||
for (auto& it : format->getAttributes()) {
|
||||
const Stream::Attribute& attrib = (it).second;
|
||||
uint8_t locationCount = attrib._element.getLocationCount();
|
||||
for (int i = 0; i < locationCount; ++i) {
|
||||
|
@ -69,15 +70,15 @@ void GL41Backend::updateInput() {
|
|||
}
|
||||
|
||||
// now we need to bind the buffers and assign the attrib pointers
|
||||
if (_input._format) {
|
||||
if (format) {
|
||||
bool hasColorAttribute{ false };
|
||||
|
||||
const Buffers& buffers = _input._buffers;
|
||||
const Offsets& offsets = _input._bufferOffsets;
|
||||
const Offsets& strides = _input._bufferStrides;
|
||||
const auto& buffers = _input._buffers;
|
||||
const auto& offsets = _input._bufferOffsets;
|
||||
const auto& strides = _input._bufferStrides;
|
||||
|
||||
const Stream::Format::AttributeMap& attributes = _input._format->getAttributes();
|
||||
auto& inputChannels = _input._format->getChannels();
|
||||
const auto& attributes = format->getAttributes();
|
||||
const auto& inputChannels = format->getChannels();
|
||||
int numInvalids = (int)_input._invalidBuffers.count();
|
||||
_stats._ISNumInputBufferChanges += numInvalids;
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ protected:
|
|||
void updateTransform(const Batch& batch) override;
|
||||
|
||||
// Resource Stage
|
||||
bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override;
|
||||
bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override;
|
||||
void releaseResourceBuffer(uint32_t slot) override;
|
||||
|
||||
// Output stage
|
||||
|
|
|
@ -60,14 +60,14 @@ GLBuffer* GL45Backend::syncGPUObject(const Buffer& buffer) {
|
|||
}
|
||||
|
||||
|
||||
bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
||||
bool GL45Backend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) {
|
||||
GLBuffer* object = syncGPUObject((*buffer));
|
||||
if (object) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id);
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_resource._buffers[slot] = buffer;
|
||||
assign(_resource._buffers[slot], buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -76,11 +76,10 @@ bool GL45Backend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
|||
}
|
||||
|
||||
void GL45Backend::releaseResourceBuffer(uint32_t slot) {
|
||||
auto& buf = _resource._buffers[slot];
|
||||
if (buf) {
|
||||
auto& bufferReference = _resource._buffers[slot];
|
||||
auto buffer = acquire(bufferReference);
|
||||
if (buffer) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0);
|
||||
buf.reset();
|
||||
reset(bufferReference);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ void GL45Backend::resetInputStage() {
|
|||
|
||||
void GL45Backend::updateInput() {
|
||||
bool isStereoNow = isStereo();
|
||||
// track stereo state change potentially happening wihtout changing the input format
|
||||
// track stereo state change potentially happening without changing the input format
|
||||
// this is a rare case requesting to invalid the format
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
_input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState);
|
||||
|
@ -39,13 +39,14 @@ void GL45Backend::updateInput() {
|
|||
InputStageState::ActivationCache newActivation;
|
||||
|
||||
// Assign the vertex format required
|
||||
if (_input._format) {
|
||||
auto format = acquire(_input._format);
|
||||
if (format) {
|
||||
bool hasColorAttribute{ false };
|
||||
|
||||
_input._attribBindingBuffers.reset();
|
||||
|
||||
const Stream::Format::AttributeMap& attributes = _input._format->getAttributes();
|
||||
auto& inputChannels = _input._format->getChannels();
|
||||
const auto& attributes = format->getAttributes();
|
||||
const auto& inputChannels = format->getChannels();
|
||||
for (auto& channelIt : inputChannels) {
|
||||
auto bufferChannelNum = (channelIt).first;
|
||||
const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second;
|
||||
|
|
|
@ -157,7 +157,7 @@ protected:
|
|||
void updateTransform(const Batch& batch) override;
|
||||
|
||||
// Resource Stage
|
||||
bool bindResourceBuffer(uint32_t slot, BufferPointer& buffer) override;
|
||||
bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override;
|
||||
void releaseResourceBuffer(uint32_t slot) override;
|
||||
|
||||
// Output stage
|
||||
|
|
|
@ -72,14 +72,14 @@ GLBuffer* GLESBackend::syncGPUObject(const Buffer& buffer) {
|
|||
return GLESBuffer::sync<GLESBuffer>(*this, buffer);
|
||||
}
|
||||
|
||||
bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
||||
bool GLESBackend::bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) {
|
||||
GLBuffer* object = syncGPUObject((*buffer));
|
||||
if (object) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, object->_id);
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_resource._buffers[slot] = buffer;
|
||||
assign(_resource._buffers[slot], buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -88,10 +88,10 @@ bool GLESBackend::bindResourceBuffer(uint32_t slot, BufferPointer& buffer) {
|
|||
}
|
||||
|
||||
void GLESBackend::releaseResourceBuffer(uint32_t slot) {
|
||||
auto& buf = _resource._buffers[slot];
|
||||
if (buf) {
|
||||
auto& bufferReference = _resource._buffers[slot];
|
||||
if (valid(bufferReference)) {
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, slot, 0);
|
||||
buf.reset();
|
||||
reset(bufferReference);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -498,7 +498,7 @@ void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Fun
|
|||
captureNamedDrawCallInfo(instanceName);
|
||||
}
|
||||
|
||||
BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) {
|
||||
const BufferPointer& Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) {
|
||||
NamedBatchData& instance = _namedData[instanceName];
|
||||
if (instance.buffers.size() <= index) {
|
||||
instance.buffers.resize(index + 1);
|
||||
|
|
|
@ -119,7 +119,7 @@ public:
|
|||
void multiDrawIndexedIndirect(uint32 numCommands, Primitive primitiveType);
|
||||
|
||||
void setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function);
|
||||
BufferPointer getNamedBuffer(const std::string& instanceName, uint8_t index = 0);
|
||||
const BufferPointer& getNamedBuffer(const std::string& instanceName, uint8_t index = 0);
|
||||
|
||||
// Input Stage
|
||||
// InputFormat
|
||||
|
|
|
@ -203,11 +203,12 @@ uint32 Framebuffer::getNumRenderBuffers() const {
|
|||
return nb;
|
||||
}
|
||||
|
||||
TexturePointer Framebuffer::getRenderBuffer(uint32 slot) const {
|
||||
const TexturePointer& Framebuffer::getRenderBuffer(uint32 slot) const {
|
||||
static const TexturePointer EMPTY;
|
||||
if (!isSwapchain() && (slot < getMaxNumRenderBuffers())) {
|
||||
return _renderBuffers[slot]._texture;
|
||||
} else {
|
||||
return TexturePointer();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -297,9 +298,10 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For
|
|||
return false;
|
||||
}
|
||||
|
||||
TexturePointer Framebuffer::getDepthStencilBuffer() const {
|
||||
const TexturePointer& Framebuffer::getDepthStencilBuffer() const {
|
||||
static const TexturePointer EMPTY;
|
||||
if (isSwapchain()) {
|
||||
return TexturePointer();
|
||||
return EMPTY;
|
||||
} else {
|
||||
return _depthStencilBuffer._texture;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ public:
|
|||
static Framebuffer* createShadowmap(uint16 width);
|
||||
|
||||
bool isSwapchain() const;
|
||||
SwapchainPointer getSwapchain() const { return _swapchain; }
|
||||
const SwapchainPointer& getSwapchain() const { return _swapchain; }
|
||||
|
||||
uint32 getFrameCount() const;
|
||||
|
||||
|
@ -105,13 +105,13 @@ public:
|
|||
const TextureViews& getRenderBuffers() const { return _renderBuffers; }
|
||||
|
||||
int32 setRenderBuffer(uint32 slot, const TexturePointer& texture, uint32 subresource = 0);
|
||||
TexturePointer getRenderBuffer(uint32 slot) const;
|
||||
const TexturePointer& getRenderBuffer(uint32 slot) const;
|
||||
uint32 getRenderBufferSubresource(uint32 slot) const;
|
||||
|
||||
bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
|
||||
bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
|
||||
bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0);
|
||||
TexturePointer getDepthStencilBuffer() const;
|
||||
const TexturePointer& getDepthStencilBuffer() const;
|
||||
uint32 getDepthStencilBufferSubresource() const;
|
||||
Format getDepthStencilBufferFormat() const;
|
||||
|
||||
|
|
|
@ -816,8 +816,10 @@ bool AddressManager::setDomainInfo(const QUrl& domainURL, LookupTrigger trigger)
|
|||
const QString hostname = domainURL.host();
|
||||
quint16 port = domainURL.port();
|
||||
bool emitHostChanged { false };
|
||||
// Check if domain handler is in error state. always emit host changed if true.
|
||||
bool isInErrorState = DependencyManager::get<NodeList>()->getDomainHandler().isInErrorState();
|
||||
|
||||
if (domainURL != _domainURL) {
|
||||
if (domainURL != _domainURL || isInErrorState) {
|
||||
addCurrentAddressToHistory(trigger);
|
||||
emitHostChanged = true;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ DomainHandler::DomainHandler(QObject* parent) :
|
|||
|
||||
// stop the refresh timer if we connect to a domain
|
||||
connect(this, &DomainHandler::connectedToDomain, &_apiRefreshTimer, &QTimer::stop);
|
||||
|
||||
// stop the refresh timer if redirected to the error domain
|
||||
connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop);
|
||||
}
|
||||
|
||||
void DomainHandler::disconnect() {
|
||||
|
@ -99,7 +102,6 @@ void DomainHandler::softReset() {
|
|||
|
||||
clearSettings();
|
||||
|
||||
_isInErrorState = false;
|
||||
_connectionDenialsSinceKeypairRegen = 0;
|
||||
_checkInPacketsSinceLastReply = 0;
|
||||
|
||||
|
@ -107,13 +109,16 @@ void DomainHandler::softReset() {
|
|||
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
||||
|
||||
// restart the API refresh timer in case we fail to connect and need to refresh information
|
||||
QMetaObject::invokeMethod(&_apiRefreshTimer, "start");
|
||||
if (!_isInErrorState) {
|
||||
QMetaObject::invokeMethod(&_apiRefreshTimer, "start");
|
||||
}
|
||||
}
|
||||
|
||||
void DomainHandler::hardReset() {
|
||||
emit resetting();
|
||||
|
||||
softReset();
|
||||
_isInErrorState = false;
|
||||
|
||||
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
|
||||
_pendingDomainID = QUuid();
|
||||
|
@ -338,6 +343,7 @@ void DomainHandler::loadedErrorDomain(std::map<QString, QString> namedPaths) {
|
|||
void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) {
|
||||
_errorDomainURL = errorUrl;
|
||||
_lastDomainConnectionError = reasonCode;
|
||||
_isInErrorState = true;
|
||||
emit redirectToErrorDomainURL(_errorDomainURL);
|
||||
}
|
||||
|
||||
|
@ -480,9 +486,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
|
||||
#else
|
||||
if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
|
||||
_isInErrorState = true;
|
||||
// ingest the error - this is a "hard" connection refusal.
|
||||
emit redirectToErrorDomainURL(_errorDomainURL);
|
||||
setRedirectErrorState(_errorDomainURL, (int)reasonCode);
|
||||
} else {
|
||||
emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
|
||||
}
|
||||
|
|
|
@ -87,8 +87,6 @@ public:
|
|||
void connectedToServerless(std::map<QString, QString> namedPaths);
|
||||
|
||||
void loadedErrorDomain(std::map<QString, QString> namedPaths);
|
||||
// sets domain handler in error state.
|
||||
void setRedirectErrorState(QUrl errorUrl, int reasonCode);
|
||||
|
||||
QString getViewPointFromNamedPath(QString namedPath);
|
||||
|
||||
|
@ -172,6 +170,11 @@ public slots:
|
|||
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
|
||||
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
// sets domain handler in error state.
|
||||
void setRedirectErrorState(QUrl errorUrl, int reasonCode);
|
||||
|
||||
bool isInErrorState() { return _isInErrorState; }
|
||||
|
||||
private slots:
|
||||
void completedHostnameLookup(const QHostInfo& hostInfo);
|
||||
void completedIceServerHostnameLookup();
|
||||
|
|
|
@ -37,6 +37,7 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) :
|
|||
_shouldChangeSocketOptions(shouldChangeSocketOptions)
|
||||
{
|
||||
connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams);
|
||||
connect(this, &Socket::pendingDatagrams, this, &Socket::processPendingDatagrams, Qt::QueuedConnection);
|
||||
|
||||
// make sure we hear about errors and state changes from the underlying socket
|
||||
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
|
@ -315,55 +316,85 @@ void Socket::checkForReadyReadBackup() {
|
|||
}
|
||||
|
||||
void Socket::readPendingDatagrams() {
|
||||
int packetsRead = 0;
|
||||
|
||||
int packetSizeWithHeader = -1;
|
||||
|
||||
while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) {
|
||||
|
||||
// we're reading a packet so re-start the readyRead backup timer
|
||||
_readyReadBackupTimer->start();
|
||||
|
||||
// Max datagrams to read before processing:
|
||||
static const int MAX_DATAGRAMS_CONSECUTIVELY = 10000;
|
||||
while (_udpSocket.hasPendingDatagrams()
|
||||
&& (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1
|
||||
&& packetsRead <= MAX_DATAGRAMS_CONSECUTIVELY) {
|
||||
// grab a time point we can mark as the receive time of this packet
|
||||
auto receiveTime = p_high_resolution_clock::now();
|
||||
|
||||
// setup a HifiSockAddr to read into
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
// setup a buffer to read the packet into
|
||||
auto buffer = std::unique_ptr<char[]>(new char[packetSizeWithHeader]);
|
||||
|
||||
QHostAddress senderAddress;
|
||||
quint16 senderPort;
|
||||
|
||||
// pull the datagram
|
||||
auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader,
|
||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
||||
&senderAddress, &senderPort);
|
||||
|
||||
// save information for this packet, in case it is the one that sticks readyRead
|
||||
_lastPacketSizeRead = sizeRead;
|
||||
_lastPacketSockAddr = senderSockAddr;
|
||||
|
||||
if (sizeRead <= 0) {
|
||||
// we either didn't pull anything for this packet or there was an error reading (this seems to trigger
|
||||
// on windows even if there's not a packet available)
|
||||
// we either didn't pull anything for this packet or there was an error reading (this seems to trigger
|
||||
// on windows even if there's not a packet available)
|
||||
if (sizeRead < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = _unfilteredHandlers.find(senderSockAddr);
|
||||
_incomingDatagrams.push_back({ senderAddress, senderPort, packetSizeWithHeader,
|
||||
std::move(buffer), receiveTime });
|
||||
++packetsRead;
|
||||
|
||||
}
|
||||
|
||||
if (packetsRead > _maxDatagramsRead) {
|
||||
_maxDatagramsRead = packetsRead;
|
||||
qCDebug(networking) << "readPendingDatagrams: Datagrams read:" << packetsRead;
|
||||
}
|
||||
emit pendingDatagrams(packetsRead);
|
||||
}
|
||||
|
||||
void Socket::processPendingDatagrams(int) {
|
||||
// setup a HifiSockAddr to read into
|
||||
HifiSockAddr senderSockAddr;
|
||||
|
||||
while (!_incomingDatagrams.empty()) {
|
||||
auto& datagram = _incomingDatagrams.front();
|
||||
senderSockAddr.setAddress(datagram._senderAddress);
|
||||
senderSockAddr.setPort(datagram._senderPort);
|
||||
int datagramSize = datagram._datagramLength;
|
||||
auto receiveTime = datagram._receiveTime;
|
||||
|
||||
// we're reading a packet so re-start the readyRead backup timer
|
||||
_readyReadBackupTimer->start();
|
||||
|
||||
// save information for this packet, in case it is the one that sticks readyRead
|
||||
_lastPacketSizeRead = datagramSize;
|
||||
_lastPacketSockAddr = senderSockAddr;
|
||||
|
||||
// Process unfiltered packets first.
|
||||
auto it = _unfilteredHandlers.find(senderSockAddr);
|
||||
if (it != _unfilteredHandlers.end()) {
|
||||
// we have a registered unfiltered handler for this HifiSockAddr - call that and return
|
||||
// we have a registered unfiltered handler for this HifiSockAddr (eg. STUN packet) - call that and return
|
||||
if (it->second) {
|
||||
auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr);
|
||||
auto basePacket = BasePacket::fromReceivedPacket(std::move(datagram._datagram),
|
||||
datagramSize, senderSockAddr);
|
||||
basePacket->setReceiveTime(receiveTime);
|
||||
it->second(std::move(basePacket));
|
||||
}
|
||||
|
||||
_incomingDatagrams.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if this was a control packet or a data packet
|
||||
bool isControlPacket = *reinterpret_cast<uint32_t*>(buffer.get()) & CONTROL_BIT_MASK;
|
||||
bool isControlPacket = *reinterpret_cast<uint32_t*>(datagram._datagram.get()) & CONTROL_BIT_MASK;
|
||||
|
||||
if (isControlPacket) {
|
||||
// setup a control packet from the data we just read
|
||||
auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr);
|
||||
auto controlPacket = ControlPacket::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr);
|
||||
controlPacket->setReceiveTime(receiveTime);
|
||||
|
||||
// move this control packet to the matching connection, if there is one
|
||||
|
@ -375,13 +406,13 @@ void Socket::readPendingDatagrams() {
|
|||
|
||||
} else {
|
||||
// setup a Packet from the data we just read
|
||||
auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr);
|
||||
auto packet = Packet::fromReceivedPacket(std::move(datagram._datagram), datagramSize, senderSockAddr);
|
||||
packet->setReceiveTime(receiveTime);
|
||||
|
||||
// save the sequence number in case this is the packet that sticks readyRead
|
||||
_lastReceivedSequenceNumber = packet->getSequenceNumber();
|
||||
|
||||
// call our verification operator to see if this packet is verified
|
||||
// call our hash verification operator to see if this packet is verified
|
||||
if (!_packetFilterOperator || _packetFilterOperator(*packet)) {
|
||||
if (packet->isReliable()) {
|
||||
// if this was a reliable packet then signal the matching connection with the sequence number
|
||||
|
@ -395,6 +426,7 @@ void Socket::readPendingDatagrams() {
|
|||
qCDebug(networking) << "Can't process packet: version" << (unsigned int)NLPacket::versionInHeader(*packet)
|
||||
<< ", type" << NLPacket::typeInHeader(*packet);
|
||||
#endif
|
||||
_incomingDatagrams.pop_front();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -410,6 +442,8 @@ void Socket::readPendingDatagrams() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
_incomingDatagrams.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QTimer>
|
||||
|
@ -94,6 +95,7 @@ public:
|
|||
|
||||
signals:
|
||||
void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr);
|
||||
void pendingDatagrams(int datagramCount);
|
||||
|
||||
public slots:
|
||||
void cleanupConnection(HifiSockAddr sockAddr);
|
||||
|
@ -101,6 +103,7 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
void processPendingDatagrams(int datagramCount);
|
||||
void checkForReadyReadBackup();
|
||||
|
||||
void handleSocketError(QAbstractSocket::SocketError socketError);
|
||||
|
@ -144,6 +147,17 @@ private:
|
|||
int _lastPacketSizeRead { 0 };
|
||||
SequenceNumber _lastReceivedSequenceNumber;
|
||||
HifiSockAddr _lastPacketSockAddr;
|
||||
|
||||
struct Datagram {
|
||||
QHostAddress _senderAddress;
|
||||
int _senderPort;
|
||||
int _datagramLength;
|
||||
std::unique_ptr<char[]> _datagram;
|
||||
p_high_resolution_clock::time_point _receiveTime;
|
||||
};
|
||||
|
||||
std::list<Datagram> _incomingDatagrams;
|
||||
int _maxDatagramsRead { 0 };
|
||||
|
||||
friend UDTTest;
|
||||
};
|
||||
|
|
|
@ -286,6 +286,7 @@ void PhysicsEngine::reinsertObject(ObjectMotionState* object) {
|
|||
void PhysicsEngine::processTransaction(PhysicsEngine::Transaction& transaction) {
|
||||
// removes
|
||||
for (auto object : transaction.objectsToRemove) {
|
||||
bumpAndPruneContacts(object);
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
if (body) {
|
||||
removeDynamicsForBody(body);
|
||||
|
|
|
@ -86,7 +86,7 @@ void CauterizedModel::createRenderItemSet() {
|
|||
// Create the render payloads
|
||||
int numParts = (int)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||
if (!fbxGeometry.meshes[i].blendshapes.empty()) {
|
||||
if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) {
|
||||
initializeBlendshapes(fbxGeometry.meshes[i], i);
|
||||
}
|
||||
auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
||||
|
@ -97,6 +97,7 @@ void CauterizedModel::createRenderItemSet() {
|
|||
shapeID++;
|
||||
}
|
||||
}
|
||||
_blendedVertexBuffersInitialized = true;
|
||||
} else {
|
||||
Model::createRenderItemSet();
|
||||
}
|
||||
|
|
|
@ -302,15 +302,22 @@ bool Model::updateGeometry() {
|
|||
assert(_meshStates.empty());
|
||||
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
int i = 0;
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterDualQuaternions.resize(mesh.clusters.size());
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
_meshStates.push_back(state);
|
||||
if (!mesh.blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) {
|
||||
initializeBlendshapes(mesh, i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
_blendedVertexBuffersInitialized = true;
|
||||
needFullUpdate = true;
|
||||
emit rigReady();
|
||||
}
|
||||
|
||||
return needFullUpdate;
|
||||
}
|
||||
|
||||
|
@ -1027,6 +1034,10 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti
|
|||
_modelMeshMaterialNames.clear();
|
||||
_modelMeshRenderItemShapes.clear();
|
||||
|
||||
_blendedVertexBuffers.clear();
|
||||
_normalsAndTangents.clear();
|
||||
_blendedVertexBuffersInitialized = false;
|
||||
|
||||
_addedToScene = false;
|
||||
|
||||
_renderInfoVertexCount = 0;
|
||||
|
@ -1273,8 +1284,7 @@ QStringList Model::getJointNames() const {
|
|||
class Blender : public QRunnable {
|
||||
public:
|
||||
|
||||
Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients);
|
||||
Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector<float>& blendshapeCoefficients);
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
|
@ -1283,37 +1293,37 @@ private:
|
|||
ModelPointer _model;
|
||||
int _blendNumber;
|
||||
Geometry::WeakPointer _geometry;
|
||||
QVector<FBXMesh> _meshes;
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
};
|
||||
|
||||
Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) :
|
||||
Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector<float>& blendshapeCoefficients) :
|
||||
_model(model),
|
||||
_blendNumber(blendNumber),
|
||||
_geometry(geometry),
|
||||
_meshes(meshes),
|
||||
_blendshapeCoefficients(blendshapeCoefficients) {
|
||||
}
|
||||
|
||||
void Blender::run() {
|
||||
DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
|
||||
QVector<glm::vec3> vertices;
|
||||
QVector<NormalType> normalsAndTangents;
|
||||
if (_model) {
|
||||
if (_model && _model->isLoaded()) {
|
||||
DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
|
||||
int offset = 0;
|
||||
int normalsAndTangentsOffset = 0;
|
||||
foreach (const FBXMesh& mesh, _meshes) {
|
||||
if (mesh.blendshapes.isEmpty()) {
|
||||
auto meshes = _model->getFBXGeometry().meshes;
|
||||
int meshIndex = 0;
|
||||
foreach (const FBXMesh& mesh, meshes) {
|
||||
auto modelMeshNormalsAndTangents = _model->_normalsAndTangents.find(meshIndex++);
|
||||
if (mesh.blendshapes.isEmpty() || modelMeshNormalsAndTangents == _model->_normalsAndTangents.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vertices += mesh.vertices;
|
||||
normalsAndTangents += mesh.normalsAndTangents;
|
||||
normalsAndTangents += modelMeshNormalsAndTangents->second;
|
||||
glm::vec3* meshVertices = vertices.data() + offset;
|
||||
NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset;
|
||||
offset += mesh.vertices.size();
|
||||
normalsAndTangentsOffset += mesh.normalsAndTangents.size();
|
||||
normalsAndTangentsOffset += modelMeshNormalsAndTangents->second.size();
|
||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
||||
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
||||
|
@ -1353,9 +1363,8 @@ void Blender::run() {
|
|||
}
|
||||
// post the result to the ModelBlender, which will dispatch to the model if still alive
|
||||
QMetaObject::invokeMethod(DependencyManager::get<ModelBlender>().data(), "setBlendedVertices",
|
||||
Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber),
|
||||
Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
||||
Q_ARG(const QVector<NormalType>&, normalsAndTangents));
|
||||
Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector<glm::vec3>, vertices),
|
||||
Q_ARG(QVector<NormalType>, normalsAndTangents));
|
||||
}
|
||||
|
||||
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) {
|
||||
|
@ -1526,18 +1535,15 @@ bool Model::maybeStartBlender() {
|
|||
if (isLoaded()) {
|
||||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
if (fbxGeometry.hasBlendedMeshes()) {
|
||||
QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry,
|
||||
fbxGeometry.meshes, _blendshapeCoefficients));
|
||||
QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, _blendshapeCoefficients));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents) {
|
||||
auto geometryRef = geometry.lock();
|
||||
if (!geometryRef || _renderGeometry != geometryRef || blendNumber < _appliedBlendNumber) {
|
||||
void Model::setBlendedVertices(int blendNumber, const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents) {
|
||||
if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendedVertexBuffersInitialized) {
|
||||
return;
|
||||
}
|
||||
_appliedBlendNumber = blendNumber;
|
||||
|
@ -1546,26 +1552,28 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo
|
|||
int normalAndTangentIndex = 0;
|
||||
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||
if (mesh.blendshapes.isEmpty()) {
|
||||
auto meshNormalsAndTangents = _normalsAndTangents.find(i);
|
||||
if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto vertexCount = mesh.vertices.size();
|
||||
const auto verticesSize = vertexCount * sizeof(glm::vec3);
|
||||
const auto& buffer = _blendedVertexBuffers[i];
|
||||
assert(buffer && _blendedVertexBuffersInitialized);
|
||||
buffer->resize(mesh.vertices.size() * sizeof(glm::vec3) + mesh.normalsAndTangents.size() * sizeof(NormalType));
|
||||
buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3));
|
||||
buffer->setSubData(verticesSize, mesh.normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType));
|
||||
const auto& buffer = _blendedVertexBuffers.find(i);
|
||||
assert(buffer != _blendedVertexBuffers.end());
|
||||
buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType));
|
||||
buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3));
|
||||
buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType));
|
||||
|
||||
index += vertexCount;
|
||||
normalAndTangentIndex += mesh.normalsAndTangents.size();
|
||||
normalAndTangentIndex += meshNormalsAndTangents->second.size();
|
||||
}
|
||||
}
|
||||
|
||||
void Model::deleteGeometry() {
|
||||
_deleteGeometryCounter++;
|
||||
_blendedVertexBuffers.clear();
|
||||
_normalsAndTangents.clear();
|
||||
_blendedVertexBuffersInitialized = false;
|
||||
_meshStates.clear();
|
||||
_rig.destroyAnimGraph();
|
||||
|
@ -1599,6 +1607,7 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const {
|
|||
}
|
||||
|
||||
void Model::initializeBlendshapes(const FBXMesh& mesh, int index) {
|
||||
_blendedVertexBuffers[index] = std::make_shared<gpu::Buffer>();
|
||||
QVector<NormalType> normalsAndTangents;
|
||||
normalsAndTangents.resize(2 * mesh.normals.size());
|
||||
|
||||
|
@ -1627,12 +1636,10 @@ void Model::initializeBlendshapes(const FBXMesh& mesh, int index) {
|
|||
}
|
||||
});
|
||||
const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3);
|
||||
_blendedVertexBuffers[index] = std::make_shared<gpu::Buffer>();
|
||||
_blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType));
|
||||
_blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData());
|
||||
_blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data());
|
||||
mesh.normalsAndTangents = normalsAndTangents;
|
||||
_blendedVertexBuffersInitialized = true;
|
||||
_normalsAndTangents[index] = normalsAndTangents;
|
||||
}
|
||||
|
||||
void Model::createRenderItemSet() {
|
||||
|
@ -1673,7 +1680,7 @@ void Model::createRenderItemSet() {
|
|||
// Create the render payloads
|
||||
int numParts = (int)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||
if (!fbxGeometry.meshes[i].blendshapes.empty()) {
|
||||
if (!fbxGeometry.meshes[i].blendshapes.empty() && _blendedVertexBuffers.find(i) == _blendedVertexBuffers.end()) {
|
||||
initializeBlendshapes(fbxGeometry.meshes[i], i);
|
||||
}
|
||||
_modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
|
||||
|
@ -1683,6 +1690,7 @@ void Model::createRenderItemSet() {
|
|||
shapeID++;
|
||||
}
|
||||
}
|
||||
_blendedVertexBuffersInitialized = true;
|
||||
}
|
||||
|
||||
bool Model::isRenderable() const {
|
||||
|
@ -1775,35 +1783,38 @@ ModelBlender::~ModelBlender() {
|
|||
}
|
||||
|
||||
void ModelBlender::noteRequiresBlend(ModelPointer model) {
|
||||
Lock lock(_mutex);
|
||||
if (_pendingBlenders < QThread::idealThreadCount()) {
|
||||
if (model->maybeStartBlender()) {
|
||||
_pendingBlenders++;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
_modelsRequiringBlends.insert(model);
|
||||
}
|
||||
_modelsRequiringBlends.insert(model);
|
||||
}
|
||||
|
||||
void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents) {
|
||||
void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector<glm::vec3> vertices, QVector<NormalType> normalsAndTangents) {
|
||||
if (model) {
|
||||
model->setBlendedVertices(blendNumber, geometry, vertices, normalsAndTangents);
|
||||
model->setBlendedVertices(blendNumber, vertices, normalsAndTangents);
|
||||
}
|
||||
_pendingBlenders--;
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end();) {
|
||||
_pendingBlenders--;
|
||||
_modelsRequiringBlends.erase(model);
|
||||
std::set<ModelWeakPointer, std::owner_less<ModelWeakPointer>> modelsToErase;
|
||||
for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end(); i++) {
|
||||
auto weakPtr = *i;
|
||||
_modelsRequiringBlends.erase(i++); // remove front of the set
|
||||
ModelPointer nextModel = weakPtr.lock();
|
||||
if (nextModel && nextModel->maybeStartBlender()) {
|
||||
_pendingBlenders++;
|
||||
return;
|
||||
break;
|
||||
} else {
|
||||
modelsToErase.insert(weakPtr);
|
||||
}
|
||||
}
|
||||
for (auto& weakPtr : modelsToErase) {
|
||||
_modelsRequiringBlends.erase(weakPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,8 +144,7 @@ public:
|
|||
bool maybeStartBlender();
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents);
|
||||
void setBlendedVertices(int blendNumber, const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents);
|
||||
|
||||
bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); }
|
||||
bool isAddedToScene() const { return _addedToScene; }
|
||||
|
@ -345,7 +344,7 @@ public:
|
|||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
bool areBlendedVertexBuffersInitialized(int index) { return _blendedVertexBuffersInitialized; }
|
||||
std::unordered_map<int, QVector<NormalType>> _normalsAndTangents;
|
||||
|
||||
public slots:
|
||||
void loadURLFinished(bool success);
|
||||
|
@ -521,8 +520,7 @@ public:
|
|||
bool shouldComputeBlendshapes() { return _computeBlendshapes; }
|
||||
|
||||
public slots:
|
||||
void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<NormalType>& normalsAndTangents);
|
||||
void setBlendedVertices(ModelPointer model, int blendNumber, QVector<glm::vec3> vertices, QVector<NormalType> normalsAndTangents);
|
||||
void setComputeBlendshapes(bool computeBlendshapes) { _computeBlendshapes = computeBlendshapes; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -1974,8 +1974,9 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
|
|||
}
|
||||
|
||||
int ScriptEngine::getNumRunningEntityScripts() const {
|
||||
QReadLocker locker { &_entityScriptsLock };
|
||||
int sum = 0;
|
||||
for (auto& st : _entityScripts) {
|
||||
for (const auto& st : _entityScripts) {
|
||||
if (st.status == EntityScriptStatus::RUNNING) {
|
||||
++sum;
|
||||
}
|
||||
|
@ -1984,14 +1985,20 @@ int ScriptEngine::getNumRunningEntityScripts() const {
|
|||
}
|
||||
|
||||
void ScriptEngine::setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details) {
|
||||
_entityScripts[entityID] = details;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
_entityScripts[entityID] = details;
|
||||
}
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
|
||||
void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus &status, const QString& errorInfo) {
|
||||
EntityScriptDetails &details = _entityScripts[entityID];
|
||||
details.status = status;
|
||||
details.errorInfo = errorInfo;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
EntityScriptDetails& details = _entityScripts[entityID];
|
||||
details.status = status;
|
||||
details.errorInfo = errorInfo;
|
||||
}
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
|
||||
|
@ -2042,6 +2049,7 @@ QFuture<QVariant> ScriptEngine::getLocalEntityScriptDetails(const EntityItemID&
|
|||
}
|
||||
|
||||
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
|
||||
QReadLocker locker { &_entityScriptsLock };
|
||||
auto it = _entityScripts.constFind(entityID);
|
||||
if (it == _entityScripts.constEnd()) {
|
||||
return false;
|
||||
|
@ -2050,6 +2058,11 @@ bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntitySc
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const {
|
||||
QReadLocker locker { &_entityScriptsLock };
|
||||
return _entityScripts.contains(entityID);
|
||||
}
|
||||
|
||||
const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" };
|
||||
|
||||
void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) {
|
||||
|
@ -2064,14 +2077,15 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const
|
|||
}
|
||||
foreach(DeferredLoadEntity retry, retryLoads) {
|
||||
// check whether entity was since been deleted
|
||||
if (!_entityScripts.contains(retry.entityID)) {
|
||||
|
||||
EntityScriptDetails details;
|
||||
if (!getEntityScriptDetails(retry.entityID, details)) {
|
||||
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)"
|
||||
<< retry.entityID;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check whether entity has since been unloaded or otherwise errored-out
|
||||
auto details = _entityScripts[retry.entityID];
|
||||
if (details.status != EntityScriptStatus::PENDING) {
|
||||
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; "
|
||||
<< retry.entityID << details.status;
|
||||
|
@ -2079,7 +2093,11 @@ void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const
|
|||
}
|
||||
|
||||
// propagate leader's failure reasons to the pending entity
|
||||
const auto leaderDetails = _entityScripts[leaderID];
|
||||
EntityScriptDetails leaderDetails;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
leaderDetails = _entityScripts[leaderID];
|
||||
}
|
||||
if (leaderDetails.status != EntityScriptStatus::RUNNING) {
|
||||
qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)")
|
||||
.arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status);
|
||||
|
@ -2125,7 +2143,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_entityScripts.contains(entityID)) {
|
||||
if (!hasEntityScriptDetails(entityID)) {
|
||||
// make sure EntityScriptDetails has an entry for this UUID right away
|
||||
// (which allows bailing from the loading/provisioning process early if the Entity gets deleted mid-flight)
|
||||
updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending...");
|
||||
|
@ -2166,9 +2184,12 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
_occupiedScriptURLs[entityScript] = entityID;
|
||||
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
auto previousStatus = _entityScripts.contains(entityID) ? _entityScripts[entityID].status : EntityScriptStatus::PENDING;
|
||||
qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString()
|
||||
<< "(previous: " << previousStatus << ")";
|
||||
{
|
||||
EntityScriptDetails details;
|
||||
bool hasEntityScript = getEntityScriptDetails(entityID, details);
|
||||
qCDebug(scriptengine) << "loadEntityScript.LOADING: " << entityScript << entityID.toString()
|
||||
<< "(previous: " << (hasEntityScript ? details.status : EntityScriptStatus::PENDING) << ")";
|
||||
}
|
||||
#endif
|
||||
|
||||
EntityScriptDetails newDetails;
|
||||
|
@ -2197,7 +2218,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
#ifdef DEBUG_ENTITY_STATES
|
||||
qCDebug(scriptengine) << "loadEntityScript.contentAvailable" << status << QUrl(url).fileName() << entityID.toString();
|
||||
#endif
|
||||
if (!isStopping() && _entityScripts.contains(entityID)) {
|
||||
if (!isStopping() && hasEntityScriptDetails(entityID)) {
|
||||
_contentAvailableQueue[entityID] = { entityID, url, contents, isURL, success, status };
|
||||
} else {
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
|
@ -2267,8 +2288,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
bool isFileUrl = isURL && scriptOrURL.startsWith("file://");
|
||||
auto fileName = isURL ? scriptOrURL : "about:EmbeddedEntityScript";
|
||||
|
||||
const EntityScriptDetails &oldDetails = _entityScripts[entityID];
|
||||
const QString entityScript = oldDetails.scriptText;
|
||||
QString entityScript;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
entityScript = _entityScripts[entityID].scriptText;
|
||||
}
|
||||
|
||||
EntityScriptDetails newDetails;
|
||||
newDetails.scriptText = scriptOrURL;
|
||||
|
@ -2446,8 +2470,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
|
|||
"entityID:" << entityID;
|
||||
#endif
|
||||
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
const EntityScriptDetails &oldDetails = _entityScripts[entityID];
|
||||
EntityScriptDetails oldDetails;
|
||||
if (getEntityScriptDetails(entityID, oldDetails)) {
|
||||
auto scriptText = oldDetails.scriptText;
|
||||
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
|
@ -2460,7 +2484,10 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
|
|||
#endif
|
||||
if (shouldRemoveFromMap) {
|
||||
// this was a deleted entity, we've been asked to remove it from the map
|
||||
_entityScripts.remove(entityID);
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
_entityScripts.remove(entityID);
|
||||
}
|
||||
emit entityScriptDetailsUpdated();
|
||||
} else if (oldDetails.status != EntityScriptStatus::UNLOADED) {
|
||||
EntityScriptDetails newDetails;
|
||||
|
@ -2491,10 +2518,19 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";
|
||||
#endif
|
||||
foreach(const EntityItemID& entityID, _entityScripts.keys()) {
|
||||
|
||||
QList<EntityItemID> keys;
|
||||
{
|
||||
QReadLocker locker{ &_entityScriptsLock };
|
||||
keys = _entityScripts.keys();
|
||||
}
|
||||
foreach(const EntityItemID& entityID, keys) {
|
||||
unloadEntityScript(entityID);
|
||||
}
|
||||
_entityScripts.clear();
|
||||
{
|
||||
QWriteLocker locker{ &_entityScriptsLock };
|
||||
_entityScripts.clear();
|
||||
}
|
||||
emit entityScriptDetailsUpdated();
|
||||
_occupiedScriptURLs.clear();
|
||||
|
||||
|
@ -2508,7 +2544,7 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
}
|
||||
|
||||
void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
||||
if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !_entityScripts.contains(entityID)) {
|
||||
if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2518,7 +2554,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
|
|||
}
|
||||
recurseGuard = true;
|
||||
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
EntityScriptDetails details;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
details = _entityScripts[entityID];
|
||||
}
|
||||
// Check to see if a file based script needs to be reloaded (easier debugging)
|
||||
if (details.lastModified > 0) {
|
||||
QString filePath = QUrl(details.scriptText).toLocalFile();
|
||||
|
@ -2584,7 +2624,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
EntityScriptDetails details;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
details = _entityScripts[entityID];
|
||||
}
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
|
||||
// If this is a remote call, we need to check to see if the function is remotely callable
|
||||
|
@ -2646,7 +2690,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
EntityScriptDetails details;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
details = _entityScripts[entityID];
|
||||
}
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
QScriptValueList args;
|
||||
|
@ -2680,7 +2728,11 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
|
|||
refreshFileScript(entityID);
|
||||
}
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
EntityScriptDetails details = _entityScripts[entityID];
|
||||
EntityScriptDetails details;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
details = _entityScripts[entityID];
|
||||
}
|
||||
QScriptValue entityScript = details.scriptObject; // previously loaded
|
||||
if (entityScript.property(methodName).isFunction()) {
|
||||
QScriptValueList args;
|
||||
|
|
|
@ -461,7 +461,9 @@ public:
|
|||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) {
|
||||
return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING;
|
||||
QReadLocker locker { &_entityScriptsLock };
|
||||
auto it = _entityScripts.constFind(entityID);
|
||||
return it != _entityScripts.constEnd() && it->status == EntityScriptStatus::RUNNING;
|
||||
}
|
||||
QVariant cloneEntityScriptDetails(const EntityItemID& entityID);
|
||||
QFuture<QVariant> getLocalEntityScriptDetails(const EntityItemID& entityID) override;
|
||||
|
@ -559,6 +561,7 @@ public:
|
|||
void clearDebugLogWindow();
|
||||
int getNumRunningEntityScripts() const;
|
||||
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
|
||||
bool hasEntityScriptDetails(const EntityItemID& entityID) const;
|
||||
|
||||
public slots:
|
||||
|
||||
|
@ -771,6 +774,7 @@ protected:
|
|||
bool _isInitialized { false };
|
||||
QHash<QTimer*, CallbackData> _timerFunctionMap;
|
||||
QSet<QUrl> _includedURLs;
|
||||
mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive };
|
||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
QHash<QString, EntityItemID> _occupiedScriptURLs;
|
||||
QList<DeferredLoadEntity> _deferredEntityLoads;
|
||||
|
|
|
@ -16,55 +16,16 @@
|
|||
#include "NumericalConstants.h"
|
||||
#include "shared/ConicalViewFrustum.h"
|
||||
|
||||
/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use:
|
||||
// PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum.
|
||||
|
||||
(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of
|
||||
the Thing you want to prioritize and sort:
|
||||
|
||||
class SortableWrapper: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableWrapper(const Thing& thing) : _thing(thing) { }
|
||||
glm::vec3 getPosition() const override { return _thing->getPosition(); }
|
||||
float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); }
|
||||
uint64_t getTimestamp() const override { return _thing->getLastTime(); }
|
||||
Thing getThing() const { return _thing; }
|
||||
private:
|
||||
Thing _thing;
|
||||
};
|
||||
|
||||
(2) Make a PrioritySortUtil::PriorityQueue<Thing> and add them to the queue:
|
||||
|
||||
PrioritySortUtil::PriorityQueue<SortableWrapper> sortedThings(viewFrustum);
|
||||
std::priority_queue< PrioritySortUtil::Sortable<Thing> > sortedThings;
|
||||
for (thing in things) {
|
||||
sortedThings.push(SortableWrapper(thing));
|
||||
}
|
||||
|
||||
(3) Loop over your priority queue and do timeboxed work:
|
||||
|
||||
NOTE: Be careful using references to members of instances of T from std::priority_queue<T>.
|
||||
Under the hood std::priority_queue<T> may re-use instances of T.
|
||||
For example, after a pop() or a push() the top T may have the same memory address
|
||||
as the top T before the pop() or push() (but point to a swapped instance of T).
|
||||
This causes a reference to member variable of T to point to a different value
|
||||
when operations taken on std::priority_queue<T> shuffle around the instances of T.
|
||||
|
||||
uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET;
|
||||
while (!sortedThings.empty()) {
|
||||
const Thing& thing = sortedThings.top();
|
||||
// ...do work on thing...
|
||||
sortedThings.pop();
|
||||
if (usecTimestampNow() > cutoffTime) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
const float OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY;
|
||||
|
||||
namespace PrioritySortUtil {
|
||||
|
||||
constexpr float DEFAULT_ANGULAR_COEF { 1.0f };
|
||||
constexpr float DEFAULT_CENTER_COEF { 0.5f };
|
||||
constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) };
|
||||
constexpr float DEFAULT_AGE_COEF { 0.25f };
|
||||
|
||||
class Sortable {
|
||||
public:
|
||||
|
@ -84,8 +45,9 @@ namespace PrioritySortUtil {
|
|||
PriorityQueue() = delete;
|
||||
PriorityQueue(const ConicalViewFrustums& views) : _views(views) { }
|
||||
PriorityQueue(const ConicalViewFrustums& views, float angularWeight, float centerWeight, float ageWeight)
|
||||
: _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
|
||||
{ }
|
||||
: _views(views), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight)
|
||||
, _usecCurrentTime(usecTimestampNow()) {
|
||||
}
|
||||
|
||||
void setViews(const ConicalViewFrustums& views) { _views = views; }
|
||||
|
||||
|
@ -93,6 +55,7 @@ namespace PrioritySortUtil {
|
|||
_angularWeight = angularWeight;
|
||||
_centerWeight = centerWeight;
|
||||
_ageWeight = ageWeight;
|
||||
_usecCurrentTime = usecTimestampNow();
|
||||
}
|
||||
|
||||
size_t size() const { return _vector.size(); }
|
||||
|
@ -131,23 +94,18 @@ namespace PrioritySortUtil {
|
|||
glm::vec3 offset = position - view.getPosition();
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance)
|
||||
float radius = glm::min(thing.getRadius(), MIN_RADIUS);
|
||||
float cosineAngle = (glm::dot(offset, view.getDirection()) / distance);
|
||||
float age = (float)(usecTimestampNow() - thing.getTimestamp());
|
||||
float radius = glm::max(thing.getRadius(), MIN_RADIUS);
|
||||
// Other item's angle from view centre:
|
||||
float cosineAngle = glm::dot(offset, view.getDirection()) / distance;
|
||||
float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND);
|
||||
|
||||
// we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward
|
||||
// at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it
|
||||
const float MIN_COSINE_ANGLE_FACTOR = 0.1f;
|
||||
float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR);
|
||||
|
||||
float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance
|
||||
+ _centerWeight * cosineAngle
|
||||
+ _ageWeight * cosineAngleFactor * age;
|
||||
// the "age" term accumulates at the sum of all weights
|
||||
float angularSize = radius / distance;
|
||||
float priority = (_angularWeight * angularSize + _centerWeight * cosineAngle) * (age + 1.0f) + _ageWeight * age;
|
||||
|
||||
// decrement priority of things outside keyhole
|
||||
if (distance - radius > view.getRadius()) {
|
||||
if (!view.intersects(offset, distance, radius)) {
|
||||
constexpr float OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
priority += OUT_OF_VIEW_PENALTY;
|
||||
}
|
||||
}
|
||||
|
@ -159,12 +117,13 @@ namespace PrioritySortUtil {
|
|||
float _angularWeight { DEFAULT_ANGULAR_COEF };
|
||||
float _centerWeight { DEFAULT_CENTER_COEF };
|
||||
float _ageWeight { DEFAULT_AGE_COEF };
|
||||
quint64 _usecCurrentTime { 0 };
|
||||
};
|
||||
} // namespace PrioritySortUtil
|
||||
|
||||
// for now we're keeping hard-coded sorted time budgets in one spot
|
||||
// for now we're keeping hard-coded sorted time budgets in one spot
|
||||
const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec
|
||||
const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec
|
||||
const uint64_t MAX_UPDATE_AVATARS_TIME_BUDGET = 2000; // usec
|
||||
|
||||
#endif // hifi_PrioritySortUtil_h
|
||||
|
||||
|
|
|
@ -35,10 +35,13 @@ var DEFAULT_SCRIPTS_COMBINED = [
|
|||
];
|
||||
var DEFAULT_SCRIPTS_SEPARATE = [
|
||||
"system/controllers/controllerScripts.js",
|
||||
"system/interstitialPage.js"
|
||||
//"system/chat.js"
|
||||
];
|
||||
|
||||
if (Settings.getValue("enableInterstitialMode", false)) {
|
||||
DEFAULT_SCRIPTS_SEPARATE.push("system/interstitialPage.js");
|
||||
}
|
||||
|
||||
// add a menu item for debugging
|
||||
var MENU_CATEGORY = "Developer";
|
||||
var MENU_ITEM = "Debug defaultScripts.js";
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
this.highlightedEntities = [];
|
||||
|
||||
this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
|
||||
480,
|
||||
120,
|
||||
this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
|
|
@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js");
|
|||
this.reticleMaxY;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
160,
|
||||
200,
|
||||
this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
|
||||
[],
|
||||
100,
|
||||
|
|
|
@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
this.disableModules = false;
|
||||
var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed.
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
200, // Not too high otherwise the tablet laser doesn't work.
|
||||
240, // Not too high otherwise the tablet laser doesn't work.
|
||||
this.hand === RIGHT_HAND
|
||||
? ["rightHand", "rightHandEquip", "rightHandTrigger"]
|
||||
: ["leftHand", "leftHandEquip", "leftHandTrigger"],
|
||||
|
|
|
@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
|||
this.hapticTargetID = null;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
500,
|
||||
140,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
this.hyperlink = "";
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
485,
|
||||
125,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
|
|
@ -24,6 +24,9 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
|
||||
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
|
||||
|
||||
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
|
||||
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
|
||||
|
||||
function getGrabOffset(handController) {
|
||||
var offset = GRAB_POINT_SPHERE_OFFSET;
|
||||
if (handController === Controller.Standard.LeftHand) {
|
||||
|
@ -54,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
this.cloneAllowed = true;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
500,
|
||||
140,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
|
|
@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
|||
this.startSent = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
480,
|
||||
120,
|
||||
this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"],
|
||||
[],
|
||||
100);
|
||||
|
|
|
@ -22,14 +22,14 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
this.running = false;
|
||||
|
||||
this.parameters = makeDispatcherModuleParameters(
|
||||
120,
|
||||
160,
|
||||
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||
[],
|
||||
100,
|
||||
makeLaserParams(hand, true));
|
||||
|
||||
this.grabModuleWantsNearbyOverlay = function(controllerData) {
|
||||
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||
var nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
|
||||
var nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule) {
|
||||
|
@ -42,6 +42,23 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
return true;
|
||||
}
|
||||
}
|
||||
nearGrabName = this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity";
|
||||
nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule && nearGrabModule.isReady(controllerData)) {
|
||||
// check for if near parent module is active.
|
||||
var isNearGrabModuleActive = nearGrabModule.isReady(controllerData).active;
|
||||
if (isNearGrabModuleActive) {
|
||||
// if true, return true.
|
||||
return isNearGrabModuleActive;
|
||||
} else {
|
||||
// check near action grab entity as a second pass.
|
||||
nearGrabName = this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity";
|
||||
nearGrabModule = getEnabledModuleByName(nearGrabName);
|
||||
if (nearGrabModule && nearGrabModule.isReady(controllerData)) {
|
||||
return nearGrabModule.isReady(controllerData).active;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
@ -50,14 +67,14 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput;
|
||||
};
|
||||
|
||||
this.isPointingAtTriggerable = function(controllerData, triggerPressed) {
|
||||
this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) {
|
||||
// allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger,
|
||||
// but for pointing at locked web entities or non-web overlays user must be pressing trigger
|
||||
var intersection = controllerData.rayPicks[this.hand];
|
||||
if (intersection.type === Picks.INTERSECTED_OVERLAY) {
|
||||
var objectID = intersection.objectID;
|
||||
var objectID = intersection.objectID;
|
||||
if (intersection.type === Picks.INTERSECTED_OVERLAY && !checkEntitiesOnly) {
|
||||
if ((HMD.tabletID && objectID === HMD.tabletID) ||
|
||||
(HMD.tabletScreenID && objectID === HMD.tabletScreenID) ||
|
||||
(HMD.tabletScreenID && objectID === HMD.tabletScreenID) ||
|
||||
(HMD.homeButtonID && objectID === HMD.homeButtonID)) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -65,9 +82,9 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
return overlayType === "web3d" || triggerPressed;
|
||||
}
|
||||
} else if (intersection.type === Picks.INTERSECTED_ENTITY) {
|
||||
var entityProperty = Entities.getEntityProperties(intersection.objectID);
|
||||
var entityType = entityProperty.type;
|
||||
var isLocked = entityProperty.locked;
|
||||
var entityProperties = Entities.getEntityProperties(objectID);
|
||||
var entityType = entityProperties.type;
|
||||
var isLocked = entityProperties.locked;
|
||||
return entityType === "Web" && (!isLocked || triggerPressed);
|
||||
}
|
||||
return false;
|
||||
|
@ -103,7 +120,8 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE &&
|
||||
controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
|
||||
var allowThisModule = !otherModuleRunning || isTriggerPressed;
|
||||
if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) {
|
||||
|
||||
if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) {
|
||||
this.updateAllwaysOn();
|
||||
if (isTriggerPressed) {
|
||||
this.dominantHandOverride = true; // Override dominant hand.
|
||||
|
@ -121,13 +139,27 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand.
|
||||
otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand.
|
||||
var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData);
|
||||
// only allow for non-near grab
|
||||
var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun;
|
||||
var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE;
|
||||
var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn;
|
||||
if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed))) {
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
if (allowThisModule) {
|
||||
if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) {
|
||||
// if trigger is down + not pointing at a web entity, keep running web surface laser
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
} else if (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) {
|
||||
// if trigger is down + pointing at a web entity/overlay, keep running web surface laser
|
||||
this.running = true;
|
||||
return makeRunningValues(true, [], []);
|
||||
} else {
|
||||
this.deleteContextOverlay();
|
||||
this.running = false;
|
||||
this.dominantHandOverride = false;
|
||||
return makeRunningValues(false, [], []);
|
||||
}
|
||||
}
|
||||
// if module needs to stop from near grabs or other modules are running, stop it.
|
||||
this.deleteContextOverlay();
|
||||
this.running = false;
|
||||
this.dominantHandOverride = false;
|
||||
|
|
|
@ -860,7 +860,7 @@ var toolBar = (function () {
|
|||
propertiesTool.setVisible(false);
|
||||
selectionManager.clearSelections();
|
||||
cameraManager.disable();
|
||||
selectionDisplay.triggerMapping.disable();
|
||||
selectionDisplay.disableTriggerMapping();
|
||||
tablet.landscape = false;
|
||||
Controller.disableMapping(CONTROLLER_MAPPING_NAME);
|
||||
} else {
|
||||
|
@ -876,7 +876,7 @@ var toolBar = (function () {
|
|||
gridTool.setVisible(true);
|
||||
grid.setEnabled(true);
|
||||
propertiesTool.setVisible(true);
|
||||
selectionDisplay.triggerMapping.enable();
|
||||
selectionDisplay.enableTriggerMapping();
|
||||
print("starting tablet in landscape mode");
|
||||
tablet.landscape = true;
|
||||
Controller.enableMapping(CONTROLLER_MAPPING_NAME);
|
||||
|
|
|
@ -282,14 +282,15 @@ function loaded() {
|
|||
function refreshEntityList() {
|
||||
PROFILE("refresh-entity-list", function() {
|
||||
PROFILE("filter", function() {
|
||||
let searchTerm = elFilter.value;
|
||||
let searchTerm = elFilter.value.toLowerCase();
|
||||
if (searchTerm === '') {
|
||||
visibleEntities = entities.slice(0);
|
||||
} else {
|
||||
visibleEntities = entities.filter(function(e) {
|
||||
return e.name.indexOf(searchTerm) > -1
|
||||
|| e.type.indexOf(searchTerm) > -1
|
||||
|| e.fullUrl.indexOf(searchTerm) > -1;
|
||||
return e.name.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.type.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.fullUrl.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.id.toLowerCase().indexOf(searchTerm) > -1;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
|
||||
var span = document.createElement('span');
|
||||
span.style = "margin:10px 5px;color:#1b6420;font-size:15px;";
|
||||
span.innerHTML = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Setup your Wallet</a> to get money and shop in Marketplace.";
|
||||
span.innerHTML = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Activate your Wallet</a> to get money and shop in Marketplace.";
|
||||
|
||||
var xButton = document.createElement('a');
|
||||
xButton.id = "xButton";
|
||||
|
|
|
@ -137,10 +137,10 @@
|
|||
|
||||
var loadingToTheSpotID = Overlays.addOverlay("image3d", {
|
||||
name: "Loading-Destination-Card-Text",
|
||||
localPosition: { x: 0.0 , y: -1.8, z: 0.0 },
|
||||
url: "http://hifi-content.s3.amazonaws.com/alexia/LoadingScreens/goTo_button.png",
|
||||
localPosition: { x: 0.0 , y: -1.5, z: -0.3 },
|
||||
url: Script.resourcesPath() + "images/interstitialPage/goTo_button.png",
|
||||
alpha: 1,
|
||||
dimensions: { x: 1.2, y: 0.6},
|
||||
dimensions: { x: 1.5, y: 1.0 },
|
||||
visible: isVisible,
|
||||
emissive: true,
|
||||
ignoreRayIntersection: false,
|
||||
|
@ -415,13 +415,13 @@
|
|||
Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay);
|
||||
Overlays.hoverEnterOverlay.connect(function(overlayID, event) {
|
||||
if (overlayID === loadingToTheSpotID) {
|
||||
Overlays.editOverlay(loadingToTheSpotID, { color: greyColor});
|
||||
Overlays.editOverlay(loadingToTheSpotID, { color: greyColor });
|
||||
}
|
||||
});
|
||||
|
||||
Overlays.hoverLeaveOverlay.connect(function(overlayID, event) {
|
||||
if (overlayID === loadingToTheSpotID) {
|
||||
Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor});
|
||||
Overlays.editOverlay(loadingToTheSpotID, { color: whiteColor });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
//
|
||||
|
||||
/* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections,
|
||||
getMainTabletIDs, getControllerWorldLocation */
|
||||
getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */
|
||||
|
||||
var SPACE_LOCAL = "local";
|
||||
var SPACE_WORLD = "world";
|
||||
|
@ -22,6 +22,7 @@ var HIGHLIGHT_LIST_NAME = "editHandleHighlightList";
|
|||
|
||||
Script.include([
|
||||
"./controllers.js",
|
||||
"./controllerDispatcherUtils.js",
|
||||
"./utils.js"
|
||||
]);
|
||||
|
||||
|
@ -448,6 +449,8 @@ SelectionDisplay = (function() {
|
|||
var CTRL_KEY_CODE = 16777249;
|
||||
|
||||
var RAIL_AXIS_LENGTH = 10000;
|
||||
|
||||
var NO_HAND = -1;
|
||||
|
||||
var TRANSLATE_DIRECTION = {
|
||||
X: 0,
|
||||
|
@ -478,8 +481,6 @@ SelectionDisplay = (function() {
|
|||
YAW: 1,
|
||||
ROLL: 2
|
||||
};
|
||||
|
||||
var NO_TRIGGER_HAND = -1;
|
||||
|
||||
var spaceMode = SPACE_LOCAL;
|
||||
var overlayNames = [];
|
||||
|
@ -802,11 +803,21 @@ SelectionDisplay = (function() {
|
|||
|
||||
// We get mouseMoveEvents from the handControllers, via handControllerPointer.
|
||||
// But we dont' get mousePressEvents.
|
||||
that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
|
||||
Script.scriptEnding.connect(that.triggerMapping.disable);
|
||||
that.triggeredHand = NO_TRIGGER_HAND;
|
||||
that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click');
|
||||
that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
|
||||
that.triggeredHand = NO_HAND;
|
||||
that.pressedHand = NO_HAND;
|
||||
that.triggered = function() {
|
||||
return that.triggeredHand !== NO_TRIGGER_HAND;
|
||||
return that.triggeredHand !== NO_HAND;
|
||||
}
|
||||
function pointingAtDesktopWindowOrTablet(hand) {
|
||||
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
|
||||
SelectionManager.pointingAtDesktopWindowRight) ||
|
||||
(hand === Controller.Standard.LeftHand &&
|
||||
SelectionManager.pointingAtDesktopWindowLeft);
|
||||
var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) ||
|
||||
(hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft);
|
||||
return pointingAtDesktopWindow || pointingAtTablet;
|
||||
}
|
||||
function makeClickHandler(hand) {
|
||||
return function (clicked) {
|
||||
|
@ -814,26 +825,39 @@ SelectionDisplay = (function() {
|
|||
if (that.triggered() && hand !== that.triggeredHand) {
|
||||
return;
|
||||
}
|
||||
if (!that.triggered() && clicked) {
|
||||
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
|
||||
SelectionManager.pointingAtDesktopWindowRight) ||
|
||||
(hand === Controller.Standard.LeftHand &&
|
||||
SelectionManager.pointingAtDesktopWindowLeft);
|
||||
var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) ||
|
||||
(hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft);
|
||||
if (pointingAtDesktopWindow || pointingAtTablet) {
|
||||
return;
|
||||
}
|
||||
if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) {
|
||||
that.triggeredHand = hand;
|
||||
that.mousePressEvent({});
|
||||
} else if (that.triggered() && !clicked) {
|
||||
that.triggeredHand = NO_TRIGGER_HAND;
|
||||
that.triggeredHand = NO_HAND;
|
||||
that.mouseReleaseEvent({});
|
||||
}
|
||||
};
|
||||
}
|
||||
that.triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
|
||||
that.triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
|
||||
function makePressHandler(hand) {
|
||||
return function (value) {
|
||||
if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) {
|
||||
that.pressedHand = hand;
|
||||
that.updateHighlight({});
|
||||
} else {
|
||||
that.pressedHand = NO_HAND;
|
||||
that.resetPreviousHandleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
|
||||
that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
|
||||
that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
|
||||
that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
|
||||
that.enableTriggerMapping = function() {
|
||||
that.triggerClickMapping.enable();
|
||||
that.triggerPressMapping.enable();
|
||||
};
|
||||
that.disableTriggerMapping = function() {
|
||||
that.triggerClickMapping.disable();
|
||||
that.triggerPressMapping.disable();
|
||||
}
|
||||
Script.scriptEnding.connect(that.disableTriggerMapping);
|
||||
|
||||
// FUNCTION DEF(s): Intersection Check Helpers
|
||||
function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) {
|
||||
|
@ -960,35 +984,10 @@ SelectionDisplay = (function() {
|
|||
}
|
||||
return Uuid.NULL;
|
||||
};
|
||||
|
||||
// FUNCTION: MOUSE MOVE EVENT
|
||||
var lastMouseEvent = null;
|
||||
that.mouseMoveEvent = function(event) {
|
||||
var wantDebug = false;
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent BEG =======================");
|
||||
}
|
||||
lastMouseEvent = event;
|
||||
if (activeTool) {
|
||||
if (wantDebug) {
|
||||
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
|
||||
}
|
||||
activeTool.onMove(event);
|
||||
|
||||
if (wantDebug) {
|
||||
print(" Trigger SelectionManager::update");
|
||||
}
|
||||
SelectionManager._update();
|
||||
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent END =======================");
|
||||
}
|
||||
// EARLY EXIT--(Move handled via active tool)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
that.updateHighlight = function(event) {
|
||||
// if no tool is active, then just look for handles to highlight...
|
||||
var pickRay = generalComputePickRay(event.x, event.y);
|
||||
var pickRay = generalComputePickRay(event.x, event.y);
|
||||
var result = Overlays.findRayIntersection(pickRay);
|
||||
var pickedColor;
|
||||
var highlightNeeded = false;
|
||||
|
@ -1039,7 +1038,36 @@ SelectionDisplay = (function() {
|
|||
} else {
|
||||
that.resetPreviousHandleColor();
|
||||
}
|
||||
};
|
||||
|
||||
// FUNCTION: MOUSE MOVE EVENT
|
||||
var lastMouseEvent = null;
|
||||
that.mouseMoveEvent = function(event) {
|
||||
var wantDebug = false;
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent BEG =======================");
|
||||
}
|
||||
lastMouseEvent = event;
|
||||
if (activeTool) {
|
||||
if (wantDebug) {
|
||||
print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove");
|
||||
}
|
||||
activeTool.onMove(event);
|
||||
|
||||
if (wantDebug) {
|
||||
print(" Trigger SelectionManager::update");
|
||||
}
|
||||
SelectionManager._update();
|
||||
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent END =======================");
|
||||
}
|
||||
// EARLY EXIT--(Move handled via active tool)
|
||||
return true;
|
||||
}
|
||||
|
||||
that.updateHighlight(event);
|
||||
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent END =======================");
|
||||
}
|
||||
|
@ -1135,9 +1163,10 @@ SelectionDisplay = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
function controllerComputePickRay() {
|
||||
var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
|
||||
if (controllerPose.valid && that.triggered()) {
|
||||
function controllerComputePickRay(hand) {
|
||||
var hand = that.triggered() ? that.triggeredHand : that.pressedHand;
|
||||
var controllerPose = getControllerWorldLocation(hand, true);
|
||||
if (controllerPose.valid) {
|
||||
var controllerPosition = controllerPose.translation;
|
||||
// This gets point direction right, but if you want general quaternion it would be more complicated:
|
||||
var controllerDirection = Quat.getUp(controllerPose.rotation);
|
||||
|
@ -1148,6 +1177,12 @@ SelectionDisplay = (function() {
|
|||
function generalComputePickRay(x, y) {
|
||||
return controllerComputePickRay() || Camera.computePickRay(x, y);
|
||||
}
|
||||
|
||||
function getControllerAvatarFramePositionFromPickRay(pickRay) {
|
||||
var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position);
|
||||
controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition);
|
||||
return controllerPosition;
|
||||
}
|
||||
|
||||
function getDistanceToCamera(position) {
|
||||
var cameraPosition = Camera.getPosition();
|
||||
|
@ -2083,6 +2118,7 @@ SelectionDisplay = (function() {
|
|||
var rotation = null;
|
||||
var previousPickRay = null;
|
||||
var beginMouseEvent = null;
|
||||
var beginControllerPosition = null;
|
||||
|
||||
var onBegin = function(event, pickRay, pickResult) {
|
||||
var proportional = directionEnum === STRETCH_DIRECTION.ALL;
|
||||
|
@ -2218,6 +2254,9 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
beginMouseEvent = event;
|
||||
if (that.triggered()) {
|
||||
beginControllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay);
|
||||
}
|
||||
};
|
||||
|
||||
var onEnd = function(event, reason) {
|
||||
|
@ -2256,13 +2295,15 @@ SelectionDisplay = (function() {
|
|||
if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) {
|
||||
pickRay = previousPickRay;
|
||||
}
|
||||
|
||||
var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
|
||||
var controllerTrigger = HMD.isHMDAvailable() && HMD.isHandControllerAvailable() &&
|
||||
controllerPose.valid && that.triggered();
|
||||
|
||||
// Are we using handControllers or Mouse - only relevant for 3D tools
|
||||
var controllerPose = getControllerWorldLocation(that.triggeredHand, true);
|
||||
var vector = null;
|
||||
var newPick = null;
|
||||
if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() &&
|
||||
controllerPose.valid && that.triggered() && directionFor3DStretch) {
|
||||
if (controllerTrigger && directionFor3DStretch) {
|
||||
localDeltaPivot = deltaPivot3D;
|
||||
newPick = pickRay.origin;
|
||||
vector = Vec3.subtract(newPick, lastPick3D);
|
||||
|
@ -2286,12 +2327,23 @@ SelectionDisplay = (function() {
|
|||
var newDimensions;
|
||||
if (proportional) {
|
||||
var viewportDimensions = Controller.getViewportDimensions();
|
||||
var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x;
|
||||
var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y;
|
||||
var mouseDifference = mouseXDifference + mouseYDifference;
|
||||
var toCameraDistance = getDistanceToCamera(position);
|
||||
var dimensionsMultiple = toCameraDistance * STRETCH_DIRECTION_ALL_CAMERA_DISTANCE_MULTIPLE;
|
||||
var dimensionChange = mouseDifference * dimensionsMultiple;
|
||||
|
||||
var dimensionChange;
|
||||
if (controllerTrigger) {
|
||||
var controllerPosition = getControllerAvatarFramePositionFromPickRay(pickRay);
|
||||
var vecControllerDifference = Vec3.subtract(controllerPosition, beginControllerPosition);
|
||||
var controllerDifference = vecControllerDifference.x + vecControllerDifference.y +
|
||||
vecControllerDifference.z;
|
||||
dimensionChange = controllerDifference * dimensionsMultiple;
|
||||
} else {
|
||||
var mouseXDifference = (event.x - beginMouseEvent.x) / viewportDimensions.x;
|
||||
var mouseYDifference = (beginMouseEvent.y - event.y) / viewportDimensions.y;
|
||||
var mouseDifference = mouseXDifference + mouseYDifference;
|
||||
dimensionChange = mouseDifference * dimensionsMultiple;
|
||||
}
|
||||
|
||||
var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3;
|
||||
percentChange = dimensionChange / averageInitialDimension;
|
||||
percentChange += 1.0;
|
||||
|
|
|
@ -564,7 +564,7 @@
|
|||
}
|
||||
|
||||
function walletNotSetup() {
|
||||
createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET);
|
||||
createNotification("Your wallet isn't activated yet. Open the WALLET app.", NotificationType.WALLET);
|
||||
}
|
||||
|
||||
function connectionAdded(connectionName) {
|
||||
|
|
Loading…
Reference in a new issue