diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index d785579c38..ffd7cc703b 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -94,7 +94,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); - + packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); + connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -385,8 +386,11 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { // loop through all other nodes that have sufficient audio to mix DependencyManager::get()->eachNode([&](const SharedNodePointer& otherNode){ - // make sure that we have audio data for this other node and that it isn't being ignored by our listening node - if (otherNode->getLinkedData() && !node->isIgnoringNodeWithID(otherNode->getUUID())) { + // make sure that we have audio data for this other node + // and that it isn't being ignored by our listening node + // and that it isn't ignoring our listening node + if (otherNode->getLinkedData() + && !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix @@ -489,7 +493,7 @@ void AudioMixer::handleNodeAudioPacket(QSharedPointer message, void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer message, SharedNodePointer sendingNode) { auto nodeList = DependencyManager::get(); - if (sendingNode->isAllowedEditor()) { + if (sendingNode->getCanKick()) { glm::vec3 position; float radius; @@ -599,6 +603,23 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { }); } +void AudioMixer::handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto nodeList = DependencyManager::get(); + QUuid nodeUUID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + if (sendingNode->getCanKick()) { + auto node = nodeList->nodeWithUUID(nodeUUID); + if (node) { + // we need to set a flag so we send them the appropriate packet to mute them + AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); + nodeData->setShouldMuteClient(true); + } else { + qWarning() << "Node mute packet received for unknown node " << uuidStringWithoutCurlyBraces(nodeUUID); + } + } else { + qWarning() << "Node mute packet received from node that cannot mute, ignoring"; + } +} + void AudioMixer::handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode) { auto clientData = dynamic_cast(sendingNode->getLinkedData()); if (clientData) { @@ -814,9 +835,13 @@ void AudioMixer::broadcastMixes() { // if the stream should be muted, send mute packet if (nodeData->getAvatarAudioStream() - && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) { + && (shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness()) + || nodeData->shouldMuteClient())) { auto mutePacket = NLPacket::create(PacketType::NoisyMute, 0); nodeList->sendPacket(std::move(mutePacket), *node); + + // probably now we just reset the flag, once should do it (?) + nodeData->setShouldMuteClient(false); } if (node->getType() == NodeType::Agent && node->getActiveSocket() diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 3c68e4c6af..91eafadd9d 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -49,6 +49,7 @@ private slots: void handleNodeKilled(SharedNodePointer killedNode); void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleNodeMuteRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void removeHRTFsForFinishedInjector(const QUuid& streamID); diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 52c659c240..c74461a444 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -86,6 +86,9 @@ public: bool shouldFlushEncoder() { return _shouldFlushEncoder; } QString getCodecName() { return _selectedCodecName; } + + bool shouldMuteClient() { return _shouldMuteClient; } + void setShouldMuteClient(bool shouldMuteClient) { _shouldMuteClient = shouldMuteClient; } signals: void injectorStreamFinished(const QUuid& streamIdentifier); @@ -114,6 +117,8 @@ private: Decoder* _decoder{ nullptr }; // for mic stream bool _shouldFlushEncoder { false }; + + bool _shouldMuteClient { false }; }; #endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index d87a5f1cc9..2c9fadc7b1 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -230,9 +230,11 @@ void AvatarMixer::broadcastAvatarData() { [&](const SharedNodePointer& otherNode)->bool { // 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 (!otherNode->getLinkedData() || otherNode->getUUID() == node->getUUID() - || node->isIgnoringNodeWithID(otherNode->getUUID())) { + || node->isIgnoringNodeWithID(otherNode->getUUID()) + || otherNode->isIgnoringNodeWithID(node->getUUID())) { return false; } else { return true; @@ -512,12 +514,19 @@ void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); - nodeList->linkedDataCreateCallback = [] (Node* node) { - node->setLinkedData(std::unique_ptr { new AvatarMixerClientData }); - }; - // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); + + float domainMinimumScale = _domainMinimumScale; + float domainMaximumScale = _domainMaximumScale; + + nodeList->linkedDataCreateCallback = [domainMinimumScale, domainMaximumScale] (Node* node) { + auto clientData = std::unique_ptr { new AvatarMixerClientData }; + clientData->getAvatar().setDomainMinimumScale(domainMinimumScale); + clientData->getAvatar().setDomainMaximumScale(domainMaximumScale); + + node->setLinkedData(std::move(clientData)); + }; // start the broadcastThread _broadcastThread.start(); @@ -549,4 +558,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { _maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA; qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; + + const QString AVATARS_SETTINGS_KEY = "avatars"; + + static const QString MIN_SCALE_OPTION = "min_avatar_scale"; + float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); + _domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + + static const QString MAX_SCALE_OPTION = "max_avatar_scale"; + float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); + _domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + + // make sure that the domain owner didn't flip min and max + if (_domainMinimumScale > _domainMaximumScale) { + std::swap(_domainMinimumScale, _domainMaximumScale); + } + + qDebug() << "This domain requires a minimum avatar scale of" << _domainMinimumScale + << "and a maximum avatar scale of" << _domainMaximumScale; } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 9286cd4691..6e1d722145 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -59,6 +59,9 @@ private: float _maxKbpsPerNode = 0.0f; + float _domainMinimumScale { MIN_AVATAR_SCALE }; + float _domainMaximumScale { MAX_AVATAR_SCALE }; + QTimer* _broadcastTimer = nullptr; }; diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 56707de471..18d64f4ac5 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -17,7 +17,7 @@ #include #include -class ScriptableAvatar : public AvatarData, public Dependency{ +class ScriptableAvatar : public AvatarData, public Dependency { Q_OBJECT public: @@ -39,4 +39,4 @@ private: std::shared_ptr _animSkeleton; }; -#endif // hifi_ScriptableAvatar_h \ No newline at end of file +#endif // hifi_ScriptableAvatar_h diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 7594d5dd2c..23eec6197c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -278,6 +278,13 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); + + QString entityScriptSourceWhitelist; + if (readOptionString("entityScriptSourceWhitelist", settingsSectionObject, entityScriptSourceWhitelist)) { + tree->setEntityScriptSourceWhitelist(entityScriptSourceWhitelist); + } else { + tree->setEntityScriptSourceWhitelist(""); + } } void EntityServer::nodeAdded(SharedNodePointer node) { diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 7808812493..6df41257f2 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -16,6 +16,7 @@ if (HIFI_MEMORY_DEBUGGING) if (UNIX) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") endif (UNIX) endif () endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 4006673cad..911732fcef 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -388,6 +388,23 @@ "default": "", "advanced": false }, + { + "name": "ac_subnet_whitelist", + "label": "Assignment Client IP address Whitelist", + "type": "table", + "can_add_new_rows": true, + "help": "The IP addresses or subnets of ACs that can connect to this server. You can specify an IP address or a subnet in CIDR notation ('A.B.C.D/E', Example: '10.0.0.0/24'). Local ACs (localhost) are always permitted and do not need to be added here.", + "numbered": false, + "advanced": true, + "columns": [ + { + "name": "ip", + "label": "IP Address", + "type": "ip", + "can_set": true + } + ] + }, { "name": "standard_permissions", "type": "table", @@ -866,6 +883,29 @@ } ] }, + { + "name": "avatars", + "label": "Avatars", + "assignment-types": [1, 2], + "settings": [ + { + "name": "min_avatar_scale", + "type": "double", + "label": "Minimum Avatar Scale", + "help": "Limits the scale of avatars in your domain. Must be at least 0.005.", + "placeholder": 0.25, + "default": 0.25 + }, + { + "name": "max_avatar_scale", + "type": "double", + "label": "Maximum Avatar Scale", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", + "placeholder": 3.0, + "default": 3.0 + } + ] + }, { "name": "audio_env", "label": "Audio Environment", @@ -1075,6 +1115,14 @@ "default": "3600", "advanced": true }, + { + "name": "entityScriptSourceWhitelist", + "label": "Entity Scripts Allowed from:", + "help": "The domains that entity scripts are allowed from. A comma separated list of domains that entity scripts are allowed from, if someone attempts to create and entity or edit an entity to have a different domain, it will be rejected. If left blank, any domain is allowed.", + "placeholder": "", + "default": "", + "advanced": true + }, { "name": "persistFilePath", "label": "Entities File Path", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d13f9b883f..5208cb2326 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -158,6 +158,42 @@ DomainServer::DomainServer(int argc, char* argv[]) : qDebug() << "domain-server is running"; + static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; + + static const Subnet LOCALHOST { QHostAddress("127.0.0.1"), 32 }; + _acSubnetWhitelist = { LOCALHOST }; + + auto whitelist = _settingsManager.valueOrDefaultValueForKeyPath(AC_SUBNET_WHITELIST_SETTING_PATH).toStringList(); + for (auto& subnet : whitelist) { + auto netmaskParts = subnet.trimmed().split("/"); + + if (netmaskParts.size() > 2) { + qDebug() << "Ignoring subnet in whitelist, malformed: " << subnet; + continue; + } + + // The default netmask is 32 if one has not been specified, which will + // match only the ip provided. + int netmask = 32; + + if (netmaskParts.size() == 2) { + bool ok; + netmask = netmaskParts[1].toInt(&ok); + if (!ok) { + qDebug() << "Ignoring subnet in whitelist, bad netmask: " << subnet; + continue; + } + } + + auto ip = QHostAddress(netmaskParts[0]); + + if (!ip.isNull()) { + qDebug() << "Adding AC whitelist subnet: " << subnet << " -> " << (ip.toString() + "/" + QString::number(netmask)); + _acSubnetWhitelist.push_back({ ip , netmask }); + } else { + qDebug() << "Ignoring subnet in whitelist, invalid ip portion: " << subnet; + } + } } void DomainServer::parseCommandLine() { @@ -1001,6 +1037,21 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointergetSenderSockAddr().getAddress(); + + auto isHostAddressInSubnet = [&senderAddr](const Subnet& mask) -> bool { + return senderAddr.isInSubnet(mask); + }; + + auto it = find_if(_acSubnetWhitelist.begin(), _acSubnetWhitelist.end(), isHostAddressInSubnet); + if (it == _acSubnetWhitelist.end()) { + static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex( + "Received an assignment connect request from a disallowed ip address: [^ ]+"); + qDebug() << "Received an assignment connect request from a disallowed ip address:" + << senderAddr.toString(); + return; + } + // Suppress these for Assignment::AgentType to once per 5 seconds static QElapsedTimer noisyMessageTimer; static bool wasNoisyTimerStarted = false; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c14ec5eee0..73135695eb 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -36,6 +36,9 @@ typedef QSharedPointer SharedAssignmentPointer; typedef QMultiHash TransactionHash; +using Subnet = QPair; +using SubnetList = std::vector; + class DomainServer : public QCoreApplication, public HTTPSRequestHandler { Q_OBJECT public: @@ -156,6 +159,8 @@ private: void setupGroupCacheRefresh(); + SubnetList _acSubnetWhitelist; + DomainGatekeeper _gatekeeper; HTTPManager _httpManager; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index c7944bbcad..fbc5fd4bd5 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -634,7 +634,6 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { return changed; } - void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode) { // before we do any processing on this packet make sure it comes from a node that is allowed to kick if (sendingNode->getCanKick()) { @@ -1077,6 +1076,9 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson } bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { + static const QString SECURITY_ROOT_KEY = "security"; + static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; + auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1127,7 +1129,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY) { needRestart = true; } } else { @@ -1143,7 +1145,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { QJsonValue settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); - if (rootKey != "security") { + if (rootKey != SECURITY_ROOT_KEY || settingKey == AC_SUBNET_WHITELIST_KEY) { needRestart = true; } } else { diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json index b0e97b849f..36065b87a1 100644 --- a/interface/resources/controllers/xbox.json +++ b/interface/resources/controllers/xbox.json @@ -4,12 +4,12 @@ { "from": "GamePad.LY", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateZ" }, { "from": "GamePad.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" }, - { "from": "GamePad.LT", "to": "Standard.LTClick", + { "from": "GamePad.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] }, - { "from": "GamePad.LT", "to": "Standard.LT" }, - { "from": "GamePad.LB", "to": "Standard.LB" }, + { "from": "GamePad.LT", "to": "Standard.LT" }, + { "from": "GamePad.LB", "to": "Standard.LB" }, { "from": "GamePad.LS", "to": "Standard.LS" }, @@ -27,34 +27,34 @@ { "from": "GamePad.RX", "to": "Actions.Yaw" }, - { "from": "GamePad.RY", - "to": "Actions.VERTICAL_UP", - "filters": + { "from": "GamePad.RY", + "to": "Actions.VERTICAL_UP", + "filters": [ { "type": "deadZone", "min": 0.95 }, "invert" ] - }, + }, - { "from": "GamePad.RT", "to": "Standard.RTClick", + { "from": "GamePad.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] }, - { "from": "GamePad.RT", "to": "Standard.RT" }, - { "from": "GamePad.RB", "to": "Standard.RB" }, + { "from": "GamePad.RT", "to": "Standard.RT" }, + { "from": "GamePad.RB", "to": "Standard.RB" }, { "from": "GamePad.RS", "to": "Standard.RS" }, { "from": "GamePad.Start", "to": "Actions.CycleCamera" }, - { "from": "GamePad.Back", "to": "Actions.ContextMenu" }, + { "from": "GamePad.Back", "to": "Standard.Start" }, { "from": "GamePad.DU", "to": "Standard.DU" }, - { "from": "GamePad.DD", "to": "Standard.DD" }, + { "from": "GamePad.DD", "to": "Standard.DD" }, { "from": "GamePad.DL", "to": "Standard.DL" }, - { "from": "GamePad.DR", "to": "Standard.DR" }, + { "from": "GamePad.DR", "to": "Standard.DR" }, { "from": [ "GamePad.Y" ], "to": "Standard.RightPrimaryThumb", "peek": true }, - { "from": "GamePad.A", "to": "Standard.A" }, - { "from": "GamePad.B", "to": "Standard.B" }, + { "from": "GamePad.A", "to": "Standard.A" }, + { "from": "GamePad.B", "to": "Standard.B" }, { "from": "GamePad.X", "to": "Standard.X" }, { "from": "GamePad.Y", "to": "Standard.Y" } ] diff --git a/interface/resources/images/steam-min-spec-failed.png b/interface/resources/images/steam-min-spec-failed.png index 99abac9e1c..0061167a92 100644 Binary files a/interface/resources/images/steam-min-spec-failed.png and b/interface/resources/images/steam-min-spec-failed.png differ diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 9c55b1ce2d..734c442437 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -69,6 +69,10 @@ Item { StatText { text: "Present Drop Rate: " + root.presentdroprate.toFixed(2); } + StatText { + text: "Stutter Rate: " + root.stutterrate.toFixed(3); + visible: root.stutterrate != -1; + } StatText { text: "Simrate: " + root.simrate } @@ -241,7 +245,7 @@ Item { text: "GPU Buffers: " } StatText { - text: " Count: " + root.gpuTextures; + text: " Count: " + root.gpuBuffers; } StatText { text: " Memory: " + root.gpuBufferMemory; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3dcdf4c695..105aa74c20 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1199,6 +1199,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["present_rate"] = displayPlugin->presentRate(); properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); + properties["stutter_rate"] = displayPlugin->stutterRate(); properties["sim_rate"] = getAverageSimsPerSecond(); properties["avatar_sim_rate"] = getAvatarSimrate(); properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6207ecdb3c..dfa1a2f8b9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -396,15 +396,6 @@ Menu::Menu() { }); } - // Developer > Render > Enable Incremental Texture Transfer - { - auto action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::IncrementalTextureTransfer, 0, gpu::Texture::getEnableIncrementalTextureTransfers()); - connect(action, &QAction::triggered, [&](bool checked) { - qDebug() << "[TEXTURE TRANSFER SUPPORT] --- Enable Incremental Texture Transfer menu option:" << checked; - gpu::Texture::setEnableIncrementalTextureTransfers(checked); - }); - } - #else qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer and Dynamic Texture Management not supported on this platform."; #endif diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 87e0e678f1..e339da4d25 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -112,7 +112,6 @@ namespace MenuOption { const QString FrameTimer = "Show Timer"; const QString FullscreenMirror = "Mirror"; const QString Help = "Help..."; - const QString IncrementalTextureTransfer = "Enable Incremental Texture Transfer"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; const QString ActionMotorControl = "Enable Default Motor Control"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 872d2af04e..46ee15de08 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -165,16 +165,17 @@ AABox Avatar::getBounds() const { void Avatar::animateScaleChanges(float deltaTime) { float currentScale = getUniformScale(); - if (currentScale != _targetScale) { - // use exponential decay toward _targetScale + auto desiredScale = getDomainLimitedScale(); + if (currentScale != desiredScale) { + // use exponential decay toward the domain limit clamped scale const float SCALE_ANIMATION_TIMESCALE = 0.5f; float blendFactor = glm::clamp(deltaTime / SCALE_ANIMATION_TIMESCALE, 0.0f, 1.0f); - float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * _targetScale; + float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough const float MIN_RELATIVE_SCALE_ERROR = 0.03f; - if (fabsf(_targetScale - currentScale) / _targetScale < MIN_RELATIVE_SCALE_ERROR) { - animatedScale = _targetScale; + if (fabsf(desiredScale - currentScale) / desiredScale < MIN_RELATIVE_SCALE_ERROR) { + animatedScale = desiredScale; } setScale(glm::vec3(animatedScale)); // avatar scale is uniform diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 85ff485d7a..32a3586dba 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -152,7 +152,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: Transform avatarTransform; auto myAvatar = DependencyManager::get()->getMyAvatar(); avatarTransform = myAvatar->getTransform(); - palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getTargetScale()); + palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getDomainLimitedScale()); palmRotation = avatarTransform.getRotation() * pose.getRotation(); } else { glm::vec3 avatarRigidBodyPosition; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 735ba05810..b49ee94ff6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -130,6 +130,15 @@ MyAvatar::MyAvatar(RigPointer rig) : connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired, this, static_cast(&MyAvatar::goToLocation)); + // handle scale constraints imposed on us by the domain-server + auto& domainHandler = DependencyManager::get()->getDomainHandler(); + + // when we connect to a domain and retrieve its settings, we restrict our max/min scale based on those settings + connect(&domainHandler, &DomainHandler::settingsReceived, this, &MyAvatar::restrictScaleFromDomainSettings); + + // when we leave a domain we lift whatever restrictions that domain may have placed on our scale + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction); + _characterController.setEnabled(true); _bodySensorMatrix = deriveBodyFromHMDSensor(); @@ -1823,25 +1832,104 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float return false; } -void MyAvatar::increaseSize() { - if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { - _targetScale *= (1.0f + SCALING_RATIO); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); +// There can be a separation between the _targetScale and the actual scale of the rendered avatar in a domain. +// When the avatar enters a domain where their target scale is not allowed according to the min/max +// we do not change their saved target scale. Instead, we use getDomainLimitedScale() to render the avatar +// at a domain appropriate size. When the avatar leaves the limiting domain, we'll return them to their previous target scale. +// While connected to a domain that limits avatar scale if the user manually changes their avatar scale, we change +// target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were +// before they entered the limiting domain. + +void MyAvatar::clampTargetScaleToDomainLimits() { + // when we're about to change the target scale because the user has asked to increase or decrease their scale, + // we first make sure that we're starting from a target scale that is allowed by the current domain + + auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); + + if (clampedTargetScale != _targetScale) { + qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain", + (double)clampedTargetScale, (double)_targetScale); + + setTargetScale(clampedTargetScale); } } +void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { + auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale); + + if (clampedTargetScale != desiredScale) { + qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain", + clampedTargetScale, desiredScale); + } + + setTargetScale(clampedTargetScale); + qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); +} + +void MyAvatar::increaseSize() { + // make sure we're starting from an allowable scale + clampTargetScaleToDomainLimits(); + + // calculate what our new scale should be + float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); + + // attempt to change to desired scale (clamped to the domain limits) + clampScaleChangeToDomainLimits(updatedTargetScale); +} + void MyAvatar::decreaseSize() { - if (MIN_AVATAR_SCALE < (1.0f - SCALING_RATIO) * _targetScale) { - _targetScale *= (1.0f - SCALING_RATIO); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - } + // make sure we're starting from an allowable scale + clampTargetScaleToDomainLimits(); + + // calculate what our new scale should be + float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); + + // attempt to change to desired scale (clamped to the domain limits) + clampScaleChangeToDomainLimits(updatedTargetScale); } void MyAvatar::resetSize() { - _targetScale = 1.0f; - qCDebug(interfaceapp, "Reset scale to %f", (double)_targetScale); + // attempt to reset avatar size to the default (clamped to domain limits) + const float DEFAULT_AVATAR_SCALE = 1.0f; + + clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); } +void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { + // pull out the minimum and maximum scale and set them to restrict our scale + + static const QString AVATAR_SETTINGS_KEY = "avatars"; + auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); + + static const QString MIN_SCALE_OPTION = "min_avatar_scale"; + float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); + setDomainMinimumScale(settingMinScale); + + static const QString MAX_SCALE_OPTION = "max_avatar_scale"; + float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); + setDomainMaximumScale(settingMaxScale); + + // make sure that the domain owner didn't flip min and max + if (_domainMinimumScale > _domainMaximumScale) { + std::swap(_domainMinimumScale, _domainMaximumScale); + } + + qCDebug(interfaceapp, "This domain requires a minimum avatar scale of %f and a maximum avatar scale of %f", + (double)_domainMinimumScale, (double)_domainMaximumScale); + + // debug to log if this avatar's scale in this domain will be clamped + auto clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); + + if (_targetScale != clampedScale) { + qCDebug(interfaceapp, "Avatar scale will be clamped to %f because %f is not allowed by current domain", + (double)clampedScale, (double)_targetScale); + } +} + +void MyAvatar::clearScaleRestriction() { + _domainMinimumScale = MIN_AVATAR_SCALE; + _domainMaximumScale = MAX_AVATAR_SCALE; +} void MyAvatar::goToLocation(const QVariant& propertiesVar) { qCDebug(interfaceapp, "MyAvatar QML goToLocation"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 71f185c6ed..60049bea67 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -292,6 +292,9 @@ public slots: bool shouldFaceLocation = false); void goToLocation(const QVariant& properties); + void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); + void clearScaleRestriction(); + // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; glm::vec3 getThrust() { return _thrust; }; @@ -369,6 +372,8 @@ private: virtual void updatePalms() override {} void lateUpdatePalms(); + void clampTargetScaleToDomainLimits(); + void clampScaleChangeToDomainLimits(float desiredScale); float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h index 0f958f2286..748f110356 100644 --- a/interface/src/scripting/AccountScriptingInterface.h +++ b/interface/src/scripting/AccountScriptingInterface.h @@ -19,12 +19,35 @@ class AccountScriptingInterface : public QObject { Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged) + /**jsdoc + * @namespace Account + * @property username {String} username if user is logged in, otherwise it returns "Unknown user" + */ + signals: + + /**jsdoc + * Triggered when username has changed. + * @function Account.usernameChanged + * @return {Signal} + */ void usernameChanged(); public slots: static AccountScriptingInterface* getInstance(); + + /**jsdoc + * Returns the username for the currently logged in High Fidelity metaverse account. + * @function Account.getUsername + * @return {string} username if user is logged in, otherwise it returns "Unknown user" + */ QString getUsername(); + + /**jsdoc + * Determine if the user is logged into the High Fidleity metaverse. + * @function Account.isLoggedIn + * @return {bool} true when user is logged into the High Fidelity metaverse. + */ bool isLoggedIn(); bool checkAndSignalForAccessToken(); }; diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 2fa7470561..df75d331d6 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -126,6 +126,23 @@ void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool Q_ARG(bool, isChecked)); } +bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { + if (QThread::currentThread() == qApp->thread()) { + return Menu::getInstance()->isOptionChecked(menuOption); + } + bool result; + QMetaObject::invokeMethod(Menu::getInstance(), "isMenuEnabled", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menuOption)); + return result; +} + +void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isChecked) { + QMetaObject::invokeMethod(Menu::getInstance(), "setMenuEnabled", + Q_ARG(const QString&, menuOption), + Q_ARG(bool, isChecked)); +} + void MenuScriptingInterface::triggerOption(const QString& menuOption) { QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption)); } diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index 5b8a437529..855b1af13b 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -50,6 +50,9 @@ public slots: void setIsOptionChecked(const QString& menuOption, bool isChecked); void triggerOption(const QString& menuOption); + + bool isMenuEnabled(const QString& menuName); + void setMenuEnabled(const QString& menuName, bool isEnabled); signals: void menuItemEvent(const QString& menuItem); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index edf72d9758..bad2c3c056 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -128,7 +128,8 @@ void Stats::updateStats(bool force) { STAT_UPDATE(renderrate, displayPlugin->renderRate()); STAT_UPDATE(presentrate, displayPlugin->presentRate()); STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate()); - STAT_UPDATE(presentdroprate, qApp->getActiveDisplayPlugin()->droppedFrameRate()); + STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate()); + STAT_UPDATE(stutterrate, displayPlugin->stutterRate()); } else { STAT_UPDATE(presentrate, -1); STAT_UPDATE(presentnewrate, -1); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index ffa5c08bc6..5d1aac4287 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -36,7 +36,9 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, renderrate, 0) // How often the display plugin is presenting to the device STATS_PROPERTY(float, presentrate, 0) - + // How often the display device reprojecting old frames + STATS_PROPERTY(float, stutterrate, 0) + STATS_PROPERTY(float, presentnewrate, 0) STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, simrate, 0) @@ -140,6 +142,7 @@ signals: void presentrateChanged(); void presentnewrateChanged(); void presentdroprateChanged(); + void stutterrateChanged(); void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index a7d8700fed..59e3de9bcb 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -29,7 +29,19 @@ class AnimationCache : public ResourceCache, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + /**jsdoc + * @namespace AnimationCache + * @augments ResourceCache + */ + public: + + /**jsdoc + * Returns animation resource for particular animation + * @function AnimationCache.getAnimation + * @param url {string} url to load + * @return {Resource} animation + */ Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 877c6c3e91..45790524d1 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -308,6 +308,13 @@ void Rig::clearIKJointLimitHistory() { } } +int Rig::getJointParentIndex(int childIndex) const { + if (_animSkeleton && isIndexValid(childIndex)) { + return _animSkeleton->getParentIndex(childIndex); + } + return -1; +} + void Rig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) { if (isIndexValid(index)) { if (valid) { @@ -414,6 +421,16 @@ bool Rig::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& trans } } +bool Rig::getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) const { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._absolutePoses.size()) { + returnPose = _externalPoseSet._absolutePoses[jointIndex]; + return true; + } else { + return false; + } +} + bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const { // AJT: TODO: used by attachments ASSERT(false); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 86b8dadd85..151a7ae8e9 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -105,6 +105,8 @@ public: void clearIKJointLimitHistory(); + int getJointParentIndex(int childIndex) const; + // geometry space void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority); @@ -133,6 +135,7 @@ public: // rig space (thread-safe) bool getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotation) const; bool getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translation) const; + bool getAbsoluteJointPoseInRigFrame(int jointIndex, AnimPose& returnPose) const; // legacy bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9df23dad2c..2469d0df04 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -210,7 +210,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 0), bodyEulerAngles.y); packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 1), bodyEulerAngles.x); packFloatAngleToTwoByte((uint8_t*)(header->localOrientation + 2), bodyEulerAngles.z); - packFloatRatioToTwoByte((uint8_t*)(&header->scale), _targetScale); + packFloatRatioToTwoByte((uint8_t*)(&header->scale), getDomainLimitedScale()); header->lookAtPosition[0] = _headData->_lookAtPosition.x; header->lookAtPosition[1] = _headData->_lookAtPosition.y; header->lookAtPosition[2] = _headData->_lookAtPosition.z; @@ -516,7 +516,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } return buffer.size(); } - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); + setTargetScale(scale); glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); if (isNaN(lookAt)) { @@ -1439,7 +1439,7 @@ QJsonObject AvatarData::toJson() const { if (!success) { qDebug() << "Warning -- AvatarData::toJson couldn't get avatar transform"; } - avatarTransform.setScale(getTargetScale()); + avatarTransform.setScale(getDomainLimitedScale()); if (recordingBasis) { root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); // Find the relative transform @@ -1451,7 +1451,7 @@ QJsonObject AvatarData::toJson() const { root[JSON_AVATAR_RELATIVE] = Transform::toJson(avatarTransform); } - auto scale = getTargetScale(); + auto scale = getDomainLimitedScale(); if (scale != 1.0f) { root[JSON_AVATAR_SCALE] = scale; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index cb3ef0c40e..97879700ee 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -243,6 +243,12 @@ public: void setTargetScale(float targetScale); void setTargetScaleVerbose(float targetScale); + float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + void setDomainMinimumScale(float domainMinimumScale) + { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); } + void setDomainMaximumScale(float domainMaximumScale) + { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); } + // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } Q_INVOKABLE char getHandState() const { return _handState; } @@ -377,6 +383,8 @@ protected: // Body scale float _targetScale; + float _domainMinimumScale { MIN_AVATAR_SCALE }; + float _domainMaximumScale { MAX_AVATAR_SCALE }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h index 2e4e57e15a..f3dd50602c 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -18,7 +18,7 @@ class Basic2DWindowOpenGLDisplayPlugin : public OpenGLDisplayPlugin { Q_OBJECT using Parent = OpenGLDisplayPlugin; public: - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } virtual float getTargetFrameRate() const override { return _framerateTarget ? (float) _framerateTarget : TARGET_FRAMERATE_Basic2DWindowOpenGL; } diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 1852ed53ee..1da0441d8f 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -12,7 +12,7 @@ class NullDisplayPlugin : public DisplayPlugin { public: ~NullDisplayPlugin() final {} - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index c84f6b9954..63c4692a5f 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -758,7 +758,6 @@ void OpenGLDisplayPlugin::render(std::function f) { OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { - qDebug() << "Destroying OpenGLDisplayPlugin"; } void OpenGLDisplayPlugin::updateCompositeFramebuffer() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h index 509e13eda7..9bb82b1836 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.h @@ -13,7 +13,7 @@ class DebugHmdDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } bool isSupported() const override; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 2e66659de7..c5d7ac5690 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -710,3 +710,7 @@ void HmdDisplayPlugin::compositeExtra() { HmdDisplayPlugin::~HmdDisplayPlugin() { qDebug() << "Destroying HmdDisplayPlugin"; } + +float HmdDisplayPlugin::stutterRate() const { + return _stutterRate.rate(); +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e50183dd90..435f547899 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -44,6 +44,8 @@ public: return false; } + float stutterRate() const override; + protected: virtual void hmdPresent() = 0; virtual bool isHmdMounted() const = 0; @@ -108,8 +110,9 @@ protected: QMap _frameInfos; FrameInfo _currentPresentFrameInfo; FrameInfo _currentRenderFrameInfo; + RateCounter<> _stutterRate; - bool _disablePreview{ true }; + bool _disablePreview { true }; private: ivec4 getViewportForSourceSize(const uvec2& size) const; float getLeftCenterPixel() const; diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h index 8c3ebcaa6d..debd340f24 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -13,7 +13,7 @@ class InterleavedStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT using Parent = StereoDisplayPlugin; public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return ADVANCED; } glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h index d4d20243ea..79bf2a9ecb 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -15,7 +15,7 @@ class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { Q_OBJECT using Parent = StereoDisplayPlugin; public: - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } virtual grouping getGrouping() const override { return ADVANCED; } virtual glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 332a88e499..dc5b6cd8d3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -310,14 +310,14 @@ bool RenderableModelEntityItem::getAnimationFrame() { } glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * rotationMat * fbxJoints[index].postTransform); - _absoluteJointTranslationsInObjectFrame[j] = extractTranslation(finalMat); - _absoluteJointTranslationsInObjectFrameSet[j] = true; - _absoluteJointTranslationsInObjectFrameDirty[j] = true; + _localJointTranslations[j] = extractTranslation(finalMat); + _localJointTranslationsSet[j] = true; + _localJointTranslationsDirty[j] = true; - _absoluteJointRotationsInObjectFrame[j] = glmExtractRotation(finalMat); + _localJointRotations[j] = glmExtractRotation(finalMat); - _absoluteJointRotationsInObjectFrameSet[j] = true; - _absoluteJointRotationsInObjectFrameDirty[j] = true; + _localJointRotationsSet[j] = true; + _localJointRotationsDirty[j] = true; } } } @@ -390,18 +390,18 @@ void RenderableModelEntityItem::render(RenderArgs* args) { getAnimationFrame(); // relay any inbound joint changes from scripts/animation/network to the model/rig - for (int index = 0; index < _absoluteJointRotationsInObjectFrame.size(); index++) { - if (_absoluteJointRotationsInObjectFrameDirty[index]) { - glm::quat rotation = _absoluteJointRotationsInObjectFrame[index]; + for (int index = 0; index < _localJointRotations.size(); index++) { + if (_localJointRotationsDirty[index]) { + glm::quat rotation = _localJointRotations[index]; _model->setJointRotation(index, true, rotation, 1.0f); - _absoluteJointRotationsInObjectFrameDirty[index] = false; + _localJointRotationsDirty[index] = false; } } - for (int index = 0; index < _absoluteJointTranslationsInObjectFrame.size(); index++) { - if (_absoluteJointTranslationsInObjectFrameDirty[index]) { - glm::vec3 translation = _absoluteJointTranslationsInObjectFrame[index]; + for (int index = 0; index < _localJointTranslations.size(); index++) { + if (_localJointTranslationsDirty[index]) { + glm::vec3 translation = _localJointTranslations[index]; _model->setJointTranslation(index, true, translation, 1.0f); - _absoluteJointTranslationsInObjectFrameDirty[index] = false; + _localJointTranslationsDirty[index] = false; } } }); @@ -1028,15 +1028,101 @@ glm::vec3 RenderableModelEntityItem::getAbsoluteJointTranslationInObjectFrame(in } bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { + if (!_model) { + return false; + } + RigPointer rig = _model->getRig(); + if (!rig) { + return false; + } + + int jointParentIndex = rig->getJointParentIndex(index); + if (jointParentIndex == -1) { + return setLocalJointRotation(index, rotation); + } + + bool success; + AnimPose jointParentPose; + success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); + if (!success) { + return false; + } + AnimPose jointParentInversePose = jointParentPose.inverse(); + + AnimPose jointAbsolutePose; // in rig frame + success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); + if (!success) { + return false; + } + jointAbsolutePose.rot = rotation; + + AnimPose jointRelativePose = jointParentInversePose * jointAbsolutePose; + return setLocalJointRotation(index, jointRelativePose.rot); +} + +bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) { + if (!_model) { + return false; + } + RigPointer rig = _model->getRig(); + if (!rig) { + return false; + } + + int jointParentIndex = rig->getJointParentIndex(index); + if (jointParentIndex == -1) { + return setLocalJointTranslation(index, translation); + } + + bool success; + AnimPose jointParentPose; + success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose); + if (!success) { + return false; + } + AnimPose jointParentInversePose = jointParentPose.inverse(); + + AnimPose jointAbsolutePose; // in rig frame + success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose); + if (!success) { + return false; + } + jointAbsolutePose.trans = translation; + + AnimPose jointRelativePose = jointParentInversePose * jointAbsolutePose; + return setLocalJointTranslation(index, jointRelativePose.trans); +} + +glm::quat RenderableModelEntityItem::getLocalJointRotation(int index) const { + if (_model) { + glm::quat result; + if (_model->getJointRotation(index, result)) { + return result; + } + } + return glm::quat(); +} + +glm::vec3 RenderableModelEntityItem::getLocalJointTranslation(int index) const { + if (_model) { + glm::vec3 result; + if (_model->getJointTranslation(index, result)) { + return result; + } + } + return glm::vec3(); +} + +bool RenderableModelEntityItem::setLocalJointRotation(int index, const glm::quat& rotation) { bool result = false; _jointDataLock.withWriteLock([&] { _jointRotationsExplicitlySet = true; resizeJointArrays(); - if (index >= 0 && index < _absoluteJointRotationsInObjectFrame.size() && - _absoluteJointRotationsInObjectFrame[index] != rotation) { - _absoluteJointRotationsInObjectFrame[index] = rotation; - _absoluteJointRotationsInObjectFrameSet[index] = true; - _absoluteJointRotationsInObjectFrameDirty[index] = true; + if (index >= 0 && index < _localJointRotations.size() && + _localJointRotations[index] != rotation) { + _localJointRotations[index] = rotation; + _localJointRotationsSet[index] = true; + _localJointRotationsDirty[index] = true; result = true; _needsJointSimulation = true; } @@ -1044,16 +1130,16 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, return result; } -bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) { +bool RenderableModelEntityItem::setLocalJointTranslation(int index, const glm::vec3& translation) { bool result = false; _jointDataLock.withWriteLock([&] { _jointTranslationsExplicitlySet = true; resizeJointArrays(); - if (index >= 0 && index < _absoluteJointTranslationsInObjectFrame.size() && - _absoluteJointTranslationsInObjectFrame[index] != translation) { - _absoluteJointTranslationsInObjectFrame[index] = translation; - _absoluteJointTranslationsInObjectFrameSet[index] = true; - _absoluteJointTranslationsInObjectFrameDirty[index] = true; + if (index >= 0 && index < _localJointTranslations.size() && + _localJointTranslations[index] != translation) { + _localJointTranslations[index] = translation; + _localJointTranslationsSet[index] = true; + _localJointTranslationsDirty[index] = true; result = true; _needsJointSimulation = true; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index a52b0b0041..93d48c6085 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -74,6 +74,12 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override; virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override; + + virtual glm::quat getLocalJointRotation(int index) const override; + virtual glm::vec3 getLocalJointTranslation(int index) const override; + virtual bool setLocalJointRotation(int index, const glm::quat& rotation) override; + virtual bool setLocalJointTranslation(int index, const glm::vec3& translation) override; + virtual void setJointRotations(const QVector& rotations) override; virtual void setJointRotationsSet(const QVector& rotationsSet) override; virtual void setJointTranslations(const QVector& translations) override; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 68636415f8..babf714d9b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -43,6 +43,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _lastSimulated(0), _lastUpdated(0), _lastEdited(0), + _lastEditedBy(ENTITY_ITEM_DEFAULT_LAST_EDITED_BY), _lastEditedFromRemote(0), _lastEditedFromRemoteInRemoteTime(0), _created(UNKNOWN_CREATED_TIME), @@ -141,6 +142,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_CLIENT_ONLY; requestedProperties += PROP_OWNING_AVATAR_ID; + requestedProperties += PROP_LAST_EDITED_BY; + return requestedProperties; } @@ -279,6 +282,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, getParentID()); APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); + APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -803,6 +807,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); + READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -1205,6 +1210,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly); COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); + properties._defaultSettings = false; return properties; @@ -1307,6 +1314,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly); SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); + AACube saveQueryAACube = _queryAACube; checkAndAdjustQueryAACube(); if (saveQueryAACube != _queryAACube) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index a751d76b2a..19d6aec783 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -411,8 +411,9 @@ public: // these are in the frame of this object virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); } virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); } - virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } - virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + + virtual bool setLocalJointRotation(int index, const glm::quat& rotation) override { return false; } + virtual bool setLocalJointTranslation(int index, const glm::vec3& translation) override { return false; } virtual int getJointIndex(const QString& name) const { return -1; } virtual QStringList getJointNames() const { return QStringList(); } @@ -448,6 +449,9 @@ public: virtual void emitScriptEvent(const QVariant& message) {} + QUuid getLastEditedBy() const { return _lastEditedBy; } + void setLastEditedBy(QUuid value) { _lastEditedBy = value; } + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -463,6 +467,7 @@ protected: // and physics changes quint64 _lastUpdated; // last time this entity called update(), this includes animations and non-physics changes quint64 _lastEdited; // last official local or remote edit time + QUuid _lastEditedBy; // id of last editor quint64 _lastBroadcast; // the last time we sent an edit packet about this entity quint64 _lastEditedFromRemote; // last time we received and edit from the server diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a0142dec8f..e20bd81ab3 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -227,6 +227,7 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; + CHECK_PROPERTY_CHANGE(PROP_LAST_EDITED_BY, lastEditedBy); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); @@ -368,6 +369,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable } + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions); if (!skipDefaults) { @@ -611,6 +613,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool setType(typeScriptValue.toVariant().toString()); } + COPY_PROPERTY_FROM_QSCRIPTVALUE(lastEditedBy, QUuid, setLastEditedBy); COPY_PROPERTY_FROM_QSCRIPTVALUE(position, glmVec3, setPosition); COPY_PROPERTY_FROM_QSCRIPTVALUE(dimensions, glmVec3, setDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(rotation, glmQuat, setRotation); @@ -750,6 +753,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } void EntityItemProperties::merge(const EntityItemProperties& other) { + COPY_PROPERTY_IF_CHANGED(lastEditedBy); COPY_PROPERTY_IF_CHANGED(position); COPY_PROPERTY_IF_CHANGED(dimensions); COPY_PROPERTY_IF_CHANGED(rotation); @@ -1667,6 +1671,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt } void EntityItemProperties::markAllChanged() { + _lastEditedByChanged = true; _simulationOwnerChanged = true; _positionChanged = true; _dimensionsChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index d5cce92f87..5a812ffe37 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -83,6 +83,7 @@ public: quint64 getLastEdited() const { return _lastEdited; } float getEditedAgo() const /// Elapsed seconds since this entity was last edited { return (float)(usecTimestampNow() - getLastEdited()) / (float)USECS_PER_SECOND; } + EntityPropertyFlags getChangedProperties() const; bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret? @@ -218,6 +219,8 @@ public: DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI); + DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); + static QString getBackgroundModeString(BackgroundMode mode); @@ -455,6 +458,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, LastEditedBy, lastEditedBy, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 3ab827a222..ca7ac669f3 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -75,4 +75,6 @@ const QString ENTITY_ITEM_DEFAULT_NAME = QString(""); const uint16_t ENTITY_ITEM_DEFAULT_DPI = 30; +const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid(); + #endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index e09db1e867..127f3d0eea 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -181,6 +181,8 @@ enum EntityPropertyList { PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts + PROP_LAST_EDITED_BY, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 306477b10c..ee4db74c64 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1154,17 +1154,76 @@ bool EntityScriptingInterface::setAbsoluteJointRotationInObjectFrame(const QUuid return false; } +glm::vec3 EntityScriptingInterface::getLocalJointTranslation(const QUuid& entityID, int jointIndex) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto modelEntity = std::dynamic_pointer_cast(entity); + return modelEntity->getLocalJointTranslation(jointIndex); + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::getLocalJointRotation(const QUuid& entityID, int jointIndex) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto modelEntity = std::dynamic_pointer_cast(entity); + return modelEntity->getLocalJointRotation(jointIndex); + } else { + return glm::quat(); + } +} + +bool EntityScriptingInterface::setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + bool result = modelEntity->setLocalJointTranslation(jointIndex, translation); + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + properties = entity->getProperties(); + entity->setLastBroadcast(now); + }); + + properties.setJointTranslationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} + +bool EntityScriptingInterface::setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::quat rotation) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + bool result = modelEntity->setLocalJointRotation(jointIndex, rotation); + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + properties = entity->getProperties(); + entity->setLastBroadcast(now); + }); + + properties.setJointRotationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} -bool EntityScriptingInterface::setAbsoluteJointRotationsInObjectFrame(const QUuid& entityID, - const QVector& rotations) { + +bool EntityScriptingInterface::setLocalJointRotations(const QUuid& entityID, const QVector& rotations) { if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { auto now = usecTimestampNow(); auto modelEntity = std::dynamic_pointer_cast(entity); bool result = false; for (int index = 0; index < rotations.size(); index++) { - result |= modelEntity->setAbsoluteJointRotationInObjectFrame(index, rotations[index]); + result |= modelEntity->setLocalJointRotation(index, rotations[index]); } if (result) { EntityItemProperties properties; @@ -1184,15 +1243,14 @@ bool EntityScriptingInterface::setAbsoluteJointRotationsInObjectFrame(const QUui } -bool EntityScriptingInterface::setAbsoluteJointTranslationsInObjectFrame(const QUuid& entityID, - const QVector& translations) { +bool EntityScriptingInterface::setLocalJointTranslations(const QUuid& entityID, const QVector& translations) { if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { auto now = usecTimestampNow(); auto modelEntity = std::dynamic_pointer_cast(entity); bool result = false; for (int index = 0; index < translations.size(); index++) { - result |= modelEntity->setAbsoluteJointTranslationInObjectFrame(index, translations[index]); + result |= modelEntity->setLocalJointTranslation(index, translations[index]); } if (result) { EntityItemProperties properties; @@ -1211,12 +1269,12 @@ bool EntityScriptingInterface::setAbsoluteJointTranslationsInObjectFrame(const Q return false; } -bool EntityScriptingInterface::setAbsoluteJointsDataInObjectFrame(const QUuid& entityID, - const QVector& rotations, - const QVector& translations) { +bool EntityScriptingInterface::setLocalJointsData(const QUuid& entityID, + const QVector& rotations, + const QVector& translations) { // for a model with 80 joints, sending both these in one edit packet causes the packet to be too large. - return setAbsoluteJointRotationsInObjectFrame(entityID, rotations) || - setAbsoluteJointTranslationsInObjectFrame(entityID, translations); + return setLocalJointRotations(entityID, rotations) || + setLocalJointTranslations(entityID, translations); } int EntityScriptingInterface::getJointIndex(const QUuid& entityID, const QString& name) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 3a24ff59fd..3d46113611 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -186,13 +186,17 @@ public slots: Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation); - Q_INVOKABLE bool setAbsoluteJointRotationsInObjectFrame(const QUuid& entityID, - const QVector& rotations); - Q_INVOKABLE bool setAbsoluteJointTranslationsInObjectFrame(const QUuid& entityID, - const QVector& translations); - Q_INVOKABLE bool setAbsoluteJointsDataInObjectFrame(const QUuid& entityID, - const QVector& rotations, - const QVector& translations); + + Q_INVOKABLE glm::vec3 getLocalJointTranslation(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::quat getLocalJointRotation(const QUuid& entityID, int jointIndex); + Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation); + Q_INVOKABLE bool setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::quat rotation); + + Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector& rotations); + Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, + const QVector& rotations, + const QVector& translations); Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 7de841a321..a03e4b8f08 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -63,6 +63,11 @@ EntityTree::~EntityTree() { eraseAllOctreeElements(false); } +void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { + _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(','); +} + + void EntityTree::createRootElement() { _rootElement = createNewElement(); } @@ -925,6 +930,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c quint64 startCreate = 0, endCreate = 0; quint64 startLogging = 0, endLogging = 0; + const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec + bool suppressDisallowedScript = false; + _totalEditMessages++; EntityItemID entityItemID; @@ -935,7 +943,31 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c entityItemID, properties); endDecode = usecTimestampNow(); - const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec + if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) { + bool passedWhiteList = false; + auto entityScript = properties.getScript(); + for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) { + if (entityScript.startsWith(whiteListedPrefix, Qt::CaseInsensitive)) { + passedWhiteList = true; + break; + } + } + if (!passedWhiteList) { + if (wantEditLogging()) { + qCDebug(entities) << "User [" << senderNode->getUUID() << "] attempting to set entity script not on whitelist, edit rejected"; + } + + // If this was an add, we also want to tell the client that sent this edit that the entity was not added. + if (message.getType() == PacketType::EntityAdd) { + QWriteLocker locker(&_recentlyDeletedEntitiesLock); + _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID); + validEditPacket = passedWhiteList; + } else { + suppressDisallowedScript = true; + } + } + } + if ((message.getType() == PacketType::EntityAdd || (message.getType() == PacketType::EntityEdit && properties.lifetimeChanged())) && !senderNode->getCanRez() && senderNode->getCanRezTmp()) { @@ -960,6 +992,12 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID); endLookup = usecTimestampNow(); if (existingEntity && message.getType() == PacketType::EntityEdit) { + + if (suppressDisallowedScript) { + properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP); + properties.setScript(existingEntity->getScript()); + } + // if the EntityItem exists, then update it startLogging = usecTimestampNow(); if (wantEditLogging()) { @@ -975,6 +1013,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c endLogging = usecTimestampNow(); startUpdate = usecTimestampNow(); + properties.setLastEditedBy(senderNode->getUUID()); updateEntity(entityItemID, properties, senderNode); existingEntity->markAsChangedOnServer(); endUpdate = usecTimestampNow(); @@ -983,6 +1022,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { // this is a new entity... assign a new entityID properties.setCreated(properties.getLastEdited()); + properties.setLastEditedBy(senderNode->getUUID()); startCreate = usecTimestampNow(); EntityItemPointer newEntity = addEntity(entityItemID, properties); endCreate = usecTimestampNow(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 68c8618482..441b686e3b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -64,6 +64,7 @@ public: void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; } + void setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist); /// Implements our type specific root element factory virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override; @@ -342,6 +343,8 @@ protected: QHash> _childrenOfAvatars; // which entities are children of which avatars float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; + + QStringList _entityScriptSourceWhitelist; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index b098247524..df568817ac 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -389,13 +389,13 @@ bool ModelEntityItem::shouldBePhysical() const { } void ModelEntityItem::resizeJointArrays(int newSize) { - if (newSize >= 0 && newSize > _absoluteJointRotationsInObjectFrame.size()) { - _absoluteJointRotationsInObjectFrame.resize(newSize); - _absoluteJointRotationsInObjectFrameSet.resize(newSize); - _absoluteJointRotationsInObjectFrameDirty.resize(newSize); - _absoluteJointTranslationsInObjectFrame.resize(newSize); - _absoluteJointTranslationsInObjectFrameSet.resize(newSize); - _absoluteJointTranslationsInObjectFrameDirty.resize(newSize); + if (newSize >= 0 && newSize > _localJointRotations.size()) { + _localJointRotations.resize(newSize); + _localJointRotationsSet.resize(newSize); + _localJointRotationsDirty.resize(newSize); + _localJointTranslations.resize(newSize); + _localJointTranslationsSet.resize(newSize); + _localJointTranslationsDirty.resize(newSize); } } @@ -404,9 +404,9 @@ void ModelEntityItem::setJointRotations(const QVector& rotations) { _jointRotationsExplicitlySet = rotations.size() > 0; resizeJointArrays(rotations.size()); for (int index = 0; index < rotations.size(); index++) { - if (_absoluteJointRotationsInObjectFrameSet[index]) { - _absoluteJointRotationsInObjectFrame[index] = rotations[index]; - _absoluteJointRotationsInObjectFrameDirty[index] = true; + if (_localJointRotationsSet[index]) { + _localJointRotations[index] = rotations[index]; + _localJointRotationsDirty[index] = true; } } }); @@ -417,7 +417,7 @@ void ModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { _jointRotationsExplicitlySet = rotationsSet.size() > 0; resizeJointArrays(rotationsSet.size()); for (int index = 0; index < rotationsSet.size(); index++) { - _absoluteJointRotationsInObjectFrameSet[index] = rotationsSet[index]; + _localJointRotationsSet[index] = rotationsSet[index]; } }); } @@ -427,9 +427,9 @@ void ModelEntityItem::setJointTranslations(const QVector& translation _jointTranslationsExplicitlySet = translations.size() > 0; resizeJointArrays(translations.size()); for (int index = 0; index < translations.size(); index++) { - if (_absoluteJointTranslationsInObjectFrameSet[index]) { - _absoluteJointTranslationsInObjectFrame[index] = translations[index]; - _absoluteJointTranslationsInObjectFrameSet[index] = true; + if (_localJointTranslationsSet[index]) { + _localJointTranslations[index] = translations[index]; + _localJointTranslationsSet[index] = true; } } }); @@ -440,7 +440,7 @@ void ModelEntityItem::setJointTranslationsSet(const QVector& translationsS _jointTranslationsExplicitlySet = translationsSet.size() > 0; resizeJointArrays(translationsSet.size()); for (int index = 0; index < translationsSet.size(); index++) { - _absoluteJointTranslationsInObjectFrameSet[index] = translationsSet[index]; + _localJointTranslationsSet[index] = translationsSet[index]; } }); } @@ -449,7 +449,7 @@ QVector ModelEntityItem::getJointRotations() const { QVector result; _jointDataLock.withReadLock([&] { if (_jointRotationsExplicitlySet) { - result = _absoluteJointRotationsInObjectFrame; + result = _localJointRotations; } }); return result; @@ -459,7 +459,7 @@ QVector ModelEntityItem::getJointRotationsSet() const { QVector result; _jointDataLock.withReadLock([&] { if (_jointRotationsExplicitlySet) { - result = _absoluteJointRotationsInObjectFrameSet; + result = _localJointRotationsSet; } }); @@ -470,7 +470,7 @@ QVector ModelEntityItem::getJointTranslations() const { QVector result; _jointDataLock.withReadLock([&] { if (_jointTranslationsExplicitlySet) { - result = _absoluteJointTranslationsInObjectFrame; + result = _localJointTranslations; } }); return result; @@ -480,7 +480,7 @@ QVector ModelEntityItem::getJointTranslationsSet() const { QVector result; _jointDataLock.withReadLock([&] { if (_jointTranslationsExplicitlySet) { - result = _absoluteJointTranslationsInObjectFrameSet; + result = _localJointTranslationsSet; } }); return result; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 6f2a0e1b31..58766906bb 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -117,9 +117,6 @@ public: virtual bool shouldBePhysical() const override; - virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } - virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } - virtual void setJointRotations(const QVector& rotations); virtual void setJointRotationsSet(const QVector& rotationsSet); virtual void setJointTranslations(const QVector& translations); @@ -143,14 +140,14 @@ protected: ReadWriteLockable _jointDataLock; bool _jointRotationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations - QVector _absoluteJointRotationsInObjectFrame; - QVector _absoluteJointRotationsInObjectFrameSet; // ever set? - QVector _absoluteJointRotationsInObjectFrameDirty; // needs a relay to model/rig? - + QVector _localJointRotations; + QVector _localJointRotationsSet; // ever set? + QVector _localJointRotationsDirty; // needs a relay to model/rig? + bool _jointTranslationsExplicitlySet { false }; // were the joints set as a property or just side effect of animations - QVector _absoluteJointTranslationsInObjectFrame; - QVector _absoluteJointTranslationsInObjectFrameSet; // ever set? - QVector _absoluteJointTranslationsInObjectFrameDirty; // needs a relay to model/rig? + QVector _localJointTranslations; + QVector _localJointTranslationsSet; // ever set? + QVector _localJointTranslationsDirty; // needs a relay to model/rig? int _lastKnownCurrentFrame; virtual void resizeJointArrays(int newSize = -1); diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp index 6473b6bf2b..f24a9bb932 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ b/libraries/gl/src/gl/OpenGLVersionChecker.cpp @@ -21,6 +21,7 @@ #include "GLHelpers.h" +// Minimum gl version required is 4.1 #define MINIMUM_GL_VERSION 0x0401 OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : @@ -49,7 +50,8 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { valid = true; override = false; - QGLWidget* glWidget = new QGLWidget(); + QGLWidget* glWidget = new QGLWidget(getDefaultGLFormat()); + valid = glWidget->isValid(); // Inform user if no OpenGL support if (!valid) { @@ -75,15 +77,26 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { // - major_number.minor_number // - major_number.minor_number.release_number // Reference: https://www.opengl.org/sdk/docs/man/docbook4/xhtml/glGetString.xml - const QString version { "version" }; - QString glVersion = glData[version].toString(); - QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); - int majorNumber = versionParts[0].toInt(); - int minorNumber = versionParts[1].toInt(); - int minimumMajorNumber = (MINIMUM_GL_VERSION >> 16); + + int minimumMajorNumber = (MINIMUM_GL_VERSION >> 8) & 0xFF; int minimumMinorNumber = (MINIMUM_GL_VERSION & 0xFF); - valid = (majorNumber > minimumMajorNumber - || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); + int majorNumber = 0; + int minorNumber = 0; + const QString version { "version" }; + if (glData.contains(version)) { + QString glVersion = glData[version].toString(); + QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); + if (versionParts.size() >= 2) { + majorNumber = versionParts[0].toInt(); + minorNumber = versionParts[1].toInt(); + valid = (majorNumber > minimumMajorNumber + || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); + } else { + valid = false; + } + } else { + valid = false; + } // Prompt user if below minimum if (!valid) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 3513d7a05b..719989a07a 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -307,11 +307,20 @@ void GLBackend::render(const Batch& batch) { renderPassTransfer(batch); } +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + if (_stereo._enable) { + glEnable(GL_CLIP_DISTANCE0); + } +#endif { PROFILE_RANGE(_stereo._enable ? "Render Stereo" : "Render"); renderPassDraw(batch); } - +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + if (_stereo._enable) { + glDisable(GL_CLIP_DISTANCE0); + } +#endif // Restore the saved stereo state for the next batch _stereo._enable = savedStereo; } @@ -326,13 +335,24 @@ void GLBackend::syncCache() { glEnable(GL_LINE_SMOOTH); } +#ifdef GPU_STEREO_DRAWCALL_DOUBLED void GLBackend::setupStereoSide(int side) { ivec4 vp = _transform._viewport; vp.z /= 2; glViewport(vp.x + side * vp.z, vp.y, vp.z, vp.w); + +#ifdef GPU_STEREO_CAMERA_BUFFER +#ifdef GPU_STEREO_DRAWCALL_DOUBLED + glVertexAttribI1i(14, side); +#endif +#else _transform.bindCurrentCamera(side); +#endif + } +#else +#endif void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) { resetStages(); @@ -385,8 +405,11 @@ void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) { // term strategy is to get rid of any GL calls in favor of the HIFI GPU API // As long as we don;t use several versions of shaders we can avoid this more complex code path -// #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo()); +#ifdef GPU_STEREO_CAMERA_BUFFER +#define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, (GLShader::Version) isStereo()) +#else #define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc +#endif void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 417b090d31..42557ddd97 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -29,6 +29,29 @@ #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 +//#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE +//#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER +#define GPU_STEREO_TECHNIQUE_INSTANCED + + +// Let these be configured by the one define picked above +#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE +#define GPU_STEREO_DRAWCALL_DOUBLED +#endif + +#ifdef GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER +#define GPU_STEREO_DRAWCALL_DOUBLED +#define GPU_STEREO_CAMERA_BUFFER +#endif + +#ifdef GPU_STEREO_TECHNIQUE_INSTANCED +#define GPU_STEREO_DRAWCALL_INSTANCED +#define GPU_STEREO_CAMERA_BUFFER +#endif + namespace gpu { namespace gl { class GLBackend : public Backend, public std::enable_shared_from_this { @@ -204,7 +227,10 @@ protected: void renderPassTransfer(const Batch& batch); void renderPassDraw(const Batch& batch); + +#ifdef GPU_STEREO_DRAWCALL_DOUBLED void setupStereoSide(int side); +#endif virtual void initInput() final; virtual void killInput() final; @@ -262,7 +288,19 @@ protected: }; struct TransformStageState { +#ifdef GPU_STEREO_CAMERA_BUFFER + struct Cameras { + TransformCamera _cams[2]; + + 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)); }; + }; + + using CameraBufferElement = Cameras; +#else using CameraBufferElement = TransformCamera; +#endif using TransformCameras = std::vector; TransformCamera _camera; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp index 9256a42b80..618988a217 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp @@ -187,7 +187,11 @@ void GLBackend::updateInput() { glVertexAttribFormat(slot + locNum, count, type, isNormalized, offset + locNum * perLocationSize); glVertexAttribBinding(slot + locNum, attrib._channel); } +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glVertexBindingDivisor(attrib._channel, attrib._frequency * (isStereo() ? 2 : 1)); +#else glVertexBindingDivisor(attrib._channel, attrib._frequency); +#endif } (void)CHECK_GL_ERROR(); } @@ -306,7 +310,11 @@ void GLBackend::updateInput() { for (size_t locNum = 0; locNum < locationCount; ++locNum) { glVertexAttribPointer(slot + (GLuint)locNum, count, type, isNormalized, stride, reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereo() ? 2 : 1)); +#else glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); +#endif } // TODO: Support properly the IAttrib version diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index ed0b9607e6..81d38c3339 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -48,7 +48,13 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // check the program cache // pick the program version + // check the program cache + // pick the program version +#ifdef GPU_STEREO_CAMERA_BUFFER + GLuint glprogram = pipelineObject->_program->getProgram((GLShader::Version) isStereo()); +#else GLuint glprogram = pipelineObject->_program->getProgram(); +#endif if (_pipeline._program != glprogram) { _pipeline._program = glprogram; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp index 7f821078cd..eb53f5d45b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp @@ -31,10 +31,25 @@ void GLBackend::do_setProjectionTransform(const Batch& batch, size_t paramOffset void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) { memcpy(&_transform._viewport, batch.readData(batch._params[paramOffset]._uint), sizeof(Vec4i)); +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + { + ivec4& vp = _transform._viewport; + glViewport(vp.x, vp.y, vp.z, vp.w); + + // Where we assign the GL viewport + if (_stereo._enable) { + vp.z /= 2; + if (_stereo._pass) { + vp.x += vp.z; + } + } + } +#else if (!_inRenderTransferPass && !isStereo()) { ivec4& vp = _transform._viewport; glViewport(vp.x, vp.y, vp.z, vp.w); } +#endif // The Viewport is tagged invalid because the CameraTransformUBO is not up to date and will need update on next drawcall _transform._invalidViewport = true; @@ -100,13 +115,21 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_invalidView || _invalidProj || _invalidViewport) { size_t offset = _cameraUboSize * _cameras.size(); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); - if (stereo._enable) { - _cameras.push_back((_camera.getEyeCamera(0, stereo, _view))); - _cameras.push_back((_camera.getEyeCamera(1, stereo, _view))); - } else { - _cameras.push_back((_camera.recomputeDerived(_view))); - } + if (stereo._enable) { +#ifdef GPU_STEREO_CAMERA_BUFFER + _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view), _camera.getEyeCamera(1, stereo, _view))); +#else + _cameras.push_back((_camera.getEyeCamera(0, stereo, _view))); + _cameras.push_back((_camera.getEyeCamera(1, stereo, _view))); +#endif + } else { +#ifdef GPU_STEREO_CAMERA_BUFFER + _cameras.push_back(CameraBufferElement(_camera.recomputeDerived(_view))); +#else + _cameras.push_back((_camera.recomputeDerived(_view))); +#endif + } } // Flags are clean @@ -122,9 +145,13 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta } if (offset != INVALID_OFFSET) { +#ifdef GPU_STEREO_CAMERA_BUFFER + bindCurrentCamera(0); +#else if (!stereo._enable) { bindCurrentCamera(0); } +#endif } (void)CHECK_GL_ERROR(); } @@ -148,7 +175,11 @@ void GLBackend::updateTransform(const Batch& batch) { glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); glVertexAttribIPointer(gpu::Stream::DRAW_CALL_INFO, 2, GL_UNSIGNED_SHORT, 0, _transform._drawCallInfoOffsets[batch._currentNamedCall]); +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, (isStereo() ? 2 : 1)); +#else glVertexAttribDivisor(gpu::Stream::DRAW_CALL_INFO, 1); +#endif } (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp index f8d493c25b..23439a640a 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp @@ -54,9 +54,24 @@ static const std::array DOMAIN_DEFINES { { "#define GPU_GEOMETRY_SHADER", } }; +// Stereo specific defines +static const std::string stereoVersion { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN" +#endif +#ifdef GPU_STEREO_DRAWCALL_DOUBLED +#ifdef GPU_STEREO_CAMERA_BUFFER + "#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED" +#else + "#define GPU_TRANSFORM_IS_STEREO" +#endif +#endif +}; + // Versions specific of the shader static const std::array VERSION_DEFINES { { - "" + "", + stereoVersion } }; GLShader* compileBackendShader(GLBackend& backend, const Shader& shader) { @@ -181,7 +196,8 @@ bool GLShader::makeProgram(GLBackend& backend, Shader& shader, const Shader::Bin // Define the public slots only from the default version if (version == 0) { shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs); - } else { + } // else + { GLShader::UniformMapping mapping; for (auto srcUniform : shader.getUniforms()) { mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.h b/libraries/gpu-gl/src/gpu/gl/GLShader.h index e75e96cf16..40dd0b7be9 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShader.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.h @@ -19,6 +19,8 @@ public: enum Version { Mono = 0, + Stereo, + NumVersions }; @@ -40,8 +42,8 @@ public: GLint getUniformLocation(GLint srcLoc, Version version = Mono) { // THIS will be used in the future PR as we grow the number of versions - // return _uniformMappings[version][srcLoc]; - return srcLoc; + return _uniformMappings[version][srcLoc]; + // return srcLoc; } const std::weak_ptr _backend; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp index 6c2b2f434e..a87d0ad6b8 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp @@ -25,11 +25,14 @@ void GL41Backend::do_draw(const Batch& batch, size_t paramOffset) { uint32 startVertex = batch._params[paramOffset + 0]._uint; if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, 2); +#else setupStereoSide(0); glDrawArrays(mode, startVertex, numVertices); setupStereoSide(1); glDrawArrays(mode, startVertex, numVertices); - +#endif _stats._DSNumTriangles += 2 * numVertices / 3; _stats._DSNumDrawcalls += 2; @@ -55,11 +58,14 @@ void GL41Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) { GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawElementsInstanced(mode, numIndices, glType, indexBufferByteOffset, 2); +#else setupStereoSide(0); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); setupStereoSide(1); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - +#endif _stats._DSNumTriangles += 2 * numIndices / 3; _stats._DSNumDrawcalls += 2; } else { @@ -83,11 +89,14 @@ void GL41Backend::do_drawInstanced(const Batch& batch, size_t paramOffset) { if (isStereo()) { GLint trueNumInstances = 2 * numInstances; +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, trueNumInstances); +#else setupStereoSide(0); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); setupStereoSide(1); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - + glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); +#endif _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; _stats._DSNumDrawcalls += trueNumInstances; } else { @@ -124,10 +133,14 @@ void GL41Backend::do_drawIndexedInstanced(const Batch& batch, size_t paramOffset if (isStereo()) { GLint trueNumInstances = 2 * numInstances; +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, trueNumInstances, 0, startInstance); +#else setupStereoSide(0); glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); setupStereoSide(1); glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); +#endif _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; _stats._DSNumDrawcalls += trueNumInstances; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp index bae6326e8f..d7dde8b7d6 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.cpp @@ -25,10 +25,14 @@ void GL45Backend::do_draw(const Batch& batch, size_t paramOffset) { uint32 startVertex = batch._params[paramOffset + 0]._uint; if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, 2); +#else setupStereoSide(0); glDrawArrays(mode, startVertex, numVertices); setupStereoSide(1); glDrawArrays(mode, startVertex, numVertices); +#endif _stats._DSNumTriangles += 2 * numVertices / 3; _stats._DSNumDrawcalls += 2; @@ -55,11 +59,14 @@ void GL45Backend::do_drawIndexed(const Batch& batch, size_t paramOffset) { GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); if (isStereo()) { +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawElementsInstanced(mode, numIndices, glType, indexBufferByteOffset, 2); +#else setupStereoSide(0); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); setupStereoSide(1); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - +#endif _stats._DSNumTriangles += 2 * numIndices / 3; _stats._DSNumDrawcalls += 2; } else { @@ -83,10 +90,14 @@ void GL45Backend::do_drawInstanced(const Batch& batch, size_t paramOffset) { if (isStereo()) { GLint trueNumInstances = 2 * numInstances; +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawArraysInstanced(mode, startVertex, numVertices, trueNumInstances); +#else setupStereoSide(0); glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); setupStereoSide(1); glDrawArraysInstanced(mode, startVertex, numVertices, numInstances); +#endif _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; _stats._DSNumDrawcalls += trueNumInstances; @@ -112,10 +123,15 @@ void GL45Backend::do_drawIndexedInstanced(const Batch& batch, size_t paramOffset if (isStereo()) { GLint trueNumInstances = 2 * numInstances; + +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, trueNumInstances, 0, startInstance); +#else setupStereoSide(0); glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); setupStereoSide(1); glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); +#endif _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; _stats._DSNumDrawcalls += trueNumInstances; } else { diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 438b1e9454..291888e81b 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -14,6 +14,8 @@ #include "../gl/GLBackend.h" #include "../gl/GLTexture.h" +#define INCREMENTAL_TRANSFER 0 + namespace gpu { namespace gl45 { using namespace gpu::gl; @@ -56,6 +58,7 @@ public: GLint pageDimensionsIndex { 0 }; }; +#if INCREMENTAL_TRANSFER struct TransferState { TransferState(GL45Texture& texture); uvec3 currentPageSize() const; @@ -74,6 +77,10 @@ public: uvec3 mipOffset; const uint8_t* srcPointer { nullptr }; }; + protected: + TransferState _transferState; +#endif + protected: void updateMips() override; void stripToMip(uint16_t newMinMip); @@ -91,7 +98,6 @@ public: void derez(); SparseInfo _sparseInfo; - TransferState _transferState; uint32_t _allocatedPages { 0 }; uint32_t _lastMipAllocatedPages { 0 }; uint16_t _mipOffset { 0 }; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index f97364f5c5..0516bc6be0 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -162,6 +162,8 @@ void GL45Backend::initTextureManagementStage() { } } +#if INCREMENTAL_TRANSFER + using TransferState = GL45Backend::GL45Texture::TransferState; TransferState::TransferState(GL45Texture& texture) : texture(texture) { @@ -246,6 +248,7 @@ void TransferState::populatePage(std::vector& buffer) { uvec3 TransferState::currentPageSize() const { return glm::clamp(mipDimensions - mipOffset, uvec3(1), texture._sparseInfo.pageDimensions); } +#endif GLuint GL45Texture::allocate(const Texture& texture) { GLuint result; @@ -258,11 +261,19 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) { } GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, GLuint externalId) - : GLTexture(backend, texture, externalId), _sparseInfo(*this), _transferState(*this) { + : GLTexture(backend, texture, externalId), _sparseInfo(*this) +#if INCREMENTAL_TRANSFER +, _transferState(*this) +#endif +{ } GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& texture, bool transferrable) - : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) { + : GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this) +#if INCREMENTAL_TRANSFER +, _transferState(*this) +#endif + { auto theBackend = _backend.lock(); if (_transferrable && theBackend && theBackend->isTextureManagementSparseEnabled()) { @@ -274,10 +285,10 @@ GL45Texture::GL45Texture(const std::weak_ptr& backend, const Texture& } GL45Texture::~GL45Texture() { - // External textures cycle very quickly, so don't spam the log with messages about them. - if (!_gpuObject.getUsage().isExternal()) { - qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); - } + // // External textures cycle very quickly, so don't spam the log with messages about them. + // if (!_gpuObject.getUsage().isExternal()) { + // qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str(); + // } // Remove this texture from the candidate list of derezzable textures if (_transferrable) { @@ -375,39 +386,40 @@ void GL45Texture::updateSize() const { void GL45Texture::startTransfer() { Parent::startTransfer(); _sparseInfo.update(); +#if INCREMENTAL_TRANSFER _transferState.updateMip(); +#endif } bool GL45Texture::continueTransfer() { - if (!Texture::getEnableIncrementalTextureTransfers()) { - size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; - for (uint8_t face = 0; face < maxFace; ++face) { - for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { - auto size = _gpuObject.evalMipDimensions(mipLevel); - if (_sparseInfo.sparse && mipLevel <= _sparseInfo.maxSparseLevel) { - glTexturePageCommitmentEXT(_id, mipLevel, 0, 0, face, size.x, size.y, 1, GL_TRUE); - _allocatedPages += _sparseInfo.getPageCount(size); - } - if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { - auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); - if (GL_TEXTURE_2D == _target) { - glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else if (GL_TEXTURE_CUBE_MAP == _target) { - // DSA ARB does not work on AMD, so use EXT - // glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); - auto target = CUBE_FACE_LAYOUT[face]; - glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - } else { - Q_ASSERT(false); - } - (void)CHECK_GL_ERROR(); +#if !INCREMENTAL_TRANSFER + size_t maxFace = GL_TEXTURE_CUBE_MAP == _target ? CUBE_NUM_FACES : 1; + for (uint8_t face = 0; face < maxFace; ++face) { + for (uint16_t mipLevel = _minMip; mipLevel <= _maxMip; ++mipLevel) { + auto size = _gpuObject.evalMipDimensions(mipLevel); + if (_sparseInfo.sparse && mipLevel <= _sparseInfo.maxSparseLevel) { + glTexturePageCommitmentEXT(_id, mipLevel, 0, 0, face, size.x, size.y, 1, GL_TRUE); + _allocatedPages += _sparseInfo.getPageCount(size); + } + if (_gpuObject.isStoredMipFaceAvailable(mipLevel, face)) { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + if (GL_TEXTURE_2D == _target) { + glTextureSubImage2D(_id, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else if (GL_TEXTURE_CUBE_MAP == _target) { + // DSA ARB does not work on AMD, so use EXT + // glTextureSubImage3D(_id, mipLevel, 0, 0, face, size.x, size.y, 1, texelFormat.format, texelFormat.type, mip->readData()); + auto target = CUBE_FACE_LAYOUT[face]; + glTextureSubImage2DEXT(_id, target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + } else { + Q_ASSERT(false); } + (void)CHECK_GL_ERROR(); } } - return false; } - + return false; +#else static std::vector buffer; if (buffer.empty()) { buffer.resize(DEFAULT_PAGE_BUFFER_SIZE); @@ -458,6 +470,7 @@ bool GL45Texture::continueTransfer() { _lastMipAllocatedPages = _allocatedPages; } return result; +#endif } void GL45Texture::finishTransfer() { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 33786155db..45aff54b8f 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -35,18 +35,15 @@ std::atomic Texture::_allowedCPUMemoryUsage { 0 }; #define MIN_CORES_FOR_INCREMENTAL_TEXTURES 5 -bool recommendedIncrementalTransfers = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES); -bool recommendedSparseTextures = recommendedIncrementalTransfers; +bool recommendedSparseTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_INCREMENTAL_TEXTURES); -std::atomic Texture::_enableSparseTextures { recommendedIncrementalTransfers }; -std::atomic Texture::_enableIncrementalTextureTransfers { recommendedSparseTextures }; +std::atomic Texture::_enableSparseTextures { recommendedSparseTextures }; struct ReportTextureState { ReportTextureState() { qDebug() << "[TEXTURE TRANSFER SUPPORT]" << "\n\tidealThreadCount:" << QThread::idealThreadCount() - << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures - << "\n\tRECOMMENDED enableIncrementalTextures:" << recommendedIncrementalTransfers; + << "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures; } } report; @@ -59,16 +56,6 @@ void Texture::setEnableSparseTextures(bool enabled) { #endif } -void Texture::setEnableIncrementalTextureTransfers(bool enabled) { -#ifdef Q_OS_WIN - qDebug() << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Incremental Texture Transfer:" << enabled; - _enableIncrementalTextureTransfers = enabled; -#else - qDebug() << "[TEXTURE TRANSFER SUPPORT] Incremental Texture Transfer not supported on this platform."; -#endif -} - - void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { if (prevObjectSize == newObjectSize) { return; @@ -84,10 +71,6 @@ bool Texture::getEnableSparseTextures() { return _enableSparseTextures.load(); } -bool Texture::getEnableIncrementalTextureTransfers() { - return _enableIncrementalTextureTransfers.load(); -} - uint32_t Texture::getTextureCPUCount() { return _textureCPUCount.load(); } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 2a93ec3066..856bd4983d 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -143,10 +143,8 @@ class Texture : public Resource { static std::atomic _textureCPUCount; static std::atomic _textureCPUMemoryUsage; static std::atomic _allowedCPUMemoryUsage; - static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); - static std::atomic _enableSparseTextures; - static std::atomic _enableIncrementalTextureTransfers; + static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: static uint32_t getTextureCPUCount(); @@ -162,10 +160,7 @@ public: static void setAllowedGPUMemoryUsage(Size size); static bool getEnableSparseTextures(); - static bool getEnableIncrementalTextureTransfers(); - static void setEnableSparseTextures(bool enabled); - static void setEnableIncrementalTextureTransfers(bool enabled); using ExternalRecycler = std::function; using ExternalIdAndFence = std::pair; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 12a1cd10f1..db7808e781 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -22,29 +22,84 @@ struct TransformCamera { }; layout(std140) uniform transformCameraBuffer { +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_CAMERA + TransformCamera _camera[2]; +#else TransformCamera _camera; +#endif +#else + TransformCamera _camera; +#endif }; +#ifdef GPU_VERTEX_SHADER +#ifdef GPU_TRANSFORM_IS_STEREO + +#ifdef GPU_TRANSFORM_STEREO_CAMERA +#ifdef GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED +layout(location=14) in int _inStereoSide; +#endif + +flat out int _stereoSide; +#endif + +#endif + +#endif + +#ifdef GPU_PIXEL_SHADER +#ifdef GPU_TRANSFORM_STEREO_CAMERA +flat in int _stereoSide; +#endif +#endif + TransformCamera getTransformCamera() { +#ifdef GPU_TRANSFORM_IS_STEREO + #ifdef GPU_TRANSFORM_STEREO_CAMERA + #ifdef GPU_VERTEX_SHADER + #ifdef GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED + _stereoSide = _inStereoSide; + #endif + #ifdef GPU_TRANSFORM_STEREO_CAMERA_INSTANCED + _stereoSide = gl_InstanceID % 2; + #endif + #endif + return _camera[_stereoSide]; + #else + return _camera; + #endif +#else return _camera; +#endif } vec3 getEyeWorldPos() { - return _camera._viewInverse[3].xyz; + return getTransformCamera()._viewInverse[3].xyz; } - bool cam_isStereo() { +#ifdef GPU_TRANSFORM_IS_STEREO + return getTransformCamera()._stereoInfo.x > 0.0; +#else return _camera._stereoInfo.x > 0.0; +#endif } float cam_getStereoSide() { +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_CAMERA + return getTransformCamera()._stereoInfo.y; +#else return _camera._stereoInfo.y; +#endif +#else + return _camera._stereoInfo.y; +#endif } <@endfunc@> - <@func declareStandardObjectTransform()@> struct TransformObject { mat4 _model; @@ -92,6 +147,25 @@ TransformObject getTransformObject() { <$viewport$> = <$cameraTransform$>._viewport; <@endfunc@> +<@func transformStereoClipsSpace(cameraTransform, clipPos)@> + { +#ifdef GPU_TRANSFORM_IS_STEREO + +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN + vec4 eyeClipEdge[2]= vec4[2](vec4(-1,0,0,1), vec4(1,0,0,1)); + vec2 eyeOffsetScale = vec2(-0.5, +0.5); + uint eyeIndex = _stereoSide; + gl_ClipDistance[0] = dot(<$clipPos$>, eyeClipEdge[eyeIndex]); + float newClipPosX = <$clipPos$>.x * 0.5 + eyeOffsetScale[eyeIndex] * <$clipPos$>.w; + <$clipPos$>.x = newClipPosX; +#endif + +#else +#endif + } +<@endfunc@> + + <@func transformModelToEyeWorldAlignedPos(cameraTransform, objectTransform, modelPos, eyeWorldAlignedPos)@> { // _transformModelToEyeWorldAlignedPos @@ -108,6 +182,8 @@ TransformObject getTransformObject() { <$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$> <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; + + <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> } <@endfunc@> @@ -117,6 +193,8 @@ TransformObject getTransformObject() { <$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$> <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); + + <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> } <@endfunc@> @@ -135,11 +213,10 @@ TransformObject getTransformObject() { <@endfunc@> <@func transformModelToWorldDir(cameraTransform, objectTransform, modelDir, worldDir)@> - { // transformModelToEyeDir + { // transformModelToEyeDir vec3 mr0 = <$objectTransform$>._modelInverse[0].xyz; vec3 mr1 = <$objectTransform$>._modelInverse[1].xyz; vec3 mr2 = <$objectTransform$>._modelInverse[2].xyz; - <$worldDir$> = vec3(dot(mr0, <$modelDir$>), dot(mr1, <$modelDir$>), dot(mr2, <$modelDir$>)); } <@endfunc@> @@ -173,6 +250,8 @@ TransformObject getTransformObject() { <@func transformEyeToClipPos(cameraTransform, eyePos, clipPos)@> { // transformEyeToClipPos <$clipPos$> = <$cameraTransform$>._projection * vec4(<$eyePos$>.xyz, 1.0); + + <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> } <@endfunc@> diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index c1f764f5fa..39371cc3e3 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -18,7 +18,7 @@ #include #include -const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; +const char* KeyboardMouseDevice::NAME = "Keyboard/Mouse"; bool KeyboardMouseDevice::_enableTouch = true; void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 8177c9bcc0..399ca4e93d 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -52,21 +52,21 @@ public: MOUSE_AXIS_WHEEL_X_POS, MOUSE_AXIS_WHEEL_X_NEG, }; - + enum TouchAxisChannel { TOUCH_AXIS_X_POS = MOUSE_AXIS_WHEEL_X_NEG + 1, TOUCH_AXIS_X_NEG, TOUCH_AXIS_Y_POS, TOUCH_AXIS_Y_NEG, }; - + enum TouchButtonChannel { TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1, }; // Plugin functions bool isSupported() const override { return true; } - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return false; } @@ -88,8 +88,8 @@ public: void wheelEvent(QWheelEvent* event); static void enableTouch(bool enableTouch) { _enableTouch = enableTouch; } - - static const QString NAME; + + static const char* NAME; protected: diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index 64f02b5df3..20952df4d7 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -21,7 +21,7 @@ #include #include -const QString TouchscreenDevice::NAME = "Touchscreen"; +const char* TouchscreenDevice::NAME = "Touchscreen"; bool TouchscreenDevice::isSupported() const { for (auto touchDevice : QTouchDevice::devices()) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h index 7bfaa23be8..65e771e8f0 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h @@ -22,7 +22,7 @@ class QGestureEvent; class TouchscreenDevice : public InputPlugin { Q_OBJECT public: - + enum TouchAxisChannel { TOUCH_AXIS_X_POS = 0, TOUCH_AXIS_X_NEG, @@ -37,7 +37,7 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } + virtual const QString getName() const override { return NAME; } bool isHandController() const override { return false; } @@ -48,8 +48,8 @@ public: void touchEndEvent(const QTouchEvent* event); void touchUpdateEvent(const QTouchEvent* event); void touchGestureEvent(const QGestureEvent* event); - - static const QString NAME; + + static const char* NAME; protected: diff --git a/libraries/model/src/model/skybox.slv b/libraries/model/src/model/skybox.slv index 5df1aa0a4a..6fd9532fa1 100755 --- a/libraries/model/src/model/skybox.slv +++ b/libraries/model/src/model/skybox.slv @@ -36,4 +36,6 @@ void main(void) { // Position is supposed to come in clip space gl_Position = vec4(inPosition.xy, 0.0, 1.0); + + <$transformStereoClipsSpace(cam, gl_Position)$> } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index eecc1515f5..5d7340b2ce 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -272,27 +272,17 @@ void DomainHandler::setIsConnected(bool isConnected) { } void DomainHandler::requestDomainSettings() { - // TODO: the nodes basically lock if they don't get a response - add a timeout to this so that they at least restart - // if they can't get settings - - NodeType_t owningNodeType = DependencyManager::get()->getOwnerType(); - if (owningNodeType == NodeType::Agent) { - // for now the agent nodes don't need any domain settings - _settingsObject = QJsonObject(); - emit settingsReceived(_settingsObject); - } else { - qCDebug(networking) << "Requesting settings from domain server"; - - Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); - - auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); - packet->writePrimitive(assignmentType); - - auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(packet), _sockAddr); - - _settingsTimer.start(); - } + qCDebug(networking) << "Requesting settings from domain server"; + + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); + + auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); + packet->writePrimitive(assignmentType); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), _sockAddr); + + _settingsTimer.start(); } void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 82bac4cc3d..7a778edaad 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -815,3 +815,31 @@ void NodeList::kickNodeBySessionID(const QUuid& nodeID) { } } + +void NodeList::muteNodeBySessionID(const QUuid& nodeID) { + // cannot mute yourself, or nobody + if (!nodeID.isNull() && _sessionUUID != nodeID ) { + if (getThisNodeCanKick()) { + auto audioMixer = soloNodeOfType(NodeType::AudioMixer); + if (audioMixer) { + // setup the packet + auto mutePacket = NLPacket::create(PacketType::NodeMuteRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + mutePacket->write(nodeID.toRfc4122()); + + qDebug() << "Sending packet to mute node" << uuidStringWithoutCurlyBraces(nodeID); + + sendPacket(std::move(mutePacket), *audioMixer); + } else { + qWarning() << "Couldn't find audio mixer to send node mute request"; + } + } else { + qWarning() << "You do not have permissions to mute in this domain." + << "Request to mute node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent"; + } + } else { + qWarning() << "NodeList::muteNodeBySessionID called with an invalid ID or an ID which matches the current session ID."; + + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 41a4a51515..4c06a13469 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -74,6 +74,7 @@ public: bool isIgnoringNode(const QUuid& nodeID) const; void kickNodeBySessionID(const QUuid& nodeID); + void muteNodeBySessionID(const QUuid& nodeID); public slots: void reset(); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index eba84dddd4..48b21a1e98 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -88,7 +88,24 @@ class ScriptableResource : public QObject { Q_PROPERTY(QUrl url READ getUrl) Q_PROPERTY(int state READ getState NOTIFY stateChanged) + /**jsdoc + * @constructor Resource + * @property url {string} url of this resource + * @property state {Resource.State} current loading state + */ + public: + + /**jsdoc + * @name Resource.State + * @static + * @property QUEUED {int} The resource is queued up, waiting to be loaded. + * @property LOADING {int} The resource is downloading + * @property LOADED {int} The resource has finished downloaded by is not complete + * @property FINISHED {int} The resource has completly finished loading and is ready. + * @property FAILED {int} Downloading the resource has failed. + */ + enum State { QUEUED, LOADING, @@ -101,6 +118,10 @@ public: ScriptableResource(const QUrl& url); virtual ~ScriptableResource() = default; + /**jsdoc + * Release this resource + * @function Resource#release + */ Q_INVOKABLE void release(); const QUrl& getUrl() const { return _url; } @@ -111,7 +132,22 @@ public: void setInScript(bool isInScript); signals: + + /**jsdoc + * Signaled when download progress for this resource has changed + * @function Resource#progressChanged + * @param bytesReceived {int} bytes downloaded so far + * @param bytesTotal {int} total number of bytes in the resource + * @returns {Signal} + */ void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); + + /**jsdoc + * Signaled when resource loading state has changed + * @function Resource#stateChanged + * @param bytesReceived {Resource.State} new state + * @returns {Signal} + */ void stateChanged(int state); protected: @@ -148,14 +184,49 @@ class ResourceCache : public QObject { Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty) Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) - + + /**jsdoc + * @namespace ResourceCache + * @property numTotal {number} total number of total resources + * @property numCached {number} total number of cached resource + * @property sizeTotal {number} size in bytes of all resources + * @property sizeCached {number} size in bytes of all cached resources + */ + public: + /**jsdoc + * Returns the total number of resources + * @function ResourceCache.getNumTotalResources + * @return {number} + */ size_t getNumTotalResources() const { return _numTotalResources; } + + /**jsdoc + * Returns the total size in bytes of all resources + * @function ResourceCache.getSizeTotalResources + * @return {number} + */ size_t getSizeTotalResources() const { return _totalResourcesSize; } + /**jsdoc + * Returns the total number of cached resources + * @function ResourceCache.getNumCachedResources + * @return {number} + */ size_t getNumCachedResources() const { return _numUnusedResources; } + + /**jsdoc + * Returns the total size in bytes of cached resources + * @function ResourceCache.getSizeCachedResources + * @return {number} + */ size_t getSizeCachedResources() const { return _unusedResourcesSize; } + /**jsdoc + * Returns list of all resource urls + * @function ResourceCache.getResourceList + * @return {string[]} + */ Q_INVOKABLE QVariantList getResourceList(); static void setRequestLimit(int limit); @@ -192,6 +263,13 @@ protected slots: /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param extra extra data to pass to the creator, if appropriate + /**jsdoc + * Asynchronously loads a resource from the spedified URL and returns it. + * @param url {string} url of resource to load + * @param fallback {string} fallback URL if load of the desired url fails + * @function ResourceCache.getResource + * @return {Resource} + */ QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), void* extra = NULL); @@ -203,6 +281,12 @@ protected: // Pointers created through this method should be owned by the caller, // which should be a QScriptEngine with ScriptableResource registered, so that // the QScriptEngine will delete the pointer when it is garbage collected. + /**jsdoc + * Prefetches a resource. + * @param url {string} url of resource to load + * @function ResourceCache.prefetch + * @return {Resource} + */ Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } /// Creates a new resource. diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6b86b7bc6e..88285602e1 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -26,7 +26,8 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack << PacketType::DomainListRequest << PacketType::StopNode - << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest; + << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest + << PacketType::NodeMuteRequest; const QSet NON_SOURCED_PACKETS = QSet() << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment @@ -47,7 +48,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_ARROW_ACTION; + return VERSION_ENTITIES_LAST_EDITED_BY; case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 0eca24176c..502ecc3951 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -99,7 +99,8 @@ public: SelectedAudioFormat, MoreEntityShapes, NodeKickRequest, - LAST_PACKET_TYPE = NodeKickRequest + NodeMuteRequest, + LAST_PACKET_TYPE = NodeMuteRequest }; }; @@ -188,6 +189,7 @@ const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61; const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; +const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65; enum class AssetServerPacketVersion: PacketVersion { VegasCongestionControl = 19 diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 98cc62bdee..d2583dce72 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -165,6 +165,12 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif // hand this packetList off to writeReliablePacketList // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacketList + if (packetList->getNumPackets() == 0) { + qCWarning(networking) << "Trying to send packet list with 0 packets, bailing."; + return 0; + } + + if (QThread::currentThread() != thread()) { auto ptr = packetList.release(); QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::AutoConnection, diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 3a9107390a..c87669c99a 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -188,6 +188,8 @@ public: virtual float renderRate() const { return -1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } + // Rate at which old frames are presented to the device display + virtual float stutterRate() const { return -1.0f; } // Rate at which new frames are being presented to the display device virtual float newFramePresentRate() const { return -1.0f; } // Rate at which rendered frames are being skipped diff --git a/libraries/plugins/src/plugins/Plugin.cpp b/libraries/plugins/src/plugins/Plugin.cpp index 7c30f252c9..58c71aa97c 100644 --- a/libraries/plugins/src/plugins/Plugin.cpp +++ b/libraries/plugins/src/plugins/Plugin.cpp @@ -7,7 +7,7 @@ // #include "Plugin.h" -QString Plugin::UNKNOWN_PLUGIN_ID("unknown"); +const char* Plugin::UNKNOWN_PLUGIN_ID { "unknown" }; void Plugin::setContainer(PluginContainer* container) { _container = container; diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index 0452c7fbfe..15588fafa4 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -18,7 +18,7 @@ class Plugin : public QObject { Q_OBJECT public: /// \return human-readable name - virtual const QString& getName() const = 0; + virtual const QString getName() const = 0; typedef enum { STANDARD, ADVANCED, DEVELOPER } grouping; @@ -26,10 +26,10 @@ public: virtual grouping getGrouping() const { return STANDARD; } /// \return string ID (not necessarily human-readable) - virtual const QString& getID() const { assert(false); return UNKNOWN_PLUGIN_ID; } + virtual const QString getID() const { assert(false); return UNKNOWN_PLUGIN_ID; } virtual bool isSupported() const; - + void setContainer(PluginContainer* container); /// Called when plugin is initially loaded, typically at application start @@ -74,6 +74,6 @@ signals: protected: bool _active { false }; PluginContainer* _container { nullptr }; - static QString UNKNOWN_PLUGIN_ID; + static const char* UNKNOWN_PLUGIN_ID; }; diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 80ba9cd1b2..bb0d5dbfab 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -121,7 +121,7 @@ AnimDebugDraw::AnimDebugDraw() : // HACK: add red, green and blue axis at (1,1,1) _animDebugDrawData->_vertexBuffer->resize(sizeof(AnimDebugDrawData::Vertex) * 6); - + static std::vector vertices({ AnimDebugDrawData::Vertex { glm::vec3(1.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) }, AnimDebugDrawData::Vertex { glm::vec3(2.0, 1.0f, 1.0f), toRGBA(255, 0, 0, 255) }, @@ -162,9 +162,10 @@ static const uint32_t blue = toRGBA(0, 0, 255, 255); const int NUM_CIRCLE_SLICES = 24; -static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius, AnimDebugDrawData::Vertex*& v) { +static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius, glm::vec4& vecColor, AnimDebugDrawData::Vertex*& v) { const float XYZ_AXIS_LENGTH = radius * 4.0f; + const uint32_t color = toRGBA(vecColor); AnimPose finalPose = rootPose * pose; glm::vec3 base = rootPose * pose.trans; @@ -192,10 +193,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // x-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = xRing[i]; - v->rgba = red; + v->rgba = color; v++; v->pos = xRing[i + 1]; - v->rgba = red; + v->rgba = color; v++; } @@ -210,10 +211,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // y-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = yRing[i]; - v->rgba = green; + v->rgba = color; v++; v->pos = yRing[i + 1]; - v->rgba = green; + v->rgba = color; v++; } @@ -228,10 +229,10 @@ static void addBone(const AnimPose& rootPose, const AnimPose& pose, float radius // z-ring for (int i = 0; i < NUM_CIRCLE_SLICES; i++) { v->pos = zRing[i]; - v->rgba = blue; + v->rgba = color; v++; v->pos = zRing[i + 1]; - v->rgba = blue; + v->rgba = color; v++; } } @@ -367,7 +368,7 @@ void AnimDebugDraw::update() { const float radius = BONE_RADIUS / (absPoses[i].scale.x * rootPose.scale.x); // draw bone - addBone(rootPose, absPoses[i], radius, v); + addBone(rootPose, absPoses[i], radius, color, v); // draw link to parent auto parentIndex = skeleton->getParentIndex(i); @@ -382,20 +383,18 @@ void AnimDebugDraw::update() { for (auto& iter : markerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); - glm::vec4 color = std::get<2>(iter.second); // TODO: currently ignored. - Q_UNUSED(color); + glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, v); + addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } AnimPose myAvatarPose(glm::vec3(1), DebugDraw::getInstance().getMyAvatarRot(), DebugDraw::getInstance().getMyAvatarPos()); for (auto& iter : myAvatarMarkerMap) { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); - glm::vec4 color = std::get<2>(iter.second); // TODO: currently ignored. - Q_UNUSED(color); + glm::vec4 color = std::get<2>(iter.second); const float radius = POSE_RADIUS; - addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, v); + addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } // draw rays from shared DebugDraw singleton diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index 0525509f0b..36e281ab5b 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -24,7 +24,7 @@ out vec4 _texCoord0; void main(void) { if (sphereParam.w != 0.0) { - + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); @@ -33,9 +33,14 @@ void main(void) { vec4 projected = gl_Position / gl_Position.w; projected.xy = (projected.xy + 1.0) * 0.5; +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN +#else if (cam_isStereo()) { projected.x = 0.5 * (projected.x + cam_getStereoSide()); } +#endif +#endif _texCoord0 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane @@ -47,11 +52,27 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; + +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN + TransformCamera cam = getTransformCamera(); + <$transformStereoClipsSpace(cam, pos)$> +#endif +#endif + _texCoord0 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN +#else if (cam_isStereo()) { _texCoord0.x = 0.5 * (_texCoord0.x + cam_getStereoSide()); } +#endif +#endif + gl_Position = pos; + } + } diff --git a/libraries/render-utils/src/deferred_light_spot.slv b/libraries/render-utils/src/deferred_light_spot.slv index 4a1a0472aa..935d756b28 100755 --- a/libraries/render-utils/src/deferred_light_spot.slv +++ b/libraries/render-utils/src/deferred_light_spot.slv @@ -46,9 +46,14 @@ void main(void) { vec4 projected = gl_Position / gl_Position.w; projected.xy = (projected.xy + 1.0) * 0.5; +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN +#else if (cam_isStereo()) { projected.x = 0.5 * (projected.x + cam_getStereoSide()); } +#endif +#endif _texCoord0 = vec4(projected.xy, 0.0, 1.0) * gl_Position.w; } else { const float depth = -1.0; //Draw at near plane @@ -60,10 +65,24 @@ void main(void) { ); vec4 pos = UNIT_QUAD[gl_VertexID]; +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN + TransformCamera cam = getTransformCamera(); + <$transformStereoClipsSpace(cam, pos)$> +#endif +#endif + _texCoord0 = vec4((pos.xy + 1.0) * 0.5, 0.0, 1.0); + +#ifdef GPU_TRANSFORM_IS_STEREO +#ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN +#else if (cam_isStereo()) { _texCoord0.x = 0.5 * (_texCoord0.x + cam_getStereoSide()); } +#endif +#endif + gl_Position = pos; } } diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 82d220ab34..d8bc319256 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -19,13 +19,60 @@ #include +/**jsdoc + * @namespace Assets + */ class AssetScriptingInterface : public QObject { Q_OBJECT public: AssetScriptingInterface(QScriptEngine* engine); + /**jsdoc + * Upload content to the connected domain's asset server. + * @function Assets.uploadData + * @static + * @param data {string} content to upload + * @param callback {Assets~uploadDataCallback} called when upload is complete + */ + + /**jsdoc + * Called when uploadData is complete + * @callback Assets~uploadDataCallback + * @param {string} url + * @param {string} hash + */ + Q_INVOKABLE void uploadData(QString data, QScriptValue callback); + + /**jsdoc + * Download data from the connected domain's asset server. + * @function Assets.downloadData + * @static + * @param url {string} url of asset to download, must be atp scheme url. + * @param callback {Assets~downloadDataCallback} + */ + + /**jsdoc + * Called when downloadData is complete + * @callback Assets~downloadDataCallback + * @param data {string} content that was downloaded + */ + Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete); + + /**jsdoc + * Sets up a path to hash mapping within the connected domain's asset server + * @function Assets.setMapping + * @static + * @param path {string} + * @param hash {string} + * @param callback {Assets~setMappingCallback} + */ + + /**jsdoc + * Called when setMapping is complete + * @callback Assets~setMappingCallback + */ Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 776c7cfec6..070bc98dbc 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1172,7 +1172,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac // Guard against meaningless query and fragment parts. // Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation()) const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; - for (QString file : includeFiles) { + for (QString includeFile : includeFiles) { + QString file = ResourceManager::normalizeURL(includeFile); QUrl thisURL; bool isStandardLibrary = false; if (file.startsWith("/~/")) { @@ -1187,20 +1188,15 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac thisURL = resolvePath(file); } - if (!_includedURLs.contains(thisURL)) { - if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && - (currentSandboxURL.scheme() != "file" || - !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) { - qCWarning(scriptengine) << "Script.include() ignoring file path" - << thisURL << "outside of original entity script" << currentSandboxURL; - } else { - // We could also check here for CORS, but we don't yet. - // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. - urls.append(thisURL); - _includedURLs << thisURL; - } + if (!isStandardLibrary && !currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && + (currentSandboxURL.scheme() != "file" || + !thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity()))) { + qCWarning(scriptengine) << "Script.include() ignoring file path" + << thisURL << "outside of original entity script" << currentSandboxURL; } else { - qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL; + // We could also check here for CORS, but we don't yet. + // It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here. + urls.append(thisURL); } } @@ -1220,13 +1216,20 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac if (contents.isNull()) { qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__; } else { - // Set the parent url so that path resolution will be relative - // to this script's url during its initial evaluation - _parentURL = url.toString(); - auto operation = [&]() { - evaluate(contents, url.toString()); - }; - doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); + std::lock_guard lock(_lock); + if (!_includedURLs.contains(url)) { + _includedURLs << url; + // Set the parent url so that path resolution will be relative + // to this script's url during its initial evaluation + _parentURL = url.toString(); + auto operation = [&]() { + evaluate(contents, url.toString()); + }; + + doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation); + } else { + qCDebug(scriptengine) << "Script.include() skipping evaluation of previously included url:" << url; + } } } _parentURL = parentURL; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 01088660ff..2b2cb3c81a 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -245,6 +245,7 @@ protected: std::function _emitScriptUpdates{ [](){ return true; } }; + std::recursive_mutex _lock; }; #endif // hifi_ScriptEngine_h diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index 69ad8e04ad..702368c2b3 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -29,6 +29,11 @@ void UsersScriptingInterface::kick(const QUuid& nodeID) { DependencyManager::get()->kickNodeBySessionID(nodeID); } +void UsersScriptingInterface::mute(const QUuid& nodeID) { + // ask the NodeList to mute the user with the given session ID + DependencyManager::get()->muteNodeBySessionID(nodeID); +} + bool UsersScriptingInterface::getCanKick() { // ask the NodeList to return our ability to kick return DependencyManager::get()->getThisNodeCanKick(); diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 712eeedeb6..3c98d0a393 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -28,6 +28,7 @@ public: public slots: void ignore(const QUuid& nodeID); void kick(const QUuid& nodeID); + void mute(const QUuid& nodeID); bool getCanKick(); diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 252079f182..a1be6db448 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -226,5 +226,5 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing); } - return NULL; + return nullptr; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 5605cc0031..f58e2c906c 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -144,6 +144,11 @@ public: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) {return false; } + virtual glm::quat getLocalJointRotation(int index) const {return glm::quat(); } + virtual glm::vec3 getLocalJointTranslation(int index) const {return glm::vec3(); } + virtual bool setLocalJointRotation(int index, const glm::quat& rotation) { return false; } + virtual bool setLocalJointTranslation(int index, const glm::vec3& translation) { return false; } + SpatiallyNestablePointer getThisPointer() const; void markAncestorMissing(bool value) { _missingAncestor = value; } diff --git a/libraries/shared/src/shared/Factory.h b/libraries/shared/src/shared/Factory.h index 6f1da6644b..00b7787fdb 100644 --- a/libraries/shared/src/shared/Factory.h +++ b/libraries/shared/src/shared/Factory.h @@ -23,12 +23,12 @@ namespace hifi { using Builder = std::function; using BuilderMap = std::map; - void registerBuilder(const Key& name, Builder builder) { + void registerBuilder(const Key name, Builder builder) { // FIXME don't allow name collisions _builders[name] = builder; } - Pointer create(const Key& name) const { + Pointer create(const Key name) const { const auto& entryIt = _builders.find(name); if (entryIt != _builders.end()) { return (*entryIt).second(); @@ -39,7 +39,7 @@ namespace hifi { template class Registrar { public: - Registrar(const Key& name, SimpleFactory& factory) { + Registrar(const Key name, SimpleFactory& factory) { factory.registerBuilder(name, [] { return std::make_shared(); }); } }; diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index aee8b40832..ba24adfc3f 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -428,6 +428,25 @@ bool Menu::menuExists(const QString& menuName) { return false; } +bool Menu::isMenuEnabled(const QString& menuName) { + QAction* action = getMenuAction(menuName); + + // only proceed if the menu actually exists + if (action) { + return action->isEnabled(); + } + return false; +} + +void Menu::setMenuEnabled(const QString& menuName, bool isEnabled) { + QAction* action = getMenuAction(menuName); + + // only proceed if the menu actually exists + if (action) { + action->setEnabled(isEnabled); + } +} + void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) { MenuWrapper* menuObj = getMenu(menuName); if (menuObj) { diff --git a/libraries/ui/src/ui/Menu.h b/libraries/ui/src/ui/Menu.h index ee60a031c3..2711fc5921 100644 --- a/libraries/ui/src/ui/Menu.h +++ b/libraries/ui/src/ui/Menu.h @@ -106,6 +106,9 @@ public slots: bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); + bool isMenuEnabled(const QString& menuName); + void setMenuEnabled(const QString& menuName, bool isEnabled); + bool getGroupingIsVisible(const QString& grouping); void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible diff --git a/plugins/hifiCodec/src/HiFiCodec.cpp b/plugins/hifiCodec/src/HiFiCodec.cpp index 4e9336ff90..77c369dcae 100644 --- a/plugins/hifiCodec/src/HiFiCodec.cpp +++ b/plugins/hifiCodec/src/HiFiCodec.cpp @@ -18,7 +18,7 @@ #include "HiFiCodec.h" -const QString HiFiCodec::NAME = "hifiAC"; +const char* HiFiCodec::NAME { "hifiAC" }; void HiFiCodec::init() { } diff --git a/plugins/hifiCodec/src/HiFiCodec.h b/plugins/hifiCodec/src/HiFiCodec.h index eeba8d56d8..09b247ff7e 100644 --- a/plugins/hifiCodec/src/HiFiCodec.h +++ b/plugins/hifiCodec/src/HiFiCodec.h @@ -16,11 +16,11 @@ class HiFiCodec : public CodecPlugin { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -36,7 +36,7 @@ public: virtual void releaseDecoder(Decoder* decoder) override; private: - static const QString NAME; + static const char* NAME; }; #endif // hifi_HiFiCodec_h diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index e41472a8c5..aec8bf072a 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -27,8 +27,8 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") #include -const QString NeuronPlugin::NAME = "Neuron"; -const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; +const char* NeuronPlugin::NAME = "Neuron"; +const char* NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; // indices of joints of the Neuron standard skeleton. // This is 'almost' the same as the High Fidelity standard skeleton. diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h index 576deb64ae..36dcf93c23 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.h +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -29,8 +29,8 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } - const QString& getID() const override { return NEURON_ID_STRING; } + virtual const QString getName() const override { return NAME; } + const QString getID() const override { return NEURON_ID_STRING; } virtual bool activate() override; virtual void deactivate() override; @@ -65,8 +65,8 @@ protected: std::shared_ptr _inputDevice { std::make_shared() }; - static const QString NAME; - static const QString NEURON_ID_STRING; + static const char* NAME; + static const char* NEURON_ID_STRING; std::string _serverAddress; int _serverPort; diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index a10e02d325..7ea17739e3 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -29,7 +29,7 @@ class Joystick : public QObject, public controller::InputDevice { public: using Pointer = std::shared_ptr; - const QString& getName() const { return _name; } + const QString getName() const { return _name; } SDL_GameController* getGameController() { return _sdlGameController; } diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index b6fa567aee..5ab5758412 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -41,7 +41,7 @@ static_assert( "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h"); -const QString SDL2Manager::NAME = "SDL2"; +const char* SDL2Manager::NAME = "SDL2"; SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index fc1654bce1..4501d0792b 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -20,11 +20,11 @@ class SDL2Manager : public InputPlugin { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } QStringList getSubdeviceNames() override; bool isHandController() const override { return false; } @@ -39,14 +39,14 @@ public: void pluginFocusOutEvent() override; void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; - + signals: void joystickAdded(Joystick* joystick); void joystickRemoved(Joystick* joystick); - + private: SDL_JoystickID getInstanceId(SDL_GameController* controller); - + int axisInvalid() const { return SDL_CONTROLLER_AXIS_INVALID; } int axisLeftX() const { return SDL_CONTROLLER_AXIS_LEFTX; } int axisLeftY() const { return SDL_CONTROLLER_AXIS_LEFTY; } @@ -55,7 +55,7 @@ private: int axisTriggerLeft() const { return SDL_CONTROLLER_AXIS_TRIGGERLEFT; } int axisTriggerRight() const { return SDL_CONTROLLER_AXIS_TRIGGERRIGHT; } int axisMax() const { return SDL_CONTROLLER_AXIS_MAX; } - + int buttonInvalid() const { return SDL_CONTROLLER_BUTTON_INVALID; } int buttonFaceBottom() const { return SDL_CONTROLLER_BUTTON_A; } int buttonFaceRight() const { return SDL_CONTROLLER_BUTTON_B; } @@ -73,13 +73,13 @@ private: int buttonDpadLeft() const { return SDL_CONTROLLER_BUTTON_DPAD_LEFT; } int buttonDpadRight() const { return SDL_CONTROLLER_BUTTON_DPAD_RIGHT; } int buttonMax() const { return SDL_CONTROLLER_BUTTON_MAX; } - + int buttonPressed() const { return SDL_PRESSED; } int buttonRelease() const { return SDL_RELEASED; } QMap _openJoysticks; bool _isInitialized { false } ; - static const QString NAME; + static const char* NAME; QStringList _subdeviceNames; }; diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index baf13f1fae..7d443bd50d 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -55,15 +55,15 @@ bool SixenseManager::_sixenseLoaded = false; -const QString SixenseManager::NAME = "Sixense"; -const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra"; +const char* SixenseManager::NAME { "Sixense" }; +const char* SixenseManager::HYDRA_ID_STRING { "Razer Hydra" }; -const QString MENU_PARENT = "Developer"; -const QString MENU_NAME = "Sixense"; -const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; -const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; -const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data"; -const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data"; +const char* MENU_PARENT { "Developer" }; +const char* MENU_NAME { "Sixense" }; +const char* MENU_PATH { "Developer" ">" "Sixense" }; +const char* TOGGLE_SMOOTH { "Smooth Sixense Movement" }; +const char* SHOW_DEBUG_RAW { "Debug Draw Raw Data" }; +const char* SHOW_DEBUG_CALIBRATED { "Debug Draw Calibrated Data" }; bool SixenseManager::isSupported() const { #if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX) diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index c77569474e..5237dba791 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -28,8 +28,8 @@ class SixenseManager : public InputPlugin { public: // Plugin functions virtual bool isSupported() const override; - virtual const QString& getName() const override { return NAME; } - virtual const QString& getID() const override { return HYDRA_ID_STRING; } + virtual const QString getName() const override { return NAME; } + virtual const QString getID() const override { return HYDRA_ID_STRING; } // Sixense always seems to initialize even if the hydras are not present. Is there // a way we can properly detect whether the hydras are present? @@ -92,8 +92,8 @@ private: std::shared_ptr _inputDevice { std::make_shared() }; - static const QString NAME; - static const QString HYDRA_ID_STRING; + static const char* NAME; + static const char* HYDRA_ID_STRING; static bool _sixenseLoaded; }; diff --git a/plugins/hifiSpacemouse/src/SpacemouseManager.h b/plugins/hifiSpacemouse/src/SpacemouseManager.h index a9933902e5..361a1fad2d 100644 --- a/plugins/hifiSpacemouse/src/SpacemouseManager.h +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.h @@ -76,8 +76,8 @@ class SpacemouseManager : public InputPlugin, public QAbstractNativeEventFilter Q_OBJECT public: bool isSupported() const override; - const QString& getName() const override { return NAME; } - const QString& getID() const override { return NAME; } + const QString getName() const override { return NAME; } + const QString getID() const override { return NAME; } bool activate() override; void deactivate() override; @@ -127,7 +127,7 @@ private: // use to calculate distance traveled since last event DWORD fLast3dmouseInputTime; - static const QString NAME; + static const char* NAME; friend class SpacemouseDevice; }; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index f0edc5a465..f56d594919 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -25,11 +25,11 @@ Q_DECLARE_LOGGING_CATEGORY(oculus) -static const QString MENU_PARENT = "Avatar"; -static const QString MENU_NAME = "Oculus Touch Controllers"; -static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +static const char* MENU_PARENT = "Avatar"; +static const char* MENU_NAME = "Oculus Touch Controllers"; +static const char* MENU_PATH = "Avatar" ">" "Oculus Touch Controllers"; -const QString OculusControllerManager::NAME = "Oculus"; +const char* OculusControllerManager::NAME = "Oculus"; bool OculusControllerManager::isSupported() const { return oculusAvailable(); diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 1ca9e0f47e..98e0e3d650 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -24,7 +24,7 @@ class OculusControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } QStringList getSubdeviceNames() override; @@ -95,7 +95,7 @@ private: ovrInputState _inputState {}; RemoteDevice::Pointer _remote; TouchDevice::Pointer _touch; - static const QString NAME; + static const char* NAME; }; #endif // hifi__OculusControllerManager diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp index f1d22f3ceb..429d3ecd16 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.cpp @@ -8,7 +8,7 @@ #include "OculusDebugDisplayPlugin.h" #include -const QString OculusDebugDisplayPlugin::NAME("Oculus Rift (Simulator)"); +const char* OculusDebugDisplayPlugin::NAME { "Oculus Rift (Simulator)" }; static const QString DEBUG_FLAG("HIFI_DEBUG_OCULUS"); static bool enableDebugOculus = true || QProcessEnvironment::systemEnvironment().contains("HIFI_DEBUG_OCULUS"); diff --git a/plugins/oculus/src/OculusDebugDisplayPlugin.h b/plugins/oculus/src/OculusDebugDisplayPlugin.h index 983511ba01..ec05cd92e2 100644 --- a/plugins/oculus/src/OculusDebugDisplayPlugin.h +++ b/plugins/oculus/src/OculusDebugDisplayPlugin.h @@ -11,7 +11,7 @@ class OculusDebugDisplayPlugin : public OculusBaseDisplayPlugin { public: - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } grouping getGrouping() const override { return DEVELOPER; } bool isSupported() const override; @@ -20,6 +20,6 @@ protected: bool isHmdMounted() const override { return true; } private: - static const QString NAME; + static const char* NAME; }; diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index 415965e948..d7d734f6a2 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -19,7 +19,7 @@ #include "OculusHelpers.h" -const QString OculusDisplayPlugin::NAME("Oculus Rift"); +const char* OculusDisplayPlugin::NAME { "Oculus Rift" }; static ovrPerfHudMode currentDebugMode = ovrPerfHud_Off; bool OculusDisplayPlugin::internalActivate() { @@ -146,6 +146,16 @@ void OculusDisplayPlugin::hmdPresent() { if (!OVR_SUCCESS(result)) { logWarning("Failed to present"); } + + static int droppedFrames = 0; + ovrPerfStats perfStats; + ovr_GetPerfStats(_session, &perfStats); + for (int i = 0; i < perfStats.FrameStatsCount; ++i) { + const auto& frameStats = perfStats.FrameStats[i]; + int delta = frameStats.CompositorDroppedFrameCount - droppedFrames; + _stutterRate.increment(delta); + droppedFrames = frameStats.CompositorDroppedFrameCount; + } } _presentRate.increment(); } diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 0c7d57c4f4..fce8e9e6ce 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -13,7 +13,7 @@ class OculusDisplayPlugin : public OculusBaseDisplayPlugin { using Parent = OculusBaseDisplayPlugin; public: ~OculusDisplayPlugin(); - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -29,7 +29,7 @@ protected: void cycleDebugOutput() override; private: - static const QString NAME; + static const char* NAME; ovrTextureSwapChain _textureSwapChain; gpu::FramebufferPointer _outputFramebuffer; bool _customized { false }; diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index b7b6ae5768..09f3e6dc8c 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -30,7 +30,7 @@ #include "OculusHelpers.h" -const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); +const char* OculusLegacyDisplayPlugin::NAME { "Oculus Rift" }; OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h index 6ffc1a7f44..20345467df 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.h @@ -21,7 +21,7 @@ class OculusLegacyDisplayPlugin : public HmdDisplayPlugin { public: OculusLegacyDisplayPlugin(); bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -41,9 +41,9 @@ protected: void uncustomizeContext() override; void hmdPresent() override; bool isHmdMounted() const override { return true; } - + private: - static const QString NAME; + static const char* NAME; GLWindow* _hmdWindow{ nullptr }; ovrHmd _hmd; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 1a4067a847..bfa61f3b36 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -33,9 +33,9 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) -const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); -const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here -const QString OpenVrThreadedSubmit = "OpenVR Threaded Submit"; // this probably shouldn't be hardcoded here +const char* OpenVrDisplayPlugin::NAME { "OpenVR (Vive)" }; +const char* StandingHMDSensorMode { "Standing HMD Sensor Mode" }; // this probably shouldn't be hardcoded here +const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here PoseData _nextRenderPoseData; PoseData _nextSimPoseData; @@ -401,9 +401,13 @@ bool OpenVrDisplayPlugin::internalActivate() { memset(&timing, 0, sizeof(timing)); timing.m_nSize = sizeof(vr::Compositor_FrameTiming); vr::VRCompositor()->GetFrameTiming(&timing); - _asyncReprojectionActive = timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync; + auto usingOpenVRForOculus = oculusViaOpenVR(); + _asyncReprojectionActive = (timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync) || usingOpenVRForOculus; _threadedSubmit = !_asyncReprojectionActive; + if (usingOpenVRForOculus) { + qDebug() << "Oculus active via OpenVR: " << usingOpenVRForOculus; + } qDebug() << "OpenVR Async Reprojection active: " << _asyncReprojectionActive; qDebug() << "OpenVR Threaded submit enabled: " << _threadedSubmit; @@ -641,6 +645,12 @@ void OpenVrDisplayPlugin::hmdPresent() { vr::VRCompositor()->PostPresentHandoff(); _presentRate.increment(); } + + vr::Compositor_FrameTiming frameTiming; + memset(&frameTiming, 0, sizeof(vr::Compositor_FrameTiming)); + frameTiming.m_nSize = sizeof(vr::Compositor_FrameTiming); + vr::VRCompositor()->GetFrameTiming(&frameTiming); + _stutterRate.increment(frameTiming.m_nNumDroppedFrames); } void OpenVrDisplayPlugin::postPreview() { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 3403bae27c..a60c21a606 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -36,7 +36,7 @@ class OpenVrDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; @@ -72,7 +72,7 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; std::atomic _keyboardSupressionCount{ 0 }; - static const QString NAME; + static const char* NAME; vr::HmdMatrix34_t _lastGoodHMDPose; mat4 _sensorResetMat; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 2803ca424e..56a5dd3f5b 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -60,6 +60,12 @@ bool isOculusPresent() { return result; } +bool oculusViaOpenVR() { + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + return enableDebugOpenVR && isOculusPresent() && vr::VR_IsHmdPresent(); +} + bool openVrSupported() { static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); @@ -331,7 +337,7 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat #define FAILED_MIN_SPEC_OVERLAY_FRIENDLY_NAME "Minimum specifications for SteamVR not met" #define FAILED_MIN_SPEC_UPDATE_INTERVAL_MS 10 #define FAILED_MIN_SPEC_AUTO_QUIT_INTERVAL_MS (MSECS_PER_SECOND * 30) -#define MIN_CORES_SPEC 5 +#define MIN_CORES_SPEC 3 void showMinSpecWarning() { auto vrSystem = acquireOpenVrSystem(); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index be79dd1155..f00cd9e117 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -15,6 +15,7 @@ #include #include +bool oculusViaOpenVR(); // is the user using Oculus via OpenVR bool openVrSupported(); vr::IVRSystem* acquireOpenVrSystem(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index ff8fc64474..2e930c0fdc 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -37,12 +37,12 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; -static const QString MENU_PARENT = "Avatar"; -static const QString MENU_NAME = "Vive Controllers"; -static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; -static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; +static const char* MENU_PARENT = "Avatar"; +static const char* MENU_NAME = "Vive Controllers"; +static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; +static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; -const QString ViveControllerManager::NAME = "OpenVR"; +const char* ViveControllerManager::NAME { "OpenVR" }; bool ViveControllerManager::isSupported() const { return openVrSupported(); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 5f34d70ba8..3fb166c842 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -33,7 +33,7 @@ class ViveControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } bool isHandController() const override { return true; } @@ -125,7 +125,7 @@ private: vr::IVRSystem* _system { nullptr }; std::shared_ptr _inputDevice { std::make_shared(_system) }; - static const QString NAME; + static const char* NAME; }; #endif // hifi__ViveControllerManager diff --git a/plugins/pcmCodec/src/PCMCodecManager.cpp b/plugins/pcmCodec/src/PCMCodecManager.cpp index 315d0622ab..7278edaf92 100644 --- a/plugins/pcmCodec/src/PCMCodecManager.cpp +++ b/plugins/pcmCodec/src/PCMCodecManager.cpp @@ -15,7 +15,7 @@ #include "PCMCodecManager.h" -const QString PCMCodec::NAME = "pcm"; +const char* PCMCodec::NAME { "pcm" }; void PCMCodec::init() { } @@ -55,7 +55,7 @@ void PCMCodec::releaseDecoder(Decoder* decoder) { // do nothing } -const QString zLibCodec::NAME = "zlib"; +const char* zLibCodec::NAME { "zlib" }; void zLibCodec::init() { } diff --git a/plugins/pcmCodec/src/PCMCodecManager.h b/plugins/pcmCodec/src/PCMCodecManager.h index 55d7c866f1..d58a219fef 100644 --- a/plugins/pcmCodec/src/PCMCodecManager.h +++ b/plugins/pcmCodec/src/PCMCodecManager.h @@ -16,11 +16,11 @@ class PCMCodec : public CodecPlugin, public Encoder, public Decoder { Q_OBJECT - + public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -45,7 +45,7 @@ public: virtual void trackLostFrames(int numFrames) override { } private: - static const QString NAME; + static const char* NAME; }; class zLibCodec : public CodecPlugin, public Encoder, public Decoder { @@ -54,7 +54,7 @@ class zLibCodec : public CodecPlugin, public Encoder, public Decoder { public: // Plugin functions bool isSupported() const override; - const QString& getName() const override { return NAME; } + const QString getName() const override { return NAME; } void init() override; void deinit() override; @@ -80,7 +80,7 @@ public: virtual void trackLostFrames(int numFrames) override { } private: - static const QString NAME; + static const char* NAME; }; #endif // hifi__PCMCodecManager_h diff --git a/scripts/system/assets/images/mute-target.svg b/scripts/system/assets/images/mute-target.svg new file mode 100755 index 0000000000..1ed642c79e --- /dev/null +++ b/scripts/system/assets/images/mute-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index c2deaa2fac..ce9b8e403f 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -203,6 +203,32 @@ function overlayFromWorldPoint(point) { return { x: horizontalPixels, y: verticalPixels }; } +var gamePad = Controller.findDevice("GamePad"); +function activeHudPoint2dGamePad() { + if (!HMD.active) { + return; + } + var headPosition = MyAvatar.getHeadPosition(); + var headDirection = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); + + var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection); + + if (!hudPoint3d) { + if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here + print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. + } + return; + } + var hudPoint2d = overlayFromWorldPoint(hudPoint3d); + + // We don't know yet if we'll want to make the cursor or laser visble, but we need to move it to see if + // it's pointing at a QML tool (aka system overlay). + setReticlePosition(hudPoint2d); + + return hudPoint2d; +} + + function activeHudPoint2d(activeHand) { // if controller is valid, update reticle position and answer 2d point. Otherwise falsey. var controllerPose = getControllerWorldLocation(activeHand, true); // note: this will return head pose if hand pose is invalid (third eye) if (!controllerPose.valid) { @@ -210,7 +236,7 @@ function activeHudPoint2d(activeHand) { // if controller is valid, update reticl } var controllerPosition = controllerPose.position; var controllerDirection = Quat.getUp(controllerPose.rotation); - + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here @@ -405,7 +431,7 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); // The following is essentially like Left and Right versions of // clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); -// except that we first update the reticle position from the appropriate hand position, before invoking the ContextMenu. +// except that we first update the reticle position from the appropriate hand position, before invoking the . var wantsMenu = 0; clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { @@ -420,6 +446,13 @@ clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (cl } wantsMenu = clicked; }); +clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) { + if (clicked) { + activeHudPoint2dGamePad(); + } + + wantsMenu = clicked; +}); clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () { // Allow the reticle depth to be set correctly: // Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move @@ -472,13 +505,16 @@ function update() { expireMouseCursor(); clearSystemLaser(); } + updateSeeking(true); if (!handControllerLockOut.expired(now)) { return off(); // Let them use mouse in peace. } + if (!Menu.isOptionChecked("First Person")) { return off(); // What to do? menus can be behind hand! } + if ((!Window.hasFocus() && !HMD.active) || !Reticle.allowMouseCapture) { // In desktop it's pretty clear when another app is on top. In that case we bail, because // hand controllers might be sputtering "valid" data and that will keep someone from deliberately @@ -487,14 +523,18 @@ function update() { // other apps anyway. So in that case, we DO keep going even though we're not on top. (Fogbugz 1831.) return off(); // Don't mess with other apps or paused mouse activity } + leftTrigger.update(); rightTrigger.update(); if (!activeTrigger.state) { return off(); // No trigger } + if (getGrabCommunications()) { return off(); } + + var hudPoint2d = activeHudPoint2d(activeHand); if (!hudPoint2d) { return off(); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 84ff6b3c89..e1c846806f 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -24,10 +24,16 @@ var desktopMenuItemName = "Desktop"; var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var button; +// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through. +// Disable them in hmd. +var desktopOnlyViews = ['Third Person', 'Mirror', 'Independent Mode', 'Entity Mode']; function onHmdChanged(isHmd) { button.writeProperty('buttonState', isHmd ? 0 : 1); button.writeProperty('defaultState', isHmd ? 0 : 1); button.writeProperty('hoverState', isHmd ? 2 : 3); + desktopOnlyViews.forEach(function (view) { + Menu.setMenuEnabled("View>" + view, !isHmd); + }); } function onClicked(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); diff --git a/scripts/system/mod.js b/scripts/system/mod.js index ea9355e376..7e5cc5d2a5 100644 --- a/scripts/system/mod.js +++ b/scripts/system/mod.js @@ -52,7 +52,9 @@ function removeOverlays() { for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; - Overlays.deleteOverlay(modOverlays[avatarID]); + for (var j = 0; j < modOverlays[avatarID].length; ++j) { + Overlays.deleteOverlay(modOverlays[avatarID][j]); + } } modOverlays = {}; @@ -74,10 +76,14 @@ function buttonClicked(){ button.clicked.connect(buttonClicked); -function overlayURL() { +function kickOverlayURL() { return ASSETS_PATH + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); } +function muteOverlayURL() { + return ASSETS_PATH + "/images/" + "mute-target.svg"; +} + function updateOverlays() { if (isShowingOverlays) { @@ -101,20 +107,28 @@ function updateOverlays() { } // setup a position for the overlay that is just above this avatar's head - var overlayPosition = avatar.getJointPosition("Head"); - overlayPosition.y += 0.45; + var kickOverlayPosition = avatar.getJointPosition("Head"); + kickOverlayPosition.y += 0.45; + var muteOverlayPosition = avatar.getJointPosition("Head"); + muteOverlayPosition.y += 0.70; if (avatarID in modOverlays) { // keep the overlay above the current position of this avatar - Overlays.editOverlay(modOverlays[avatarID], { - position: overlayPosition, - url: overlayURL() + Overlays.editOverlay(modOverlays[avatarID][0], { + position: kickOverlayPosition, + url: kickOverlayURL() }); + if (Users.canKick) { + Overlays.editOverlay(modOverlays[avatarID][1], { + position: muteOverlayPosition, + url: muteOverlayURL() + }); + } } else { // add the overlay above this avatar - var newOverlay = Overlays.addOverlay("image3d", { - url: overlayURL(), - position: overlayPosition, + var newKickOverlay = Overlays.addOverlay("image3d", { + url: kickOverlayURL(), + position: kickOverlayPosition, size: 1, scale: 0.4, color: { red: 255, green: 255, blue: 255}, @@ -124,8 +138,23 @@ function updateOverlays() { drawInFront: true }); - // push this overlay to our array of overlays - modOverlays[avatarID] = newOverlay; + modOverlays[avatarID]=[newKickOverlay]; + + if (Users.canKick) { + var newMuteOverlay = Overlays.addOverlay("image3d", { + url: muteOverlayURL(), + position: muteOverlayPosition, + size: 1, + scale: 0.4, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + solid: true, + isFacingAvatar: true, + drawInFront: true + }); + // push this overlay to our array of overlays + modOverlays[avatarID].push(newMuteOverlay); + } } } } @@ -137,9 +166,11 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ if (isShowingOverlays) { // we are currently showing overlays and an avatar just went away - // first remove the rendered overlay - Overlays.deleteOverlay(modOverlays[avatarID]); - + // first remove the rendered overlays + for (var j = 0; j < modOverlays[avatarID].length; ++j) { + Overlays.deleteOverlay(modOverlays[avatarID][j]); + } + // delete the saved ID of the overlay from our mod overlays object delete modOverlays[avatarID]; } @@ -151,7 +182,8 @@ function handleSelectedOverlay(clickedOverlay) { var modOverlayKeys = Object.keys(modOverlays); for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; - var modOverlay = modOverlays[avatarID]; + var modOverlay = modOverlays[avatarID][0]; + var muteOverlay = modOverlays[avatarID][1]; if (clickedOverlay.overlayID == modOverlay) { // matched to an overlay, ask for the matching avatar to be kicked or ignored @@ -160,8 +192,10 @@ function handleSelectedOverlay(clickedOverlay) { } else { Users.ignore(avatarID); } - // cleanup of the overlay is handled by the connection to avatarRemovedEvent + + } else if (muteOverlay && clickedOverlay.overlayID == muteOverlay) { + Users.mute(avatarID); } } } diff --git a/scripts/system/users.js b/scripts/system/users.js index 1e4cf042f3..8c52240aa9 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -228,7 +228,7 @@ var usersWindow = (function () { var WINDOW_WIDTH = 260, WINDOW_MARGIN = 12, - WINDOW_BASE_MARGIN = 6, // A little less is needed in order look correct + WINDOW_BASE_MARGIN = 24, // A little less is needed in order look correct WINDOW_FONT = { size: 12 }, @@ -253,11 +253,17 @@ var usersWindow = (function () { windowPane, windowHeading, + // Margin on the left and right side of the window to keep + // it from getting too close to the edge of the screen which + // is unclickable. + WINDOW_MARGIN_X = 20, + // Window border is similar to that of edit.js. - WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_BASE_MARGIN, - WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_BASE_MARGIN, - WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_BASE_MARGIN, - WINDOW_BORDER_LEFT_MARGIN = WINDOW_BASE_MARGIN, + WINDOW_MARGIN_HALF = WINDOW_MARGIN / 2, + WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_MARGIN_HALF, + WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_MARGIN_HALF, + WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_MARGIN_HALF, + WINDOW_BORDER_LEFT_MARGIN = WINDOW_MARGIN_HALF, WINDOW_BORDER_RADIUS = 4, WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 }, WINDOW_BORDER_ALPHA = 0.5, @@ -371,7 +377,8 @@ var usersWindow = (function () { MENU_NAME = "View", MENU_ITEM = "Users Online", - MENU_ITEM_AFTER = "Overlays", + MENU_ITEM_OVERLAYS = "Overlays", + MENU_ITEM_AFTER = MENU_ITEM_OVERLAYS, SETTING_USERS_SHOW_ME = "UsersWindow.ShowMe", SETTING_USERS_VISIBLE_TO = "UsersWindow.VisibleTo", @@ -399,6 +406,10 @@ var usersWindow = (function () { scrollbarBarClickedAt, // 0.0 .. 1.0 scrollbarValue = 0.0; // 0.0 .. 1.0 + function isWindowDisabled() { + return !Menu.isOptionChecked(MENU_ITEM) || !Menu.isOptionChecked(MENU_ITEM_OVERLAYS); + } + function isValueTrue(value) { // Work around Boolean Settings values being read as string when Interface starts up but as Booleans when re-read after // Being written if refresh script. @@ -445,7 +456,7 @@ var usersWindow = (function () { } function saturateWindowPosition() { - windowPosition.x = Math.max(0, Math.min(viewport.x - WINDOW_WIDTH, windowPosition.x)); + windowPosition.x = Math.max(WINDOW_MARGIN_X, Math.min(viewport.x - WINDOW_WIDTH - WINDOW_MARGIN_X, windowPosition.x)); windowPosition.y = Math.max(windowMinimumHeight, Math.min(viewport.y, windowPosition.y)); } @@ -744,7 +755,7 @@ var usersWindow = (function () { userClicked, delta; - if (!isVisible) { + if (!isVisible || isWindowDisabled()) { return; } @@ -856,7 +867,7 @@ var usersWindow = (function () { function onMouseMoveEvent(event) { var isVisible; - if (!isLoggedIn) { + if (!isLoggedIn || isWindowDisabled()) { return; } @@ -914,6 +925,10 @@ var usersWindow = (function () { function onMouseReleaseEvent() { var offset = {}; + if (isWindowDisabled()) { + return; + } + if (isMovingScrollbar) { Overlays.editOverlay(scrollbarBar, { backgroundAlpha: SCROLLBAR_BAR_ALPHA @@ -939,6 +954,10 @@ var usersWindow = (function () { MIRROR_MENU_ITEM = "Mirror", FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror"; + if (isWindowDisabled()) { + return; + } + viewport = Controller.getViewportDimensions(); isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM); isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM); diff --git a/tools/jsdoc/.gitignore b/tools/jsdoc/.gitignore new file mode 100644 index 0000000000..c585e19389 --- /dev/null +++ b/tools/jsdoc/.gitignore @@ -0,0 +1 @@ +out \ No newline at end of file diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md new file mode 100644 index 0000000000..c43f95cabe --- /dev/null +++ b/tools/jsdoc/README.md @@ -0,0 +1,13 @@ +#JavaScript Documentation Generation + +##Prerequisites + +* Install node.js +* Install jsdoc via npm. `npm install jsdoc -g` + +To generate html documentation for the High Fidelity JavaScript API + +`cd scripts/jsdoc` +`jsdoc . -c config.json` + +The out folder should contain index.html diff --git a/tools/jsdoc/config.json b/tools/jsdoc/config.json new file mode 100644 index 0000000000..0fb833d015 --- /dev/null +++ b/tools/jsdoc/config.json @@ -0,0 +1,8 @@ +{ + "templates": { + "default": { + "outputSourceFiles": false + } + }, + "plugins": ["plugins/hifi"] +} diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js new file mode 100644 index 0000000000..8016aa2ae5 --- /dev/null +++ b/tools/jsdoc/plugins/hifi.js @@ -0,0 +1,41 @@ +function endsWith(path, exts) { + var result = false; + exts.forEach(function(ext) { + if (path.endsWith(ext)) { + result = true; + } + }); + return result; +} + +exports.handlers = { + beforeParse: function(e) { + console.log("Scanning hifi source for jsdoc comments..."); + + // directories to scan for jsdoc comments + var dirList = [ + '../../interface/src', + '../../interface/src/scripting', + '../../libraries/script-engine/src', + '../../libraries/networking/src', + '../../libraries/animation/src', + ]; + var exts = ['.h', '.cpp']; + + const fs = require('fs'); + dirList.forEach(function (dir) { + var files = fs.readdirSync(dir) + files.forEach(function (file) { + var path = dir + "/" + file; + if (fs.lstatSync(path).isFile() && endsWith(path, exts)) { + var data = fs.readFileSync(path, "utf8"); + var reg = /(\/\*\*jsdoc(.|[\r\n])*?\*\/)/gm; + var matches = data.match(reg); + if (matches) { + e.source += matches.map(function (s) { return s.replace('/**jsdoc', '/**'); }).join('\n'); + } + } + }); + }); + } +}; diff --git a/tools/jsdoc/root.js b/tools/jsdoc/root.js new file mode 100644 index 0000000000..097403f723 --- /dev/null +++ b/tools/jsdoc/root.js @@ -0,0 +1,11 @@ +// +// root.js +// +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Root of High Fidelity generated java script documentation +// +