From 0fe4e42511cbb67464131279ba6c44df3e209601 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 1 May 2016 13:53:44 -0700 Subject: [PATCH 001/264] added zone properties to allow flying/ghosting or not --- .../entities/src/EntityItemProperties.cpp | 27 +++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 6 +++++ libraries/entities/src/EntityPropertyFlags.h | 3 +++ libraries/entities/src/ZoneEntityItem.cpp | 24 ++++++++++++++--- libraries/entities/src/ZoneEntityItem.h | 16 ++++++++--- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + scripts/system/html/entityProperties.html | 20 +++++++++++++- 8 files changed, 91 insertions(+), 8 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 92849d6e2f..feacb4ab98 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -309,6 +309,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); + CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); + CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); @@ -467,6 +469,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed); } // Web only @@ -679,6 +684,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + _lastEdited = usecTimestampNow(); } @@ -849,6 +857,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_HOUR, Stage, stage, Hour, hour); ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay); + ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); + // FIXME - these are not yet handled //ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64); @@ -1089,6 +1100,9 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1373,6 +1387,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1564,6 +1581,9 @@ void EntityItemProperties::markAllChanged() { _jointTranslationsChanged = true; _queryAACubeChanged = true; + + _flyingAllowedChanged = true; + _ghostingAllowedChanged = true; } // The minimum bounding box for the entity. @@ -1885,6 +1905,13 @@ QList EntityItemProperties::listChangedProperties() { out += "queryAACube"; } + if (flyingAllowedChanged()) { + out += "flyingAllowed"; + } + if (ghostingAllowedChanged()) { + out += "ghostingAllowed"; + } + getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); getSkybox().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2cf31e5632..f32bc977e0 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -205,6 +205,9 @@ public: DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector, QVector()); + DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); + DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); + static QString getBackgroundModeString(BackgroundMode mode); @@ -421,6 +424,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslationsSet, jointTranslationsSet, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90a7c1e2f7..19b47da8e7 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -169,6 +169,9 @@ enum EntityPropertyList { PROP_FALLOFF_RADIUS, // for Light entity + PROP_FLYING_ALLOWED, // can avatars in a zone fly? + PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics? + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 6aabef5cc0..a28b8210c2 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -23,8 +23,12 @@ bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; + const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; +const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true; +const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true; + EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity { new ZoneEntityItem(entityID) }; @@ -55,6 +59,9 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr _skyboxProperties.getProperties(properties); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); + return properties; } @@ -70,6 +77,9 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); + bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); somethingChanged = somethingChanged || somethingChangedInKeyLight || somethingChangedInStage || somethingChangedInSkybox; @@ -116,6 +126,9 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromSkybox; dataAt += bytesFromSkybox; + READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); + READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + return bytesRead; } @@ -125,13 +138,16 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += _keyLightProperties.getEntityProperties(params); - + requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_BACKGROUND_MODE; requestedProperties += _stageProperties.getEntityProperties(params); requestedProperties += _skyboxProperties.getEntityProperties(params); - + + requestedProperties += PROP_FLYING_ALLOWED; + requestedProperties += PROP_GHOSTING_ALLOWED; + return requestedProperties; } @@ -155,10 +171,12 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16?? - + _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); } void ZoneEntityItem::debugDump() const { diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 0326a0f441..56968aa9c9 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -70,6 +70,11 @@ public: const SkyboxPropertyGroup& getSkyboxProperties() const { return _skyboxProperties; } const StagePropertyGroup& getStageProperties() const { return _stageProperties; } + bool getFlyingAllowed() const { return _flyingAllowed; } + void setFlyingAllowed(bool value) { _flyingAllowed = value; } + bool getGhostingAllowed() const { return _ghostingAllowed; } + void setGhostingAllowed(bool value) { _ghostingAllowed = value; } + virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, @@ -80,18 +85,23 @@ public: static const ShapeType DEFAULT_SHAPE_TYPE; static const QString DEFAULT_COMPOUND_SHAPE_URL; - + static const bool DEFAULT_FLYING_ALLOWED; + static const bool DEFAULT_GHOSTING_ALLOWED; + protected: KeyLightPropertyGroup _keyLightProperties; - + ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; - + BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; StagePropertyGroup _stageProperties; SkyboxPropertyGroup _skyboxProperties; + bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; + bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; + static bool _drawZoneBoundaries; static bool _zonesArePickable; }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e4aab94090..6b917df87a 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -47,7 +47,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_LIGHT_HAS_FALLOFF_RADIUS; + return VERSION_ENTITIES_NO_FLY_ZONES; case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b98a87e439..42e5fff420 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -171,6 +171,7 @@ const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54; const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55; const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; +const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index ae5684d6c8..35a78682f3 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -484,6 +484,9 @@ var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); + + var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); + var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); allSections.push(elPolyVoxSections); @@ -770,6 +773,9 @@ elZoneSkyboxColorGreen.value = properties.skybox.color.green; elZoneSkyboxColorBlue.value = properties.skybox.color.blue; elZoneSkyboxURL.value = properties.skybox.url; + + elZoneFlyingAllowed.checked = properties.flyingAllowed; + elZoneGhostingAllowed.checked = properties.ghostingAllowed; showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { @@ -1076,7 +1082,10 @@ })); elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox','url')); - + + elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); + elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); @@ -1791,6 +1800,15 @@ +
+ + +
+
+ + +
+
M From ef85cc7803b0360f04ee0196b044a409096c9e23 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 1 May 2016 14:47:12 -0700 Subject: [PATCH 002/264] hook up zone flyingAllowed flag to character controller --- interface/src/avatar/MyAvatar.cpp | 6 ++++++ .../entities-renderer/src/EntityTreeRenderer.h | 2 ++ libraries/physics/src/CharacterController.cpp | 14 ++++++++++++++ libraries/physics/src/CharacterController.h | 5 +++++ 4 files changed, 27 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 614f7bd9fe..e039633961 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -426,7 +426,12 @@ void MyAvatar::simulate(float deltaTime) { EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { + bool flyingAllowed = true; entityTree->withWriteLock([&] { + std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); + if (zone) { + flyingAllowed = zone->getFlyingAllowed(); + } auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); MovingEntitiesOperator moveOperator(entityTree); @@ -454,6 +459,7 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); + _characterController.setFlyingAllowed(flyingAllowed); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a6fc58e5f1..3eb3796c94 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -88,6 +88,8 @@ public: // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } + std::shared_ptr myAvatarZone() { return _bestZone; } + signals: void mousePressOnEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event); void mousePressOffEntity(const RayToEntityIntersectionResult& intersection, const QMouseEvent* event); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index f685aee748..b9306b67b0 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -294,6 +294,10 @@ void CharacterController::setState(State desiredState, const char* reason) { #else void CharacterController::setState(State desiredState) { #endif + if (!_flyingAllowed && desiredState == State::Hover) { + desiredState = State::InAir; + } + if (desiredState != _state) { #ifdef DEBUG_STATE_CHANGE qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason; @@ -544,3 +548,13 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio avatarRigidBodyRotation = bulletToGLM(worldTrans.getRotation()); return true; } + +void CharacterController::setFlyingAllowed(bool value) { + if (_flyingAllowed != value) { + _flyingAllowed = value; + + if (!_flyingAllowed && _state == State::Hover) { + SET_STATE(State::InAir, "flying not allowed"); + } + } +} diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index d810e904a7..eac8379ad0 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -98,6 +98,9 @@ public: bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); + void setFlyingAllowed(bool value); + + protected: #ifdef DEBUG_STATE_CHANGE void setState(State state, const char* reason); @@ -147,6 +150,8 @@ protected: btRigidBody* _rigidBody { nullptr }; uint32_t _pendingFlags { 0 }; uint32_t _previousFlags { 0 }; + + bool _flyingAllowed { true }; }; #endif // hifi_CharacterControllerInterface_h From 4feb2944edaab41830588df38da2a12e788f401f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 1 May 2016 15:27:49 -0700 Subject: [PATCH 003/264] hook up zone's ghostingAllowed flag --- interface/src/avatar/MyAvatar.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e039633961..2513bb7849 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -427,10 +427,12 @@ void MyAvatar::simulate(float deltaTime) { EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { bool flyingAllowed = true; + bool ghostingAllowed = true; entityTree->withWriteLock([&] { std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); if (zone) { flyingAllowed = zone->getFlyingAllowed(); + ghostingAllowed = zone->getGhostingAllowed(); } auto now = usecTimestampNow(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); @@ -460,6 +462,9 @@ void MyAvatar::simulate(float deltaTime) { } }); _characterController.setFlyingAllowed(flyingAllowed); + if (!_characterController.isEnabled() && !ghostingAllowed) { + _characterController.setEnabled(true); + } } } @@ -1839,7 +1844,21 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } - _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); + + bool ghostingAllowed = true; + EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); + if (entityTreeRenderer) { + std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); + if (zone) { + ghostingAllowed = zone->getGhostingAllowed(); + } + } + bool checked = menu->isOptionChecked(MenuOption::EnableCharacterController); + if (!ghostingAllowed) { + checked = true; + } + + _characterController.setEnabled(checked); } void MyAvatar::clearDriveKeys() { From 0e6d9a1eecbbc82da30a5409f0c30912750ecbac Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 May 2016 14:48:31 -0700 Subject: [PATCH 004/264] avatar mixer can relay "client-only" entities between interfaces -- the entity server wont know about them. --- interface/src/Application.cpp | 6 ++ interface/src/avatar/Avatar.cpp | 85 +++++++++++++++++ interface/src/avatar/Avatar.h | 1 + interface/src/avatar/MyAvatar.cpp | 28 +++++- libraries/avatars/src/AvatarData.cpp | 93 ++++++++++++++++++- libraries/avatars/src/AvatarData.h | 20 ++++ libraries/avatars/src/AvatarHashMap.cpp | 9 +- .../src/RenderableWebEntityItem.cpp | 5 + .../entities/src/EntityEditPacketSender.cpp | 45 ++++++++- .../entities/src/EntityEditPacketSender.h | 11 ++- libraries/entities/src/EntityItem.h | 14 ++- libraries/entities/src/EntityItemProperties.h | 9 ++ .../entities/src/EntityScriptingInterface.cpp | 17 ++-- .../entities/src/EntityScriptingInterface.h | 2 +- libraries/entities/src/EntityTree.cpp | 5 +- libraries/physics/src/EntityMotionState.cpp | 12 ++- 16 files changed, 336 insertions(+), 26 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4a829b3191..0b1154d983 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -796,6 +796,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); + _entityEditSender.setMyAvatar(getMyAvatar()); // For now we're going to set the PPS for outbound packets to be super high, this is // probably not the right long term solution. But for now, we're going to do this to @@ -1087,6 +1088,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Make sure we don't time out during slow operations at startup updateHeartbeat(); + OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender(); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->setMyAvatar(getMyAvatar()); + connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); @@ -4186,6 +4191,7 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); + getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 2cacb81ce4..0090dcedf6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "Application.h" #include "Avatar.h" @@ -160,6 +161,89 @@ void Avatar::animateScaleChanges(float deltaTime) { } } +void Avatar::updateAvatarEntities() { + // - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity() + // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited + // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket + // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces + // - AvatarHashMap::processAvatarIdentityPacket's on other interfaces call avatar->setAvatarEntityData() + // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true + // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... + + quint64 now = usecTimestampNow(); + + const static quint64 refreshTime = 3 * USECS_PER_SECOND; + if (!_avatarEntityDataChanged && now - _avatarEntityChangedTime < refreshTime) { + return; + } + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + bool success = true; + QScriptEngine scriptEngine; + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties + // and either add or update the entity. + QByteArray jsonByteArray = avatarEntities.value(entityID); + QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); + if (!jsonProperties.isObject()) { + qDebug() << "got bad avatarEntity json"; + continue; + } + QVariant variantProperties = jsonProperties.toVariant(); + QVariantMap asMap = variantProperties.toMap(); + QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); + EntityItemProperties properties; + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); + properties.setClientOnly(true); + properties.setOwningAvatarID(getID()); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + + if (entity) { + if (!entityTree->updateEntity(entityID, properties)) { + qDebug() << "AVATAR-ENTITES -- updateEntity failed: " << properties.getType(); + success = false; + } + } else { + entity = entityTree->addEntity(entityID, properties); + if (!entity) { + qDebug() << "AVATAR-ENTITES -- addEntity failed: " << properties.getType(); + success = false; + } + } + } + }); + + AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); + foreach (auto entityID, recentlyDettachedAvatarEntities) { + if (!_avatarEntityData.contains(entityID)) { + EntityItemPointer dettachedEntity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + if (dettachedEntity) { + // this will cause this interface to listen to data from the entity-server about this entity. + dettachedEntity->setClientOnly(false); + } + } + } + + if (success) { + setAvatarEntityDataChanged(false); + _avatarEntityChangedTime = now; + } +} + + + void Avatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); @@ -228,6 +312,7 @@ void Avatar::simulate(float deltaTime) { simulateAttachments(deltaTime); updatePalms(); + updateAvatarEntities(); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index cb35fbb5eb..ded5ee6433 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -64,6 +64,7 @@ public: typedef std::shared_ptr PayloadPointer; void init(); + void updateAvatarEntities(); void simulate(float deltaTime); virtual void simulateAttachments(float deltaTime); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bad60643ec..f9daad923b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -311,6 +311,10 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); + if (_avatarEntityDataLocallyEdited) { + sendIdentityPacket(); + } + simulate(deltaTime); currentEnergy += energyChargeRate; @@ -443,7 +447,7 @@ void MyAvatar::simulate(float deltaTime) { EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); } } @@ -455,6 +459,8 @@ void MyAvatar::simulate(float deltaTime) { } }); } + + updateAvatarEntities(); } // thread-safe @@ -696,6 +702,16 @@ void MyAvatar::saveData() { } settings.endArray(); + settings.beginWriteArray("avatarEntityData"); + int avatarEntityIndex = 0; + for (auto entityID : _avatarEntityData.keys()) { + settings.setArrayIndex(avatarEntityIndex); + settings.setValue("id", entityID); + settings.setValue("properties", _avatarEntityData.value(entityID)); + avatarEntityIndex++; + } + settings.endArray(); + settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); @@ -807,6 +823,16 @@ void MyAvatar::loadData() { settings.endArray(); setAttachmentData(attachmentData); + int avatarEntityCount = settings.beginReadArray("avatarEntityData"); + for (int i = 0; i < avatarEntityCount; i++) { + settings.setArrayIndex(i); + QUuid entityID = settings.value("id").toUuid(); + QByteArray properties = settings.value("properties").toByteArray(); + updateAvatarEntity(entityID, properties); + } + settings.endArray(); + setAvatarEntityDataChanged(true); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fd13f8c370..9a0fc1a835 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -962,8 +962,9 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { QUrl unusedModelURL; // legacy faceModel support QUrl skeletonModelURL; QVector attachmentData; + AvatarEntityMap avatarEntityData; QString displayName; - packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName; + packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName >> avatarEntityData; bool hasIdentityChanged = false; @@ -983,6 +984,11 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { hasIdentityChanged = true; } + if (avatarEntityData != _avatarEntityData) { + setAvatarEntityData(avatarEntityData); + hasIdentityChanged = true; + } + return hasIdentityChanged; } @@ -994,7 +1000,7 @@ QByteArray AvatarData::identityByteArray() { QUrl unusedModelURL; // legacy faceModel support - identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName; + identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName << _avatarEntityData; return identityData; } @@ -1202,6 +1208,8 @@ void AvatarData::sendIdentityPacket() { [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); + + _avatarEntityDataLocallyEdited = false; } void AvatarData::sendBillboardPacket() { @@ -1389,6 +1397,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel"); static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); QJsonValue toJsonValue(const JointData& joint) { @@ -1427,6 +1436,17 @@ QJsonObject AvatarData::toJson() const { root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson; } + if (!_avatarEntityData.empty()) { + QJsonArray avatarEntityJson; + for (auto entityID : _avatarEntityData.keys()) { + QVariantMap entityData; + entityData.insert("id", entityID); + entityData.insert("properties", _avatarEntityData.value(entityID)); + avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); + } + root[JSON_AVATAR_ENTITIES] = avatarEntityJson; + } + auto recordingBasis = getRecordingBasis(); bool success; Transform avatarTransform = getTransform(success); @@ -1526,6 +1546,13 @@ void AvatarData::fromJson(const QJsonObject& json) { setAttachmentData(attachments); } + // if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) { + // QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray(); + // for (auto attachmentJson : attachmentsJson) { + // // TODO -- something + // } + // } + // Joint rotations are relative to the avatar, so they require no basis correction if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { QVector jointArray; @@ -1678,9 +1705,69 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { QVector newAttachments; newAttachments.reserve(variant.size()); for (const auto& attachmentVar : variant) { - AttachmentData attachment; + AttachmentData attachment; attachment.fromVariant(attachmentVar); newAttachments.append(attachment); } setAttachmentData(newAttachments); } + +void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData)); + return; + } + _avatarEntityData.insert(entityID, entityData); + _avatarEntityDataLocallyEdited = true; +} + +void AvatarData::clearAvatarEntity(const QUuid& entityID) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearAvatarEntity", Q_ARG(const QUuid&, entityID)); + return; + } + _avatarEntityData.remove(entityID); + _avatarEntityDataLocallyEdited = true; +} + +AvatarEntityMap AvatarData::getAvatarEntityData() const { + if (QThread::currentThread() != thread()) { + AvatarEntityMap result; + QMetaObject::invokeMethod(const_cast(this), "getAvatarEntityData", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityMap, result)); + return result; + } + return _avatarEntityData; +} + +void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData)); + return; + } + if (_avatarEntityData != avatarEntityData) { + // keep track of entities that were attached to this avatar but no longer are + AvatarEntityIDs previousAvatarEntityIDs = QSet::fromList(_avatarEntityData.keys()); + + _avatarEntityData = avatarEntityData; + setAvatarEntityDataChanged(true); + + foreach (auto entityID, previousAvatarEntityIDs) { + if (!_avatarEntityData.contains(entityID)) { + _avatarEntityDetached.insert(entityID); + } + } + } +} + +AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { + if (QThread::currentThread() != thread()) { + AvatarEntityIDs result; + QMetaObject::invokeMethod(const_cast(this), "getRecentlyDetachedIDs", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AvatarEntityIDs, result)); + return result; + } + AvatarEntityIDs result = _avatarEntityDetached; + _avatarEntityDetached.clear(); + return result; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 900da38ffa..72d34af9d9 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -61,6 +61,8 @@ typedef unsigned long long quint64; using AvatarSharedPointer = std::shared_ptr; using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; +using AvatarEntityMap = QMap; +using AvatarEntityIDs = QSet; using AvatarDataSequenceNumber = uint16_t; @@ -135,6 +137,10 @@ class AttachmentData; class Transform; using TransformPointer = std::shared_ptr; +// When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows +// the value to be reset when the sessionID changes. +const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}"); + class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -274,6 +280,9 @@ public: Q_INVOKABLE QVariantList getAttachmentsVariant() const; Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); + void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state @@ -333,6 +342,11 @@ public: glm::vec3 getClientGlobalPosition() { return _globalPosition; } + Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } + AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -405,6 +419,12 @@ protected: // updates about one avatar to another. glm::vec3 _globalPosition; + AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityMap _avatarEntityData; + bool _avatarEntityDataLocallyEdited { false }; + bool _avatarEntityDataChanged { false }; + quint64 _avatarEntityChangedTime { 0 }; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 75fb5e6028..62e87ce285 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -111,17 +111,18 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer QDataStream identityStream(message->getMessage()); QUuid sessionUUID; - + while (!identityStream.atEnd()) { QUrl faceMeshURL, skeletonURL; QVector attachmentData; + AvatarEntityMap avatarEntityData; QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; + identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName >> avatarEntityData; // mesh URL for a UUID, find avatar in our list auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - + if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) { avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire } @@ -130,6 +131,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer avatar->setAttachmentData(attachmentData); } + avatar->setAvatarEntityData(avatarEntityData); + if (avatar->getDisplayName() != displayName) { avatar->setDisplayName(displayName); } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 26aecf6050..891e1dca3b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -174,9 +174,14 @@ void RenderableWebEntityItem::render(RenderArgs* args) { #endif if (!_webSurface) { + #if defined(Q_OS_LINUX) + // these don't seem to work on Linux + return; + #else if (!buildWebSurface(static_cast(args->_renderer))) { return; } + #endif } _lastRenderTime = usecTimestampNow(); diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 1e38c32964..ea86d3d542 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include #include @@ -35,18 +36,54 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityItemID modelID, - const EntityItemProperties& properties) { +void EntityEditPacketSender::queueEditEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { if (!_shouldSend) { return; // bail early } + if (properties.getClientOnly()) { + // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server + assert(_myAvatar); + + if (!entityTree) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + return; + } + EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); + if (!entity) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; + return; + } + + // the properties that get serialized into the avatar identity packet should be the entire set + // rather than just the ones being edited. + entity->setProperties(properties); + EntityItemProperties entityProperties = entity->getProperties(); + + QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); + QVariant variantProperties = scriptProperties.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + jsonProperties = QJsonDocument(jsonObject); + + QByteArray binaryProperties = jsonProperties.toBinaryData(); + _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + return; + } QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - if (EntityItemProperties::encodeEntityEditPacket(type, modelID, properties, bufferOut)) { + if (EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut)) { #ifdef WANT_DEBUG qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << modelID; + qCDebug(entities) << " id:" << entityItemID; qCDebug(entities) << " properties:" << properties; #endif queueOctreeEditMessage(type, bufferOut); diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 26e4dd83ff..90c6cb988d 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -15,6 +15,7 @@ #include #include "EntityItem.h" +#include "AvatarData.h" /// Utility for processing, packing, queueing and sending of outbound edit voxel messages. class EntityEditPacketSender : public OctreeEditPacketSender { @@ -22,11 +23,17 @@ class EntityEditPacketSender : public OctreeEditPacketSender { public: EntityEditPacketSender(); + void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; } + AvatarData* getMyAvatar() { return _myAvatar; } + void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); } + /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. /// NOTE: EntityItemProperties assumes that all distances are in meter units - void queueEditEntityMessage(PacketType type, EntityItemID modelID, const EntityItemProperties& properties); + void queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + void queueEraseEntityMessage(const EntityItemID& entityItemID); @@ -40,5 +47,7 @@ public slots: private: bool _shouldProcessNack = true; + AvatarData* _myAvatar { nullptr }; + QScriptEngine _scriptEngine; }; #endif // hifi_EntityEditPacketSender_h diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ecb9800e70..61f7fb0082 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -420,12 +420,19 @@ public: /// entity to definitively state if the preload signal should be sent. /// /// We only want to preload if: - /// there is some script, and either the script value or the scriptTimestamp + /// there is some script, and either the script value or the scriptTimestamp /// value have changed since our last preload - bool shouldPreloadScript() const { return !_script.isEmpty() && + bool shouldPreloadScript() const { return !_script.isEmpty() && ((_loadedScript != _script) || (_loadedScriptTimestamp != _scriptTimestamp)); } void scriptHasPreloaded() { _loadedScript = _script; _loadedScriptTimestamp = _scriptTimestamp; } + bool getClientOnly() const { return _clientOnly; } + void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } + // if this entity is client-only, which avatar is it associated with? + QUuid getOwningAvatarID() const { return _owningAvatarID; } + void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + + protected: void setSimulated(bool simulated) { _simulated = simulated; } @@ -537,6 +544,9 @@ protected: mutable QHash _previouslyDeletedActions; QUuid _sourceUUID; /// the server node UUID we came from + + bool _clientOnly { false }; + QUuid _owningAvatarID; }; #endif // hifi_EntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2cf31e5632..fb24e711f4 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -276,6 +276,12 @@ public: void setJointRotationsDirty() { _jointRotationsSetChanged = true; _jointRotationsChanged = true; } void setJointTranslationsDirty() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; } + bool getClientOnly() const { return _clientOnly; } + void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } + // if this entity is client-only, which avatar is it associated with? + QUuid getOwningAvatarID() const { return _owningAvatarID; } + void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); @@ -302,6 +308,9 @@ private: glm::vec3 _naturalPosition; EntityPropertyFlags _desiredProperties; // if set will narrow scopes of copy/to/from to just these properties + + bool _clientOnly { false }; + QUuid _owningAvatarID; }; Q_DECLARE_METATYPE(EntityItemProperties); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 093fa73ace..0869ac40da 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -36,7 +36,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { - getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties); + getEntityPacketSender()->queueEditEntityMessage(packetType, _entityTree, entityID, properties); } bool EntityScriptingInterface::canAdjustLocks() { @@ -123,9 +123,10 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti } -QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { +QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + propertiesWithSimID = clientOnly; auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; @@ -272,13 +273,15 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& bool updatedEntity = false; _entityTree->withWriteLock([&] { + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); + if (!entity) { + return; + } + if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. - EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); - if (!entity) { - return; - } + //existing entity, retrieve old velocity for check down below oldVelocity = entity->getVelocity().length(); @@ -296,6 +299,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } } properties = convertLocationFromScriptSemantics(properties); + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); float cost = calculateCost(density * volume, oldVelocity, newVelocity); cost *= costMultiplier; diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index e5f913dbf8..2bd08f8e3f 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -82,7 +82,7 @@ public slots: Q_INVOKABLE bool canRez(); /// adds a model with the specific properties - Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties); + Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false); /// temporary method until addEntity can be used from QJSEngine Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b4f0c484d5..86bbf0b74d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1373,8 +1373,11 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra } properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + EntityTreePointer tree = entityTreeElement->getTree(); + // queue the packet to send to the server - args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, newID, properties); + args->packetSender->queueEditEntityMessage(PacketType::EntityAdd, tree, newID, properties); // also update the local tree instantly (note: this is not our tree, but an alternate tree) if (args->otherTree) { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index f0539110d3..070bf81e3a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -547,8 +547,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); - _entity->setLastBroadcast(usecTimestampNow()); + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); + _entity->setLastBroadcast(now); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -559,8 +562,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(usecTimestampNow()); + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, + descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(now); } } }); From 473010f634ea9fc68821d02c06c0ba028f0bb832 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 May 2016 16:45:09 -0700 Subject: [PATCH 005/264] addEntity has a clientOnly flag now --- libraries/entities/src/EntityScriptingInterface.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 0869ac40da..499b146d30 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -126,7 +126,13 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); - propertiesWithSimID = clientOnly; + + if (clientOnly) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setClientOnly(clientOnly); + propertiesWithSimID.setOwningAvatarID(myNodeID); + } auto dimensions = propertiesWithSimID.getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; From 91ff851bf8851189dc28c59d37862e7cfd580301 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 May 2016 16:59:54 -0700 Subject: [PATCH 006/264] fix call to queueEditEntityMessage --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 6c4e3994c6..858b34c97f 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -981,7 +981,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { PhysicalEntitySimulation* peSimulation = static_cast(simulation); EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; if (packetSender) { - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, tree, entity->getID(), properties); } }); }); From 46c1049a353f5d6317c6c4adf91890c6ee4eb823 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 May 2016 17:48:06 -0700 Subject: [PATCH 007/264] bump protocol version --- libraries/avatars/src/AvatarData.cpp | 1 + libraries/avatars/src/AvatarHashMap.cpp | 1 + libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 ++- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9a0fc1a835..c68240bdca 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -956,6 +956,7 @@ void AvatarData::clearJointsData() { } bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { + // this is used by the avatar-mixer QDataStream packetStream(data); QUuid avatarUUID; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 62e87ce285..612f4c6f96 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -107,6 +107,7 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { + // this is used by clients // setup a data stream to parse the packet QDataStream identityStream(message->getMessage()); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e4aab94090..fbe31d1e8d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -50,7 +50,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return VERSION_LIGHT_HAS_FALLOFF_RADIUS; case PacketType::AvatarData: case PacketType::BulkAvatarData: - return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + return static_cast(AvatarMixerPacketVersion::AvatarEntities); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b98a87e439..b29fccb45e 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -174,7 +174,8 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, - SoftAttachmentSupport + SoftAttachmentSupport, + AvatarEntities }; #endif // hifi_PacketHeaders_h From 0ab0409979b20c39ca61dbf6d1a46c2d87e66df0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 May 2016 17:48:37 -0700 Subject: [PATCH 008/264] revert accidental change --- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 891e1dca3b..26aecf6050 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -174,14 +174,9 @@ void RenderableWebEntityItem::render(RenderArgs* args) { #endif if (!_webSurface) { - #if defined(Q_OS_LINUX) - // these don't seem to work on Linux - return; - #else if (!buildWebSurface(static_cast(args->_renderer))) { return; } - #endif } _lastRenderTime = usecTimestampNow(); From 6e59abc8f6ec64d8810ae4c80a600d200ba5316e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 10 May 2016 12:13:46 +1200 Subject: [PATCH 009/264] Switch file selection dialog over to using UI Toolkit files --- .../resources/qml/dialogs/FileDialog.qml | 54 ++++++++++++------- tests/ui/qml/main.qml | 37 +++++++------ 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 916ef434b6..8bb92b60d6 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -1,4 +1,14 @@ -import QtQuick 2.0 +// +// Desktop.qml +// +// Created by Bradley Austin Davis on 14 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 import QtQuick.Controls 1.4 import Qt.labs.folderlistmodel 2.1 import Qt.labs.settings 1.0 @@ -6,17 +16,19 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import ".." -import "../windows" -import "../styles" -import "../controls" as VrControls +import "../controls-uit" +import "../styles-uit" +import "../windows-uit" + import "fileDialog" //FIXME implement shortcuts for favorite location ModalWindow { id: root - resizable: true - width: 640 - height: 480 + //resizable: true + implicitWidth: 640 + implicitHeight: 480 + HifiConstants { id: hifi } Settings { category: "FileDialog" @@ -46,6 +58,8 @@ ModalWindow { property alias model: fileTableView.model property var drives: helper.drives() + property int titleWidth: 0 + signal selectedFile(var file); signal canceled(); @@ -56,14 +70,17 @@ ModalWindow { }) } - Rectangle { - anchors.fill: parent - color: "white" + Item { + clip: true + width: pane.width + height: pane.height + anchors.margins: 0 Row { id: navControls anchors { left: parent.left; top: parent.top; margins: 8 } spacing: 8 + // FIXME implement back button //VrControls.ButtonAwesome { // id: backButton @@ -72,30 +89,29 @@ ModalWindow { // enabled: d.backStack.length != 0 // MouseArea { anchors.fill: parent; onClicked: d.navigateBack() } //} - VrControls.ButtonAwesome { + + Button { id: upButton enabled: model.parentFolder && model.parentFolder !== "" - text: "\uf0aa" - size: 32 + text: "up" onClicked: d.navigateUp(); } - VrControls.ButtonAwesome { + + Button { id: homeButton property var destination: helper.home(); enabled: d.homeDestination ? true : false - text: "\uf015" - size: 32 + text: "home" onClicked: d.navigateHome(); } - VrControls.ComboBox { + ComboBox { id: drivesSelector width: 48 height: homeButton.height model: drives visible: drives.length > 1 currentIndex: 0 - } } @@ -385,5 +401,3 @@ ModalWindow { } } } - - diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index e45749e1de..54ce16fbc2 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -211,6 +211,26 @@ ApplicationWindow { } } + Button { + text: "Open File" + property var builder: Component { + FileDialog { + title: "Open File" + filter: "*.js" + } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + Button { text: "Add Tab" onClicked: { @@ -246,24 +266,7 @@ ApplicationWindow { } } - Button { - text: "Open File" - property var builder: Component { - FileDialog { } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } } - /* Window { id: blue From 43ee64c251b83f8ef2ebfb1d1a8f9d77c0cd162e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 10 May 2016 12:50:32 +1200 Subject: [PATCH 010/264] Upate general layout of file selection dialog --- .../resources/qml/dialogs/FileDialog.qml | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 8bb92b60d6..d74913b301 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -78,8 +78,12 @@ ModalWindow { Row { id: navControls - anchors { left: parent.left; top: parent.top; margins: 8 } - spacing: 8 + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: parent.left + } + spacing: hifi.dimensions.contentSpacing.x // FIXME implement back button //VrControls.ButtonAwesome { @@ -119,7 +123,13 @@ ModalWindow { id: currentDirectory height: homeButton.height style: TextFieldStyle { renderType: Text.QtRendering } - anchors { left: navControls.right; right: parent.right; top: parent.top; margins: 8 } + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.y + left: navControls.right + leftMargin: hifi.dimensions.contentSpacing.x + right: parent.right + } property var lastValidFolder: helper.urlToPath(model.folder) onLastValidFolderChanged: text = lastValidFolder; verticalAlignment: Text.AlignVCenter @@ -179,7 +189,14 @@ ModalWindow { FileTableView { id: fileTableView - anchors { left: parent.left; right: parent.right; top: currentDirectory.bottom; bottom: currentSelection.top; margins: 8 } + anchors { + top: navControls.bottom + topMargin: hifi.dimensions.contentSpacing.y + left: parent.left + right: parent.right + bottom: currentSelection.top + bottomMargin: hifi.dimensions.contentSpacing.y + } onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); @@ -277,7 +294,13 @@ ModalWindow { TextField { id: currentSelection style: TextFieldStyle { renderType: Text.QtRendering } - anchors { right: root.selectDirectory ? parent.right : selectionType.left; rightMargin: 8; left: parent.left; leftMargin: 8; top: selectionType.top } + anchors { + left: parent.left + right: root.selectDirectory ? parent.right : selectionType.left + rightMargin: hifi.dimensions.contentSpacing.x + bottom: buttonRow.top + bottomMargin: hifi.dimensions.contentSpacing.y + } readOnly: !root.saveDialog activeFocusOnTab: !readOnly onActiveFocusChanged: if (activeFocus) { selectAll(); } @@ -286,7 +309,11 @@ ModalWindow { FileTypeSelection { id: selectionType - anchors { bottom: buttonRow.top; bottomMargin: 8; right: parent.right; rightMargin: 8; left: buttonRow.left } + anchors { + top: currentSelection.top + left: buttonRow.left + right: parent.right + } visible: !selectDirectory KeyNavigation.left: fileTableView KeyNavigation.right: openButton @@ -294,19 +321,22 @@ ModalWindow { Row { id: buttonRow - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.bottom: parent.bottom - anchors.bottomMargin: 8 - spacing: 8 + anchors { + right: parent.right + bottom: parent.bottom + } + spacing: hifi.dimensions.contentSpacing.y + Button { id: openButton + color: hifi.buttons.blue action: okAction Keys.onReturnPressed: okAction.trigger() KeyNavigation.up: selectionType KeyNavigation.left: selectionType KeyNavigation.right: cancelButton } + Button { id: cancelButton action: cancelAction From 663a2ddc6446ce3eebb6737f0273ab8042e6fb17 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 10 May 2016 14:43:54 +1200 Subject: [PATCH 011/264] Style path and filter controls --- interface/resources/qml/dialogs/FileDialog.qml | 8 ++++---- .../qml/dialogs/fileDialog/FileTypeSelection.qml | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index d74913b301..42fe7120ea 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -1,5 +1,5 @@ // -// Desktop.qml +// FileDialog.qml // // Created by Bradley Austin Davis on 14 Jan 2016 // Copyright 2015 High Fidelity, Inc. @@ -195,7 +195,7 @@ ModalWindow { left: parent.left right: parent.right bottom: currentSelection.top - bottomMargin: hifi.dimensions.contentSpacing.y + bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } onDoubleClicked: navigateToRow(row); focus: true @@ -293,11 +293,11 @@ ModalWindow { TextField { id: currentSelection - style: TextFieldStyle { renderType: Text.QtRendering } + label: "Path:" anchors { left: parent.left right: root.selectDirectory ? parent.right : selectionType.left - rightMargin: hifi.dimensions.contentSpacing.x + rightMargin: root.selectDirectory ? 0 : hifi.dimensions.contentSpacing.x bottom: buttonRow.top bottomMargin: hifi.dimensions.contentSpacing.y } diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 57ad2028ad..3d66b37b67 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -1,8 +1,18 @@ +// +// FileTypeSelection.qml +// +// Created by Bradley Austin Davis on 29 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 -import "../../controls" as VrControls +import "../../controls-uit" -VrControls.ComboBox { +ComboBox { id: root property string filtersString: "All Files (*.*)"; property var currentFilter: [ "*.*" ]; From b8c0ec86af7d663636c1bde1d2b969b9bb763f55 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 10 May 2016 15:09:13 +1200 Subject: [PATCH 012/264] Hide file filter if only 1 file type --- interface/resources/qml/dialogs/FileDialog.qml | 6 +++--- .../resources/qml/dialogs/fileDialog/FileTypeSelection.qml | 1 + tests/ui/qml/main.qml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 42fe7120ea..3be7fbc667 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -296,8 +296,8 @@ ModalWindow { label: "Path:" anchors { left: parent.left - right: root.selectDirectory ? parent.right : selectionType.left - rightMargin: root.selectDirectory ? 0 : hifi.dimensions.contentSpacing.x + right: selectionType.visible ? selectionType.left: parent.right + rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 bottom: buttonRow.top bottomMargin: hifi.dimensions.contentSpacing.y } @@ -314,7 +314,7 @@ ModalWindow { left: buttonRow.left right: parent.right } - visible: !selectDirectory + visible: !selectDirectory && filtersCount > 1 KeyNavigation.left: fileTableView KeyNavigation.right: openButton } diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 3d66b37b67..50a10974b5 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -16,6 +16,7 @@ ComboBox { id: root property string filtersString: "All Files (*.*)"; property var currentFilter: [ "*.*" ]; + property int filtersCount: filtersString.split(';;').length // Per http://doc.qt.io/qt-5/qfiledialog.html#getOpenFileName the string can contain // multiple filters separated by semicolons diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 54ce16fbc2..1745658193 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -216,7 +216,8 @@ ApplicationWindow { property var builder: Component { FileDialog { title: "Open File" - filter: "*.js" + filter: "All Files (*.*)" + //filter: "HTML files (*.html);;Other(*.png)" } } From 88207b727b8cfa6628883a88dfc2256abb1117b7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 10 May 2016 15:45:10 +1200 Subject: [PATCH 013/264] Style directory and navigation controls "as is" --- .../qml/controls-uit/GlyphButton.qml | 2 +- .../resources/qml/dialogs/FileDialog.qml | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index c4ee53c04f..2625dda723 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -17,7 +17,7 @@ import "../styles-uit" Original.Button { property int color: 0 - property int colorScheme: hifi.colorShemes.light + property int colorScheme: hifi.colorSchemes.light property string glyph: "" width: 120 diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 3be7fbc667..1f710b4ef5 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -94,25 +94,26 @@ ModalWindow { // MouseArea { anchors.fill: parent; onClicked: d.navigateBack() } //} - Button { + GlyphButton { id: upButton + glyph: hifi.glyphs.levelUp + width: height enabled: model.parentFolder && model.parentFolder !== "" - text: "up" onClicked: d.navigateUp(); } - Button { + GlyphButton { id: homeButton property var destination: helper.home(); + glyph: hifi.glyphs.home + width: height enabled: d.homeDestination ? true : false - text: "home" onClicked: d.navigateHome(); } ComboBox { id: drivesSelector - width: 48 - height: homeButton.height + width: 62 model: drives visible: drives.length > 1 currentIndex: 0 @@ -121,8 +122,8 @@ ModalWindow { TextField { id: currentDirectory + property var lastValidFolder: helper.urlToPath(model.folder) height: homeButton.height - style: TextFieldStyle { renderType: Text.QtRendering } anchors { top: parent.top topMargin: hifi.dimensions.contentMargin.y @@ -130,11 +131,7 @@ ModalWindow { leftMargin: hifi.dimensions.contentSpacing.x right: parent.right } - property var lastValidFolder: helper.urlToPath(model.folder) onLastValidFolderChanged: text = lastValidFolder; - verticalAlignment: Text.AlignVCenter - font.pointSize: 14 - font.bold: true // FIXME add support auto-completion onAccepted: { From c572e1dc3a837e0c1f0d42678ba0ba67591697b6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 10:30:49 -0700 Subject: [PATCH 014/264] delete avatar-associated entities when the avatar goes away --- interface/src/avatar/Avatar.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 1819611af6..67f8d9c967 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -107,6 +107,18 @@ Avatar::Avatar(RigPointer rig) : Avatar::~Avatar() { assert(isDead()); // mark dead before calling the dtor + + EntityTreeRenderer* treeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + entityTree->withWriteLock([&] { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + entityTree->deleteEntity(entityID, true, true); + } + }); + } + if (_motionState) { delete _motionState; _motionState = nullptr; @@ -167,7 +179,7 @@ void Avatar::updateAvatarEntities() { // - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited // - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket // - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces - // - AvatarHashMap::processAvatarIdentityPacket's on other interfaces call avatar->setAvatarEntityData() + // - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData() // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... @@ -224,18 +236,14 @@ void Avatar::updateAvatarEntities() { } } } - }); - AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); - foreach (auto entityID, recentlyDettachedAvatarEntities) { - if (!_avatarEntityData.contains(entityID)) { - EntityItemPointer dettachedEntity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); - if (dettachedEntity) { - // this will cause this interface to listen to data from the entity-server about this entity. - dettachedEntity->setClientOnly(false); + AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs(); + foreach (auto entityID, recentlyDettachedAvatarEntities) { + if (!_avatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); } } - } + }); if (success) { setAvatarEntityDataChanged(false); From 51d6d99c73cf4ee520784e03b7b113f22b5c4e70 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 11 May 2016 09:17:11 +1200 Subject: [PATCH 015/264] Fix up files list data and basic layout --- .../qml/dialogs/fileDialog/FileTableView.qml | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml b/interface/resources/qml/dialogs/fileDialog/FileTableView.qml index 93cdeebab0..d51f9e1ed9 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTableView.qml @@ -1,4 +1,14 @@ -import QtQuick 2.0 +// +// FileDialog.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 import QtQuick.Controls 1.4 TableView { @@ -8,6 +18,7 @@ TableView { root.selection.select(0) } } + //horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff itemDelegate: Component { Item { @@ -23,6 +34,7 @@ TableView { function getText() { switch (styleData.column) { //case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss"); + case 1: return root.model.get(styleData.row, "fileIsDir") ? "" : styleData.value; case 2: return root.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value); default: return styleData.value; } @@ -45,20 +57,23 @@ TableView { } TableViewColumn { + id: fileNameColumn role: "fileName" title: "Name" - width: 400 + width: Math.floor(0.55 * parent.width) + resizable: true } TableViewColumn { + id: fileMofifiedColumn role: "fileModified" - title: "Date Modified" - width: 200 + title: "Date" + width: Math.floor(0.3 * parent.width) + resizable: true } TableViewColumn { role: "fileSize" title: "Size" - width: 200 + width: Math.floor(0.15 * parent.width) - 16 - 2 // Allow space for vertical scrollbar and borders + resizable: true } } - - From c3f41cdd89a1c84a45cbe0595c821eea38607425 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 10 May 2016 09:41:44 -0700 Subject: [PATCH 016/264] Add PAINT_DELAY_DEBUG log --- interface/src/Application.cpp | 14 ++++++++++++++ libraries/plugins/src/plugins/DisplayPlugin.cpp | 12 ++++++++++++ libraries/plugins/src/plugins/DisplayPlugin.h | 6 +++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa98724a7d..0568b2f20e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2624,6 +2624,20 @@ void Application::idle(uint64_t now) { return; // bail early, we're throttled and not enough time has elapsed } +#ifdef DEBUG_PAINT_DELAY + static uint64_t paintDelaySamples{ 0 }; + static uint64_t paintDelayUsecs{ 0 }; + + paintDelayUsecs += displayPlugin->getPaintDelayUsecs(); + + static const int PAINT_DELAY_THROTTLE = 1000; + if (++paintDelaySamples % PAINT_DELAY_THROTTLE == 0) { + qCDebug(interfaceapp).nospace() << + "Paint delay (" << paintDelaySamples << " samples): " << + (float)paintDelaySamples / paintDelayUsecs << "us"; + } +#endif + auto presentCount = displayPlugin->presentCount(); if (presentCount < _renderedFrameIndex) { _renderedFrameIndex = INVALID_FRAME; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index c7fa5f5671..f946547ebe 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -1,5 +1,6 @@ #include "DisplayPlugin.h" +#include #include #include "PluginContainer.h" @@ -23,4 +24,15 @@ void DisplayPlugin::deactivate() { Parent::deactivate(); } +int64_t DisplayPlugin::getPaintDelayUsecs() const { + return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0; +} +void DisplayPlugin::incrementPresentCount() { +#ifdef DEBUG_PAINT_DELAY + // Avoid overhead if we are not debugging + _paintDelayTimer.start(); +#endif + + ++_presentedFrameIndex; +} diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 91dcf9398f..03a7737c3e 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -15,6 +15,7 @@ #include #include +#include class QImage; #include @@ -156,6 +157,8 @@ public: // Rate at which rendered frames are being skipped virtual float droppedFrameRate() const { return -1.0f; } uint32_t presentCount() const { return _presentedFrameIndex; } + // Time since last call to incrementPresentCount. Only valid if DEBUG_PAINT_DELAY is defined + int64_t getPaintDelayUsecs() const; virtual void cycleDebugOutput() {} @@ -165,9 +168,10 @@ signals: void recommendedFramebufferSizeChanged(const QSize & size); protected: - void incrementPresentCount() { ++_presentedFrameIndex; } + void incrementPresentCount(); private: std::atomic _presentedFrameIndex; + QElapsedTimer _paintDelayTimer; }; From 783be531254e4b0e4e7eb5df7162ca880b9020ac Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 10 May 2016 11:20:08 -0700 Subject: [PATCH 017/264] Trigger Idle from present Paint --- interface/src/Application.cpp | 117 +++++------------- interface/src/Application.h | 10 +- .../plugins/src/plugins/DisplayPlugin.cpp | 3 + libraries/plugins/src/plugins/DisplayPlugin.h | 4 + 4 files changed, 49 insertions(+), 85 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0568b2f20e..bce0b77096 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -347,19 +347,14 @@ public: }; #endif -enum CustomEventTypes { - Lambda = QEvent::User + 1, - Paint = Lambda + 1, -}; - class LambdaEvent : public QEvent { std::function _fun; public: LambdaEvent(const std::function & fun) : - QEvent(static_cast(Lambda)), _fun(fun) { + QEvent(static_cast(Application::Lambda)), _fun(fun) { } LambdaEvent(std::function && fun) : - QEvent(static_cast(Lambda)), _fun(fun) { + QEvent(static_cast(Application::Lambda)), _fun(fun) { } void call() const { _fun(); } }; @@ -1060,18 +1055,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); - - _idleTimer = new QTimer(this); - connect(_idleTimer, &QTimer::timeout, [=] { - idle(usecTimestampNow()); - }); - connect(this, &Application::beforeAboutToQuit, [=] { - disconnect(_idleTimer); - }); - // Setting the interval to zero forces this to get called whenever there are no messages - // in the queue, which can be pretty damn frequent. Hence the idle function has a bunch - // of logic to abort early if it's being called too often. - _idleTimer->start(0); } @@ -1437,23 +1420,15 @@ void Application::initializeUi() { }); } - void Application::paintGL() { - updateHeartbeat(); - // Some plugins process message events, potentially leading to - // re-entering a paint event. don't allow further processing if this - // happens - if (_inPaint) { + // Some plugins process message events, allowing paintGL to be called reentrantly. + if (_inPaint || _aboutToQuit) { return; } - _inPaint = true; - Finally clearFlagLambda([this] { _inPaint = false; }); - // paintGL uses a queued connection, so we can get messages from the queue even after we've quit - // and the plugins have shutdown - if (_aboutToQuit) { - return; - } + _inPaint = true; + Finally clearFlag([this] { _inPaint = false; }); + _frameCount++; _frameCounter.increment(); @@ -1811,14 +1786,16 @@ bool Application::event(QEvent* event) { return false; } - if ((int)event->type() == (int)Lambda) { - static_cast(event)->call(); + if ((int)event->type() == (int)Idle) { + idle(); + removePostedEvents(this, Idle); // clear pending idles so we don't clog the pipes return true; - } - - if ((int)event->type() == (int)Paint) { + } else if ((int)event->type() == (int)Paint) { paintGL(); return true; + } else if ((int)event->type() == (int)Lambda) { + static_cast(event)->call(); + return true; } if (!_keyboardFocusedItem.isInvalidID()) { @@ -2595,34 +2572,13 @@ bool Application::acceptSnapshot(const QString& urlString) { static uint32_t _renderedFrameIndex { INVALID_FRAME }; -void Application::idle(uint64_t now) { - // NOTICE NOTICE NOTICE NOTICE - // Do not insert new code between here and the PROFILE_RANGE declaration - // unless you know exactly what you're doing. This idle function can be - // called thousands per second or more, so any additional work that's done - // here will have a serious impact on CPU usage. Only add code after all - // the thottling logic, i.e. after PROFILE_RANGE - // NOTICE NOTICE NOTICE NOTICE - updateHeartbeat(); - - if (_aboutToQuit || _inPaint) { - return; // bail early, nothing to do here. +void Application::idle() { + // idle is called on a queued connection, so make sure we should be here. + if (_inPaint || _aboutToQuit) { + return; } auto displayPlugin = getActiveDisplayPlugin(); - // depending on whether we're throttling or not. - // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in - // perpetuity and not expect events to get backed up. - bool isThrottled = displayPlugin->isThrottled(); - // Only run simulation code if more than the targetFramePeriod have passed since last time we ran - // This attempts to lock the simulation at 60 updates per second, regardless of framerate - float timeSinceLastUpdateUs = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC; - float secondsSinceLastUpdate = timeSinceLastUpdateUs / USECS_PER_SECOND; - - if (isThrottled && (timeSinceLastUpdateUs / USECS_PER_MSEC) < THROTTLED_SIM_FRAME_PERIOD_MS) { - // Throttling both rendering and idle - return; // bail early, we're throttled and not enough time has elapsed - } #ifdef DEBUG_PAINT_DELAY static uint64_t paintDelaySamples{ 0 }; @@ -2638,43 +2594,38 @@ void Application::idle(uint64_t now) { } #endif - auto presentCount = displayPlugin->presentCount(); - if (presentCount < _renderedFrameIndex) { - _renderedFrameIndex = INVALID_FRAME; - } + float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC; - // Don't saturate the main thread with rendering and simulation, - // unless display plugin has increased by at least one frame - if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) { - // Record what present frame we're on - _renderedFrameIndex = presentCount; - - // request a paint, get to it as soon as possible: high priority - postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); - } else { - // there's no use in simulating or rendering faster then the present rate. + // Throttle if requested + if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) { return; } - // NOTICE NOTICE NOTICE NOTICE - // do NOT add new code above this line unless you want it to be executed potentially - // thousands of times per second - // NOTICE NOTICE NOTICE NOTICE + // Sync up the _renderedFrameIndex + _renderedFrameIndex = displayPlugin->presentCount(); - PROFILE_RANGE(__FUNCTION__); + // Request a paint ASAP + postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + + // Update the deadlock watchdog + updateHeartbeat(); + + auto offscreenUi = DependencyManager::get(); // These tasks need to be done on our first idle, because we don't want the showing of // overlay subwindows to do a showDesktop() until after the first time through static bool firstIdle = true; if (firstIdle) { firstIdle = false; - auto offscreenUi = DependencyManager::get(); connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); } + PROFILE_RANGE(__FUNCTION__); + + float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND; + // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. - auto offscreenUi = DependencyManager::get(); if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { _keyboardMouseDevice->pluginFocusOutEvent(); _keyboardDeviceHasFocus = false; diff --git a/interface/src/Application.h b/interface/src/Application.h index f52dfc8c07..e13dffbcf1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -93,6 +94,12 @@ class Application : public QApplication, public AbstractViewStateInterface, publ friend class PluginContainerProxy; public: + enum Event { + Idle = DisplayPlugin::Paint, + Paint = Idle + 1, + Lambda = Paint + 1 + }; + // FIXME? Empty methods, do we still need them? static void initPlugins(); static void shutdownPlugins(); @@ -282,7 +289,6 @@ public slots: private slots: void showDesktop(); void clearDomainOctreeDetails(); - void idle(uint64_t now); void aboutToQuit(); void resettingDomain(); @@ -321,6 +327,7 @@ private: void cleanupBeforeQuit(); + void idle(); void update(float deltaTime); // Various helper functions called during update() @@ -498,7 +505,6 @@ private: int _avatarAttachmentRequest = 0; bool _settingsLoaded { false }; - QTimer* _idleTimer { nullptr }; bool _fakedMouseEvent { false }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index f946547ebe..f52910b952 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -35,4 +35,7 @@ void DisplayPlugin::incrementPresentCount() { #endif ++_presentedFrameIndex; + + // Alert the app that it needs to paint a new presentation frame + qApp->postEvent(qApp, new QEvent(static_cast(Paint)), Qt::HighEventPriority); } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 03a7737c3e..1e9b16eeac 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -60,6 +60,10 @@ class DisplayPlugin : public Plugin { Q_OBJECT using Parent = Plugin; public: + enum Event { + Paint = QEvent::User + 1 + }; + bool activate() override; void deactivate() override; virtual bool isHmd() const { return false; } From 4711c23d9d5b612214689c9711dcd7a8709128fe Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 10 May 2016 14:34:12 -0700 Subject: [PATCH 018/264] Delay AvatarInputs instantiation to first paint --- interface/src/Application.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bce0b77096..4ae206795d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2619,6 +2619,9 @@ void Application::idle() { firstIdle = false; connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); + } else { + // FIXME: AvatarInputs are positioned incorrectly if instantiated before the first paint + AvatarInputs::getInstance()->update(); } PROFILE_RANGE(__FUNCTION__); @@ -2641,7 +2644,6 @@ void Application::idle() { checkChangeCursor(); Stats::getInstance()->updateStats(); - AvatarInputs::getInstance()->update(); _simCounter.increment(); From de4c9530c9d81953f517f9c4cb3fc5185cd37684 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 14:20:58 -0700 Subject: [PATCH 019/264] carry clientOnly flag over from properties when addEntity is called --- libraries/entities/src/EntityTree.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 1cd2b4a47b..9d9c0fdba9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -311,7 +311,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer result = NULL; - if (getIsClient()) { + bool clientOnly = properties.getClientOnly(); + + if (!clientOnly && getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. auto nodeList = DependencyManager::get(); if (nodeList && !nodeList->getThisNodeCanRez()) { @@ -337,6 +339,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = properties.getType(); result = EntityTypes::constructEntityItem(type, entityID, properties); + result->setClientOnly(clientOnly); if (result) { if (recordCreationTime) { From 1e849956c9ed74dd9156e44f94173b2a4bda9263 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 14:47:41 -0700 Subject: [PATCH 020/264] get rid of _avatarEntityChangedTime --- interface/src/avatar/Avatar.cpp | 5 +---- libraries/avatars/src/AvatarData.h | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 67f8d9c967..d3664b9f20 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -183,10 +183,8 @@ void Avatar::updateAvatarEntities() { // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... - quint64 now = usecTimestampNow(); - const static quint64 refreshTime = 3 * USECS_PER_SECOND; - if (!_avatarEntityDataChanged && now - _avatarEntityChangedTime < refreshTime) { + if (!_avatarEntityDataChanged) { return; } @@ -247,7 +245,6 @@ void Avatar::updateAvatarEntities() { if (success) { setAvatarEntityDataChanged(false); - _avatarEntityChangedTime = now; } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 72d34af9d9..2242860e22 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -423,7 +423,6 @@ protected: AvatarEntityMap _avatarEntityData; bool _avatarEntityDataLocallyEdited { false }; bool _avatarEntityDataChanged { false }; - quint64 _avatarEntityChangedTime { 0 }; private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); From 4b13fd969e0609e9abc5ac8e08daf92835f19e7e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 15:37:54 -0700 Subject: [PATCH 021/264] split code that sends edits via avatar-mixer out of queueEditEntityMessage --- interface/src/avatar/Avatar.cpp | 1 - .../entities/src/EntityEditPacketSender.cpp | 78 +++++++++++-------- .../entities/src/EntityEditPacketSender.h | 4 + 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index d3664b9f20..80e7aaa8a7 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -183,7 +183,6 @@ void Avatar::updateAvatarEntities() { // - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... - const static quint64 refreshTime = 3 * USECS_PER_SECOND; if (!_avatarEntityDataChanged) { return; } diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index ea86d3d542..416d3c971e 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -36,6 +36,51 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } +void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, + EntityTreePointer entityTree, + EntityItemID entityItemID, + const EntityItemProperties& properties) { + if (!_shouldSend) { + return; // bail early + } + + assert(properties.getClientOnly()); + + // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server + assert(_myAvatar); + + if (!entityTree) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + return; + } + EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); + if (!entity) { + qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; + return; + } + + // the properties that get serialized into the avatar identity packet should be the entire set + // rather than just the ones being edited. + entity->setProperties(properties); + EntityItemProperties entityProperties = entity->getProperties(); + + QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); + QVariant variantProperties = scriptProperties.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + jsonProperties = QJsonDocument(jsonObject); + + QByteArray binaryProperties = jsonProperties.toBinaryData(); + _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + return; +} + + void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, @@ -43,38 +88,9 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, if (!_shouldSend) { return; // bail early } + if (properties.getClientOnly()) { - // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server - assert(_myAvatar); - - if (!entityTree) { - qDebug() << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; - return; - } - EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); - if (!entity) { - qDebug() << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; - return; - } - - // the properties that get serialized into the avatar identity packet should be the entire set - // rather than just the ones being edited. - entity->setProperties(properties); - EntityItemProperties entityProperties = entity->getProperties(); - - QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); - QVariant variantProperties = scriptProperties.toVariant(); - QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); - - // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar - QJsonObject jsonObject = jsonProperties.object(); - if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { - jsonObject["parentID"] = AVATAR_SELF_ID.toString(); - } - jsonProperties = QJsonDocument(jsonObject); - - QByteArray binaryProperties = jsonProperties.toBinaryData(); - _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); return; } diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 90c6cb988d..9366fc9329 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -27,6 +27,10 @@ public: AvatarData* getMyAvatar() { return _myAvatar; } void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); } + void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, + EntityItemID entityItemID, const EntityItemProperties& properties); + + /// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. From b05ab1b17e219186e2c8c64d122b64e41b9ca342 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 15:59:25 -0700 Subject: [PATCH 022/264] set simulationOwner to be the same as the owningAvatar --- interface/src/avatar/Avatar.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 80e7aaa8a7..cbe69185af 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -214,6 +214,10 @@ void Avatar::updateAvatarEntities() { properties.setClientOnly(true); properties.setOwningAvatarID(getID()); + // there's not entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), 129); + if (properties.getParentID() == AVATAR_SELF_ID) { properties.setParentID(getID()); } From 144715f00cf7b8bb33ed551d9f57e4fed5859051 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 16:05:40 -0700 Subject: [PATCH 023/264] don't make changes to other avatar's avatarEntities --- libraries/entities/src/EntityScriptingInterface.cpp | 6 ++++++ libraries/physics/src/EntityMotionState.cpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e7b386e544..b160a23ced 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -284,6 +284,12 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& return; } + auto nodeList = DependencyManager::get(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + // don't edit other avatar's avatarEntities + return; + } + if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 070bf81e3a..f1897275ed 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -404,6 +404,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { assert(_body); assert(entityTreeIsLocked()); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // don't send updates for someone else's avatarEntities + return false; + } + if (_entity->actionDataNeedsTransmit()) { return true; } From dd6a4dd091b87262716db6ece18dc8a8dd77f665 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 10 May 2016 16:06:33 -0700 Subject: [PATCH 024/264] Make getPaintDelayUsecs threadsafe --- libraries/plugins/src/plugins/DisplayPlugin.cpp | 6 +++++- libraries/plugins/src/plugins/DisplayPlugin.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index f52910b952..430da00061 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -25,13 +25,17 @@ void DisplayPlugin::deactivate() { } int64_t DisplayPlugin::getPaintDelayUsecs() const { + std::lock_guard lock(_paintDelayMutex); return _paintDelayTimer.isValid() ? _paintDelayTimer.nsecsElapsed() / NSECS_PER_USEC : 0; } void DisplayPlugin::incrementPresentCount() { #ifdef DEBUG_PAINT_DELAY // Avoid overhead if we are not debugging - _paintDelayTimer.start(); + { + std::lock_guard lock(_paintDelayMutex); + _paintDelayTimer.start(); + } #endif ++_presentedFrameIndex; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 1e9b16eeac..3a668f27d7 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -161,7 +161,7 @@ public: // Rate at which rendered frames are being skipped virtual float droppedFrameRate() const { return -1.0f; } uint32_t presentCount() const { return _presentedFrameIndex; } - // Time since last call to incrementPresentCount. Only valid if DEBUG_PAINT_DELAY is defined + // Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined) int64_t getPaintDelayUsecs() const; virtual void cycleDebugOutput() {} @@ -176,6 +176,7 @@ protected: private: std::atomic _presentedFrameIndex; + mutable std::mutex _paintDelayMutex; QElapsedTimer _paintDelayTimer; }; From 2d4fd783bd7f808dc9c718092f45b8b25c0de327 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 10 May 2016 16:09:04 -0700 Subject: [PATCH 025/264] Rename Paint/Idle events to Present --- interface/src/Application.cpp | 4 ++-- interface/src/Application.h | 4 ++-- libraries/plugins/src/plugins/DisplayPlugin.cpp | 2 +- libraries/plugins/src/plugins/DisplayPlugin.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4ae206795d..3ba362e56d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1786,9 +1786,9 @@ bool Application::event(QEvent* event) { return false; } - if ((int)event->type() == (int)Idle) { + if ((int)event->type() == (int)Present) { idle(); - removePostedEvents(this, Idle); // clear pending idles so we don't clog the pipes + removePostedEvents(this, Present); // clear pending presents so we don't clog the pipes return true; } else if ((int)event->type() == (int)Paint) { paintGL(); diff --git a/interface/src/Application.h b/interface/src/Application.h index e13dffbcf1..0d88352b2f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -95,8 +95,8 @@ class Application : public QApplication, public AbstractViewStateInterface, publ public: enum Event { - Idle = DisplayPlugin::Paint, - Paint = Idle + 1, + Present = DisplayPlugin::Present, + Paint = Present + 1, Lambda = Paint + 1 }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 430da00061..a217041f4e 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -41,5 +41,5 @@ void DisplayPlugin::incrementPresentCount() { ++_presentedFrameIndex; // Alert the app that it needs to paint a new presentation frame - qApp->postEvent(qApp, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + qApp->postEvent(qApp, new QEvent(static_cast(Present)), Qt::HighEventPriority); } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 3a668f27d7..41f380aa86 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -61,7 +61,7 @@ class DisplayPlugin : public Plugin { using Parent = Plugin; public: enum Event { - Paint = QEvent::User + 1 + Present = QEvent::User + 1 }; bool activate() override; From 356ccdba26cb668e999c4af45dc691f134b17f8f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 16:16:06 -0700 Subject: [PATCH 026/264] debug icon for clientOnly --- .../src/RenderableEntityItem.cpp | 21 ++++++++++++++++--- .../src/RenderableEntityItem.h | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index d148145dde..011675fc82 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -47,6 +47,9 @@ namespace render { } void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status::Getters& statusGetters) { + auto nodeList = DependencyManager::get(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity] () -> render::Item::Status::Value { quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); @@ -81,9 +84,7 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); }); - statusGetters.push_back([entity] () -> render::Item::Status::Value { - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID); bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull(); @@ -106,4 +107,18 @@ void makeEntityItemStatusGetters(EntityItemPointer entity, render::Item::Status: return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); }); + + statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { + if (entity->getClientOnly()) { + if (entity->getOwningAvatarID() == myNodeID) { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } else { + return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + } + } + return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, + (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); + }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 09451e87d4..9840bf3150 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -26,6 +26,7 @@ enum class RenderItemStatusIcon { SIMULATION_OWNER = 3, HAS_ACTIONS = 4, OTHER_SIMULATION_OWNER = 5, + CLIENT_ONLY = 6, NONE = 255 }; From de12680ff1dab61f4eb97b431c2d8e2228194499 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 16:43:27 -0700 Subject: [PATCH 027/264] don't put actions on other people's avatarEntities --- interface/src/avatar/Avatar.cpp | 4 ++++ libraries/entities/src/EntityEditPacketSender.cpp | 4 ++++ libraries/entities/src/EntityScriptingInterface.cpp | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index cbe69185af..83cec224b6 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -187,6 +187,10 @@ void Avatar::updateAvatarEntities() { return; } + if (getID() == QUuid()) { + return; // wait until MyAvatar gets an ID before doing this. + } + EntityTreeRenderer* treeRenderer = qApp->getEntities(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (!entityTree) { diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 416d3c971e..6cb0b6ef60 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -44,6 +44,10 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, return; // bail early } + if (properties.getOwningAvatarID() != _myAvatar->getID()) { + return; // don't send updates for someone else's avatarEntity + } + assert(properties.getClientOnly()); // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index b160a23ced..3b06c5a998 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -788,6 +788,9 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return false; } + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { @@ -803,6 +806,10 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return; } + if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + return; + } + doTransmit = actor(simulation, entity); if (doTransmit) { _entityTree->entityChanged(entity); From e4e0be8fa3ba0baf38eaa5b5a0588c7a9f630dc7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 16:51:38 -0700 Subject: [PATCH 028/264] relay owningAvatar from properties to entity on entity creation --- libraries/entities/src/EntityTree.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9d9c0fdba9..2da3604c2a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -312,6 +312,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti EntityItemPointer result = NULL; bool clientOnly = properties.getClientOnly(); + QUuid owningAvatarID = properties.getOwningAvatarID(); if (!clientOnly && getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. @@ -340,6 +341,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti EntityTypes::EntityType type = properties.getType(); result = EntityTypes::constructEntityItem(type, entityID, properties); result->setClientOnly(clientOnly); + result->setOwningAvatarID(owningAvatarID); if (result) { if (recordCreationTime) { From 872f1b0c64d9dc7479dbc1a9de3615263dc21818 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 May 2016 17:48:55 -0700 Subject: [PATCH 029/264] clear avatarEntity data for entities that are deleted --- interface/src/avatar/Avatar.cpp | 5 ++++- interface/src/avatar/MyAvatar.cpp | 1 + libraries/entities/src/EntityEditPacketSender.cpp | 6 ++++++ libraries/entities/src/EntityItem.h | 1 + libraries/entities/src/EntityScriptingInterface.cpp | 10 +++++++++- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 83cec224b6..d959b298ef 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -229,7 +229,10 @@ void Avatar::updateAvatarEntities() { EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - if (!entityTree->updateEntity(entityID, properties)) { + if (entityTree->updateEntity(entityID, properties)) { + entity->markAsChangedOnServer(); + entity->updateLastEditedFromRemote(); + } else { qDebug() << "AVATAR-ENTITES -- updateEntity failed: " << properties.getType(); success = false; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index dc0d6d7e97..304aabdcc6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -828,6 +828,7 @@ void MyAvatar::loadData() { for (int i = 0; i < avatarEntityCount; i++) { settings.setArrayIndex(i); QUuid entityID = settings.value("id").toUuid(); + // QUuid entityID = QUuid::createUuid(); // generate a new ID QByteArray properties = settings.value("properties").toByteArray(); updateAvatarEntity(entityID, properties); } diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 6cb0b6ef60..28f1871346 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -81,6 +81,8 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, QByteArray binaryProperties = jsonProperties.toBinaryData(); _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); + + entity->setLastBroadcast(usecTimestampNow()); return; } @@ -115,6 +117,10 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI return; // bail early } + // in case this was a clientOnly entity: + assert(_myAvatar); + _myAvatar->clearAvatarEntity(entityItemID); + QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0); if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4286a9d6ae..c2e497e602 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -375,6 +375,7 @@ public: glm::vec3 entityToWorld(const glm::vec3& point) const; quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } + void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); } void getAllTerseUpdateProperties(EntityItemProperties& properties) const; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 3b06c5a998..ade8ade1c4 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -401,6 +401,14 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { + // don't delete other avatar's avatarEntities + shouldDelete = false; + return; + } + auto dimensions = entity->getDimensions(); float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); @@ -806,7 +814,7 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, return; } - if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) { + if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) { return; } From ac506dc4b77de0bd8831796de3ffe6fad855a6c9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 11 May 2016 12:46:16 -0700 Subject: [PATCH 030/264] where needed, copy clientOnly into properties before calling queueEditEntityMessage --- interface/src/avatar/MyAvatar.cpp | 1 + libraries/entities/src/EntityItem.cpp | 11 ++++++++++ .../entities/src/EntityItemProperties.cpp | 20 +++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 15 ++++++-------- libraries/entities/src/EntityPropertyFlags.h | 3 +++ .../entities/src/EntityScriptingInterface.cpp | 5 ++++- libraries/entities/src/EntityTree.cpp | 3 --- libraries/physics/src/EntityMotionState.cpp | 7 +++++++ 8 files changed, 52 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 304aabdcc6..33905d5e81 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -446,6 +446,7 @@ void MyAvatar::simulate(float deltaTime) { EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); properties.setLastEdited(now); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 187c4f51be..04307892f1 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -138,6 +138,9 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_PARENT_JOINT_INDEX; requestedProperties += PROP_QUERY_AA_CUBE; + requestedProperties += PROP_CLIENT_ONLY; + requestedProperties += PROP_OWNING_AVATAR_ID; + return requestedProperties; } @@ -1094,6 +1097,8 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper properties._id = getID(); properties._idSet = true; properties._created = _created; + properties.setClientOnly(_clientOnly); + properties.setOwningAvatarID(_owningAvatarID); properties._type = getType(); @@ -1135,6 +1140,9 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + properties._defaultSettings = false; return properties; @@ -1225,6 +1233,9 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + AACube saveQueryAACube = _queryAACube; checkAndAdjustQueryAACube(); if (saveQueryAACube != _queryAACube) { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 738e8910fe..354cbd03ef 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -309,6 +309,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); + CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); + CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); @@ -541,6 +543,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + + properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly())); + properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID())); + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -679,6 +687,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); + COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); + _lastEdited = usecTimestampNow(); } @@ -1564,6 +1575,9 @@ void EntityItemProperties::markAllChanged() { _jointTranslationsChanged = true; _queryAACubeChanged = true; + + _clientOnlyChanged = true; + _owningAvatarIDChanged = true; } // The minimum bounding box for the entity. @@ -1884,6 +1898,12 @@ QList EntityItemProperties::listChangedProperties() { if (queryAACubeChanged()) { out += "queryAACube"; } + if (clientOnlyChanged()) { + out += "clientOnly"; + } + if (owningAvatarIDChanged()) { + out += "owningAvatarID"; + } getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index fb24e711f4..fb36834238 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -205,6 +205,9 @@ public: DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector, QVector()); + DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false); + DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); + static QString getBackgroundModeString(BackgroundMode mode); @@ -276,12 +279,6 @@ public: void setJointRotationsDirty() { _jointRotationsSetChanged = true; _jointRotationsChanged = true; } void setJointTranslationsDirty() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; } - bool getClientOnly() const { return _clientOnly; } - void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; } - // if this entity is client-only, which avatar is it associated with? - QUuid getOwningAvatarID() const { return _owningAvatarID; } - void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } - protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); @@ -308,9 +305,6 @@ private: glm::vec3 _naturalPosition; EntityPropertyFlags _desiredProperties; // if set will narrow scopes of copy/to/from to just these properties - - bool _clientOnly { false }; - QUuid _owningAvatarID; }; Q_DECLARE_METATYPE(EntityItemProperties); @@ -430,6 +424,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslationsSet, jointTranslationsSet, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); + properties.getAnimation().debugDump(); properties.getSkybox().debugDump(); properties.getStage().debugDump(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90a7c1e2f7..394f61b5e8 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -169,6 +169,9 @@ enum EntityPropertyList { PROP_FALLOFF_RADIUS, // for Light entity + PROP_CLIENT_ONLY, // doesn't go over wire + PROP_OWNING_AVATAR_ID, // doesn't go over wire + //////////////////////////////////////////////////////////////////////////////////////////////////// // 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 ade8ade1c4..15c2bffd80 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -799,6 +799,8 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); + EntityItemProperties properties; + EntityItemPointer entity; bool doTransmit = false; _entityTree->withWriteLock([&] { @@ -820,13 +822,14 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID, doTransmit = actor(simulation, entity); if (doTransmit) { + properties.setClientOnly(entity->getClientOnly()); + properties.setOwningAvatarID(entity->getOwningAvatarID()); _entityTree->entityChanged(entity); } }); // transmit the change if (doTransmit) { - EntityItemProperties properties; _entityTree->withReadLock([&] { properties = entity->getProperties(); }); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2da3604c2a..087225c865 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -312,7 +312,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti EntityItemPointer result = NULL; bool clientOnly = properties.getClientOnly(); - QUuid owningAvatarID = properties.getOwningAvatarID(); if (!clientOnly && getIsClient()) { // if our Node isn't allowed to create entities in this domain, don't try. @@ -340,8 +339,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = properties.getType(); result = EntityTypes::constructEntityItem(type, entityID, properties); - result->setClientOnly(clientOnly); - result->setOwningAvatarID(owningAvatarID); if (result) { if (recordCreationTime) { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index f1897275ed..053bfcbd85 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -555,6 +555,9 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); _entity->setLastBroadcast(now); @@ -567,6 +570,10 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); + + newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); + newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(now); From 73c06b3bf42524d5d1898f60ef2be4d059a936ee Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 11 May 2016 14:13:08 -0700 Subject: [PATCH 031/264] updating attachedEntitiesManager to work with new system --- scripts/system/assets/images/lock.svg | 70 +++++++++++++++++++ scripts/system/assets/images/unlock.svg | 70 +++++++++++++++++++ scripts/system/attachedEntitiesManager.js | 84 ++++++++++------------- scripts/system/libraries/toolBars.js | 14 +++- 4 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 scripts/system/assets/images/lock.svg create mode 100644 scripts/system/assets/images/unlock.svg diff --git a/scripts/system/assets/images/lock.svg b/scripts/system/assets/images/lock.svg new file mode 100644 index 0000000000..1480359f43 --- /dev/null +++ b/scripts/system/assets/images/lock.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/assets/images/unlock.svg b/scripts/system/assets/images/unlock.svg new file mode 100644 index 0000000000..a7664c1f59 --- /dev/null +++ b/scripts/system/assets/images/unlock.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/scripts/system/attachedEntitiesManager.js b/scripts/system/attachedEntitiesManager.js index 9ddb040297..124b2f76ec 100644 --- a/scripts/system/attachedEntitiesManager.js +++ b/scripts/system/attachedEntitiesManager.js @@ -22,7 +22,7 @@ var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; var DRESSING_ROOM_DISTANCE = 2.0; -var SHOW_TOOL_BAR = false; +var SHOW_TOOL_BAR = true; // tool bar @@ -30,34 +30,20 @@ if (SHOW_TOOL_BAR) { var BUTTON_SIZE = 32; var PADDING = 3; Script.include(["libraries/toolBars.js"]); - var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) { - return { - x: (BUTTON_SIZE + PADDING), - y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING) - }; - }); - var saveButton = toolBar.addOverlay("image", { + + var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.attachedEntities.toolbar"); + var lockButton = toolBar.addTool({ width: BUTTON_SIZE, height: BUTTON_SIZE, - imageURL: ".../save.png", + imageURL: Script.resolvePath("assets/images/lock.svg"), color: { red: 255, green: 255, blue: 255 }, - alpha: 1 - }); - var loadButton = toolBar.addOverlay("image", { - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: ".../load.png", - color: { - red: 255, - green: 255, - blue: 255 - }, - alpha: 1 - }); + alpha: 1, + visible: true + }, false); } @@ -67,10 +53,8 @@ function mousePressEvent(event) { y: event.y }); - if (clickedOverlay == saveButton) { - manager.saveAttachedEntities(); - } else if (clickedOverlay == loadButton) { - manager.loadAttachedEntities(); + if (lockButton === toolBar.clicked(clickedOverlay)) { + manager.toggleLocked(); } } @@ -92,6 +76,8 @@ Script.scriptEnding.connect(scriptEnding); function AttachedEntitiesManager() { + var clothingLocked = true; + this.subscribeToMessages = function() { Messages.subscribe('Hifi-Object-Manipulation'); Messages.messageReceived.connect(this.handleWearableMessages); @@ -128,19 +114,6 @@ function AttachedEntitiesManager() { } } - this.avatarIsInDressingRoom = function() { - // return true if MyAvatar is near the dressing room - var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE); - for (i = 0; i < possibleDressingRoom.length; i++) { - var entityID = possibleDressingRoom[i]; - var props = Entities.getEntityProperties(entityID); - if (props.name == 'Hifi-Dressing-Room-Base') { - return true; - } - } - return false; - } - this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { // if this is still equipped, just rewrite the position information. var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); @@ -179,21 +152,23 @@ function AttachedEntitiesManager() { } if (bestJointIndex != -1) { - var wearProps = { - parentID: MyAvatar.sessionUUID, - parentJointIndex: bestJointIndex - }; + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = MyAvatar.sessionUUID; + wearProps.parentJointIndex = bestJointIndex; if (bestJointOffset && bestJointOffset.constructor === Array) { - if (this.avatarIsInDressingRoom() || bestJointOffset.length < 2) { + if (!clothingLocked || bestJointOffset.length < 2) { this.updateRelativeOffsets(grabbedEntity); } else { - // don't snap the entity to the preferred position if the avatar is in the dressing room. + // don't snap the entity to the preferred position if unlocked wearProps.localPosition = bestJointOffset[0]; wearProps.localRotation = bestJointOffset[1]; } } - Entities.editEntity(grabbedEntity, wearProps); + + // Entities.editEntity(grabbedEntity, wearProps); + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps, true); } else if (props.parentID != NULL_UUID) { // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. if (props.parentID === MyAvatar.sessionUUID && @@ -201,7 +176,11 @@ function AttachedEntitiesManager() { props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) { // this is equipped on a hand -- don't clear the parent. } else { - Entities.editEntity(grabbedEntity, { parentID: NULL_UUID }); + var wearProps = Entities.getEntityProperties(grabbedEntity); + wearProps.parentID = NULL_UUID; + wearProps.parentJointIndex = -1; + Entities.deleteEntity(grabbedEntity); + Entities.addEntity(wearProps, false); } } } @@ -221,6 +200,17 @@ function AttachedEntitiesManager() { return false; } + this.toggleLocked = function() { + print("toggleLocked"); + if (clothingLocked) { + clothingLocked = false; + toolBar.setImageURL(Script.resolvePath("assets/images/unlock.svg"), lockButton); + } else { + clothingLocked = true; + toolBar.setImageURL(Script.resolvePath("assets/images/lock.svg"), lockButton); + } + } + this.saveAttachedEntities = function() { print("--- saving attached entities ---"); saveData = []; diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index d97575d349..9efe533457 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -56,6 +56,10 @@ Overlay2D = function(properties, overlay) { // overlay is an optional variable properties.alpha = alpha; Overlays.editOverlay(overlay, { alpha: alpha }); } + this.setImageURL = function(imageURL) { + properties.imageURL = imageURL; + Overlays.editOverlay(overlay, { imageURL: imageURL }); + } this.show = function(doShow) { properties.visible = doShow; Overlays.editOverlay(overlay, { visible: doShow }); @@ -254,7 +258,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } this.save(); } - + this.setAlpha = function(alpha, tool) { if(typeof(tool) === 'undefined') { for(var tool in this.tools) { @@ -268,7 +272,11 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit this.tools[tool].setAlpha(alpha); } } - + + this.setImageURL = function(imageURL, tool) { + this.tools[tool].setImageURL(imageURL); + } + this.setBack = function(color, alpha) { if (color == null) { Overlays.editOverlay(this.back, { visible: false }); @@ -478,4 +486,4 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit } ToolBar.SPACING = 6; ToolBar.VERTICAL = 0; -ToolBar.HORIZONTAL = 1; \ No newline at end of file +ToolBar.HORIZONTAL = 1; From 57fae3fc5121a979bb836cafccd1fd643a18a234 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 May 2016 09:28:25 +1200 Subject: [PATCH 032/264] Add FiraSans-Regular font --- .../resources/fonts/FiraSans-Regular.ttf | Bin 0 -> 403924 bytes .../qml/styles-uit/FiraSansRegular.qml | 23 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 interface/resources/fonts/FiraSans-Regular.ttf create mode 100644 interface/resources/qml/styles-uit/FiraSansRegular.qml diff --git a/interface/resources/fonts/FiraSans-Regular.ttf b/interface/resources/fonts/FiraSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d9fdc0e922030c6836a9ad5f808fd7e1c5970873 GIT binary patch literal 403924 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 r-(pXe}H&MjC7NAiF2}5 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 War7s!{wz%rN_!OQ*CO@X^#2EvSW`a$ literal 0 HcmV?d00001 diff --git a/interface/resources/qml/styles-uit/FiraSansRegular.qml b/interface/resources/qml/styles-uit/FiraSansRegular.qml new file mode 100644 index 0000000000..4ae0826772 --- /dev/null +++ b/interface/resources/qml/styles-uit/FiraSansRegular.qml @@ -0,0 +1,23 @@ +// +// FiraSansRegular.qml +// +// Created by David Rowe on 12 May 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +Text { + id: root + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + property real size: 32 + font.pixelSize: size + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.family: firaSansRegular.name +} From 032fbd9a55b6ff220c7ac0e17f989ded353bae6d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 11 May 2016 16:41:00 -0700 Subject: [PATCH 033/264] taking something off now makes it an entity-server-aware entity --- scripts/system/attachedEntitiesManager.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/system/attachedEntitiesManager.js b/scripts/system/attachedEntitiesManager.js index 124b2f76ec..e0261b3c7a 100644 --- a/scripts/system/attachedEntitiesManager.js +++ b/scripts/system/attachedEntitiesManager.js @@ -166,7 +166,6 @@ function AttachedEntitiesManager() { } } - // Entities.editEntity(grabbedEntity, wearProps); Entities.deleteEntity(grabbedEntity); Entities.addEntity(wearProps, true); } else if (props.parentID != NULL_UUID) { @@ -179,8 +178,23 @@ function AttachedEntitiesManager() { var wearProps = Entities.getEntityProperties(grabbedEntity); wearProps.parentID = NULL_UUID; wearProps.parentJointIndex = -1; + + delete wearProps.id; + delete wearProps.created; + delete wearProps.age; + delete wearProps.ageAsText; + delete wearProps.naturalDimensions; + delete wearProps.naturalPosition; + delete wearProps.actionData; + delete wearProps.sittingPoints; + delete wearProps.boundingBox; + delete wearProps.clientOnly; + delete wearProps.owningAvatarID; + delete wearProps.localPosition; + delete wearProps.localRotation; + Entities.deleteEntity(grabbedEntity); - Entities.addEntity(wearProps, false); + Entities.addEntity(wearProps); } } } @@ -271,7 +285,7 @@ function AttachedEntitiesManager() { this.scrubProperties(savedProps); delete savedProps["id"]; savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions - var loadedEntityID = Entities.addEntity(savedProps); + var loadedEntityID = Entities.addEntity(savedProps, true); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'loaded', From 25deaf5bda523ef53c574e997dae4b515be42931 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 11 May 2016 18:27:32 -0700 Subject: [PATCH 034/264] Repost missed Presents with low priority --- interface/src/Application.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3ba362e56d..3599303239 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1786,14 +1786,29 @@ bool Application::event(QEvent* event) { return false; } + static bool justPresented = false; if ((int)event->type() == (int)Present) { + if (justPresented) { + justPresented = false; + + // If presentation is hogging the main thread, repost as low priority to avoid hanging the GUI. + // This has the effect of allowing presentation to exceed the paint budget by X times and + // only dropping every (1/X) frames, instead of every ceil(X) frames. + // (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS). + removePostedEvents(this, Present); + postEvent(this, new QEvent(static_cast(Present)), Qt::LowEventPriority); + return true; + } + idle(); - removePostedEvents(this, Present); // clear pending presents so we don't clog the pipes return true; } else if ((int)event->type() == (int)Paint) { + justPresented = true; paintGL(); return true; - } else if ((int)event->type() == (int)Lambda) { + } + + if ((int)event->type() == (int)Lambda) { static_cast(event)->call(); return true; } @@ -2605,7 +2620,7 @@ void Application::idle() { _renderedFrameIndex = displayPlugin->presentCount(); // Request a paint ASAP - postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority + 1); // Update the deadlock watchdog updateHeartbeat(); From c784ca9771b9e733d45e25083fdfb43c0376ee8a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 11 May 2016 18:45:56 -0700 Subject: [PATCH 035/264] debugging --- interface/src/avatar/Avatar.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 1e8ff7775e..95a3ef6132 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -197,6 +197,7 @@ void Avatar::updateAvatarEntities() { QScriptEngine scriptEngine; entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); + qDebug() << "---------------"; for (auto entityID : avatarEntities.keys()) { // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties // and either add or update the entity. @@ -206,6 +207,9 @@ void Avatar::updateAvatarEntities() { qDebug() << "got bad avatarEntity json"; continue; } + + qDebug() << jsonProperties.toJson(); + QVariant variantProperties = jsonProperties.toVariant(); QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); @@ -214,7 +218,7 @@ void Avatar::updateAvatarEntities() { properties.setClientOnly(true); properties.setOwningAvatarID(getID()); - // there's not entity-server to tell us we're the simulation owner, so always set the + // there's no entity-server to tell us we're the simulation owner, so always set the // simulationOwner to the owningAvatarID and a high priority. properties.setSimulationOwner(getID(), 129); @@ -225,17 +229,19 @@ void Avatar::updateAvatarEntities() { EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { + qDebug() << "avatar-entities existing entity, element =" << entity->getElement().get(); if (entityTree->updateEntity(entityID, properties)) { - entity->markAsChangedOnServer(); entity->updateLastEditedFromRemote(); + qDebug() << "avatar-entities after entityTree->updateEntity(), element =" << entity->getElement().get(); } else { - qDebug() << "AVATAR-ENTITES -- updateEntity failed: " << properties.getType(); + qDebug() << "AVATAR-ENTITIES -- updateEntity failed: " << properties.getType(); success = false; } } else { + qDebug() << "avatar-entities new entity"; entity = entityTree->addEntity(entityID, properties); if (!entity) { - qDebug() << "AVATAR-ENTITES -- addEntity failed: " << properties.getType(); + qDebug() << "AVATAR-ENTITIES -- addEntity failed: " << properties.getType(); success = false; } } From ce3faa2916a0b9c56ba67940c3013d5203bd1329 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 11 May 2016 18:47:20 -0700 Subject: [PATCH 036/264] fix logic in UpdateEntityOperator --- libraries/entities/src/UpdateEntityOperator.cpp | 7 ++----- libraries/entities/src/UpdateEntityOperator.h | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 94599496b0..cfd7498cdf 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -23,7 +23,6 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, _foundOld(false), _foundNew(false), _removeOld(false), - _dontMove(false), // assume we'll be moving _changeTime(usecTimestampNow()), _oldEntityCube(), _newEntityCube(), @@ -46,12 +45,11 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, // If our new properties don't have bounds details (no change to position, etc) or if this containing element would // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update - bool oldElementBestFit = _containingElement->bestFitBounds(newQueryAACube); + bool oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. if (!oldElementBestFit) { - _newEntityCube = _oldEntityCube; _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { @@ -59,14 +57,13 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, } } else { _foundOld = true; - _newEntityCube = _oldEntityCube; - _dontMove = true; if (_wantDebug) { qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit; } } + _newEntityCube = newQueryAACube; _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds if (_wantDebug) { diff --git a/libraries/entities/src/UpdateEntityOperator.h b/libraries/entities/src/UpdateEntityOperator.h index ea6faabe3e..8b5bbdf135 100644 --- a/libraries/entities/src/UpdateEntityOperator.h +++ b/libraries/entities/src/UpdateEntityOperator.h @@ -37,7 +37,6 @@ private: bool _foundOld; bool _foundNew; bool _removeOld; - bool _dontMove; quint64 _changeTime; AACube _oldEntityCube; From c29b82e24de6a0183c3cd838f01e03f9720ba877 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 May 2016 17:08:28 +1200 Subject: [PATCH 037/264] Restyle file selection dialog First pass --- .../qml/controls-uit/AttachmentsTable.qml | 7 +- .../resources/qml/controls-uit/Table.qml | 163 +++++++----------- .../resources/qml/dialogs/FileDialog.qml | 76 +++++++- .../qml/dialogs/fileDialog/FileTableView.qml | 79 --------- .../qml/styles-uit/HifiConstants.qml | 19 +- 5 files changed, 155 insertions(+), 189 deletions(-) delete mode 100644 interface/resources/qml/dialogs/fileDialog/FileTableView.qml diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml index 6022de7f93..ce93b8f4df 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -21,7 +21,6 @@ import "../hifi/models" TableView { id: tableView - // property var tableModel: ListModel { } property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light @@ -46,7 +45,7 @@ TableView { RalewayRegular { id: textHeader - size: hifi.fontSizes.tableText + size: hifi.fontSizes.tableHeading color: hifi.colors.lightGrayText text: styleData.value anchors { @@ -87,7 +86,7 @@ TableView { bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: hifi.colors.tableScrollHandleDark } } @@ -107,7 +106,7 @@ TableView { margins: 1 // Shrink } radius: 4 - color: hifi.colors.tableScrollBackground + color: hifi.colors.tableScrollBackgroundDark } } diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index de6950c07e..2a0fe545ef 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -17,20 +17,54 @@ import "../styles-uit" TableView { id: tableView - property var tableModel: ListModel { } property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + property bool expandSelectedRow: false - model: tableModel - - TableViewColumn { - role: "name" - } - - anchors { left: parent.left; right: parent.right } + model: ListModel { } headerVisible: false - headerDelegate: Item { } // Fix OSX QML bug that displays scrollbar starting too low. + headerDelegate: Rectangle { + height: hifi.dimensions.tableHeaderHeight + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + + RalewayRegular { + text: styleData.value + size: hifi.fontSizes.tableHeading + font.capitalization: Font.AllUppercase + color: hifi.colors.baseGrayHighlight + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + } + + Rectangle { + width: 1 + anchors { + left: parent.left + top: parent.top + topMargin: 1 + bottom: parent.bottom + bottomMargin: 2 + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: styleData.column > 0 + } + + Rectangle { + height: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + } + } // Use rectangle to draw border with rounded corners. frameVisible: false @@ -50,8 +84,10 @@ TableView { style: TableViewStyle { // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightEven : hifi.colors.tableRowDarkEven - alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + + padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 handle: Item { id: scrollbarHandle @@ -59,33 +95,38 @@ TableView { Rectangle { anchors { fill: parent + topMargin: 3 + bottomMargin: 3 // "" leftMargin: 2 // Move it right rightMargin: -2 // "" - topMargin: 3 // Shrink vertically - bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark } } scrollBarBackground: Item { - implicitWidth: 10 + implicitWidth: 9 Rectangle { anchors { fill: parent margins: -1 // Expand + topMargin: headerVisible ? -hifi.dimensions.tableHeaderHeight : -1 } - color: hifi.colors.baseGrayHighlight - } + color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - Rectangle { - anchors { - fill: parent - margins: 1 // Shrink + Rectangle { + // Extend header bottom border + anchors { + top: parent.top + topMargin: hifi.dimensions.tableHeaderHeight - 1 + left: parent.left + right: parent.right + } + height: 1 + color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight + visible: headerVisible } - radius: 4 - color: hifi.colors.tableScrollBackground } } @@ -99,85 +140,11 @@ TableView { } rowDelegate: Rectangle { - height: (styleData.selected ? 1.8 : 1) * hifi.dimensions.tableRowHeight + height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight color: styleData.selected ? hifi.colors.primaryHighlight : tableView.isLightColorScheme ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) } - - itemDelegate: Item { - anchors { - left: parent ? parent.left : undefined - leftMargin: hifi.dimensions.tablePadding - right: parent ? parent.right : undefined - rightMargin: hifi.dimensions.tablePadding - } - - FiraSansSemiBold { - id: textItem - text: styleData.value - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: 3 - } - - // FIXME: Put reload item in tableModel passed in from RunningScripts. - HiFiGlyphs { - id: reloadButton - text: hifi.glyphs.reloadSmall - color: reloadButtonArea.pressed ? hifi.colors.white : parent.color - anchors { - top: parent.top - right: stopButton.left - verticalCenter: parent.verticalCenter - } - MouseArea { - id: reloadButtonArea - anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) - } - } - - // FIXME: Put stop item in tableModel passed in from RunningScripts. - HiFiGlyphs { - id: stopButton - text: hifi.glyphs.closeSmall - color: stopButtonArea.pressed ? hifi.colors.white : parent.color - anchors { - top: parent.top - right: parent.right - verticalCenter: parent.verticalCenter - } - MouseArea { - id: stopButtonArea - anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) - } - } - } - - // FIXME: Automatically use aux. information from tableModel - FiraSansSemiBold { - text: tableModel.get(styleData.row) ? tableModel.get(styleData.row).url : "" - elide: Text.ElideMiddle - size: hifi.fontSizes.tableText - color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - top: textItem.bottom - left: parent.left - right: parent.right - } - visible: styleData.selected - } - } } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 1f710b4ef5..ac0858c134 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -28,6 +28,7 @@ ModalWindow { //resizable: true implicitWidth: 640 implicitHeight: 480 + HifiConstants { id: hifi } Settings { @@ -184,8 +185,9 @@ ModalWindow { } } - FileTableView { + Table { id: fileTableView + colorScheme: hifi.colorSchemes.light anchors { top: navControls.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -194,10 +196,12 @@ ModalWindow { bottom: currentSelection.top bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } + headerVisible: true onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); + model: FolderListModel { id: model nameFilters: selectionType.currentFilter @@ -218,6 +222,76 @@ ModalWindow { } } + onActiveFocusChanged: { + if (activeFocus && currentRow == -1) { + fileTableView.selection.select(0) + } + } + + itemDelegate: Item { + clip: true + + FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; } + FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; } + + FiraSansSemiBold { + text: getText(); + elide: styleData.elideMode + anchors { + left: parent.left + leftMargin: hifi.dimensions.tablePadding + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: parent.verticalCenter + } + size: hifi.fontSizes.tableText + color: hifi.colors.baseGrayHighlight + font.family: fileTableView.model.get(styleData.row, "fileIsDir") ? firaSansSemiBold.name : firaSansRegular.name + + function getText() { + switch (styleData.column) { + case 1: return fileTableView.model.get(styleData.row, "fileIsDir") ? "" : styleData.value; + case 2: return fileTableView.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value); + default: return styleData.value; + } + } + function formatSize(size) { + var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; + var suffixIndex = 0 + while ((size / 1024.0) > 1.1) { + size /= 1024.0; + ++suffixIndex; + } + + size = Math.round(size*1000)/1000; + size = size.toLocaleString() + + return size + " " + suffixes[suffixIndex]; + } + } + } + + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: "Name" + width: 0.5 * fileTableView.width + resizable: true + } + TableViewColumn { + id: fileMofifiedColumn + role: "fileModified" + title: "Date" + width: 0.3 * fileTableView.width + resizable: true + } + TableViewColumn { + role: "fileSize" + title: "Size" + width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width + resizable: true + } + function navigateToRow(row) { currentRow = row; navigateToCurrentRow(); diff --git a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml b/interface/resources/qml/dialogs/fileDialog/FileTableView.qml deleted file mode 100644 index d51f9e1ed9..0000000000 --- a/interface/resources/qml/dialogs/fileDialog/FileTableView.qml +++ /dev/null @@ -1,79 +0,0 @@ -// -// FileDialog.qml -// -// Created by Bradley Austin Davis on 22 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -TableView { - id: root - onActiveFocusChanged: { - if (activeFocus && currentRow == -1) { - root.selection.select(0) - } - } - //horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - itemDelegate: Component { - Item { - clip: true - Text { - x: 3 - anchors.verticalCenter: parent.verticalCenter - color: styleData.textColor - elide: styleData.elideMode - text: getText(); - font.italic: root.model.get(styleData.row, "fileIsDir") ? true : false - - function getText() { - switch (styleData.column) { - //case 1: return Date.fromLocaleString(locale, styleData.value, "yyyy-MM-dd hh:mm:ss"); - case 1: return root.model.get(styleData.row, "fileIsDir") ? "" : styleData.value; - case 2: return root.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value); - default: return styleData.value; - } - } - function formatSize(size) { - var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; - var suffixIndex = 0 - while ((size / 1024.0) > 1.1) { - size /= 1024.0; - ++suffixIndex; - } - - size = Math.round(size*1000)/1000; - size = size.toLocaleString() - - return size + " " + suffixes[suffixIndex]; - } - } - } - } - - TableViewColumn { - id: fileNameColumn - role: "fileName" - title: "Name" - width: Math.floor(0.55 * parent.width) - resizable: true - } - TableViewColumn { - id: fileMofifiedColumn - role: "fileModified" - title: "Date" - width: Math.floor(0.3 * parent.width) - resizable: true - } - TableViewColumn { - role: "fileSize" - title: "Size" - width: Math.floor(0.15 * parent.width) - 16 - 2 // Allow space for vertical scrollbar and borders - resizable: true - } -} diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index cb4d2157ca..4d5052f086 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -89,12 +89,16 @@ Item { readonly property color transparent: "#00ffffff" // Control specific colors - readonly property color tableRowLightOdd: white50 - readonly property color tableRowLightEven: "#1a575757" - readonly property color tableRowDarkOdd: "#80393939" - readonly property color tableRowDarkEven: "#a6181818" - readonly property color tableScrollHandle: "#707070" - readonly property color tableScrollBackground: "#323232" + readonly property color tableRowLightOdd: "#eaeaea" // Equivalent to white50 over #e3e3e3 background + readonly property color tableRowLightEven: "#c6c6c6" // Equivavlent to "#1a575757" over #e3e3e3 background + readonly property color tableRowDarkOdd: "#2e2e2e" // Equivalent to "#80393939" over #404040 background + readonly property color tableRowDarkEven: "#1c1c1c" // Equivalent to "#a6181818" over #404040 background + readonly property color tableBackgroundLight: tableRowLightEven + readonly property color tableBackgroundDark: tableRowDarkEven + readonly property color tableScrollHandleLight: tableRowLightOdd + readonly property color tableScrollHandleDark: "#707070" + readonly property color tableScrollBackgroundLight: tableRowLightEven + readonly property color tableScrollBackgroundDark: "#323232" readonly property color checkboxLightStart: "#ffffff" readonly property color checkboxLightFinish: "#afafaf" readonly property color checkboxDarkStart: "#7d7d7d" @@ -140,7 +144,7 @@ Item { readonly property real spinnerSize: 42 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 - readonly property real tableHeaderHeight: 40 + readonly property real tableHeaderHeight: 29 readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30) readonly property real modalDialogTitleHeight: 40 readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor @@ -157,6 +161,7 @@ Item { readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12 readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 + readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 readonly property real tableText: dimensions.largeScreen ? 15 : 12 readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9 readonly property real iconButton: dimensions.largeScreen ? 13 : 9 From b6fcb77d6f76306ad2a4022c2430b7656e9583fc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 May 2016 17:16:56 +1200 Subject: [PATCH 038/264] Update Running Scripts list to use updated QML table control --- .../qml/hifi/dialogs/RunningScripts.qml | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 071789fe16..31bb553809 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -118,11 +118,89 @@ Window { } HifiControls.Table { - tableModel: runningScriptsModel + model: runningScriptsModel + id: table height: 185 colorScheme: hifi.colorSchemes.dark anchors.left: parent.left anchors.right: parent.right + expandSelectedRow: true + + itemDelegate: Item { + anchors { + left: parent ? parent.left : undefined + leftMargin: hifi.dimensions.tablePadding + right: parent ? parent.right : undefined + rightMargin: hifi.dimensions.tablePadding + } + + FiraSansSemiBold { + id: textItem + text: styleData.value + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 3 + } + + HiFiGlyphs { + id: reloadButton + text: hifi.glyphs.reloadSmall + color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: stopButton.left + verticalCenter: parent.verticalCenter + } + MouseArea { + id: reloadButtonArea + anchors { fill: parent; margins: -2 } + onClicked: reloadScript(model.url) + } + } + + HiFiGlyphs { + id: stopButton + text: hifi.glyphs.closeSmall + color: stopButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: parent.right + verticalCenter: parent.verticalCenter + } + MouseArea { + id: stopButtonArea + anchors { fill: parent; margins: -2 } + onClicked: stopScript(model.url) + } + } + + } + + FiraSansSemiBold { + text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : "" + elide: Text.ElideMiddle + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + top: textItem.bottom + left: parent.left + right: parent.right + } + visible: styleData.selected + } + } + + TableViewColumn { + role: "name" + } } HifiControls.VerticalSpacer { From d798e562fc7b101c836e0ef74f711b09c507f5a9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 May 2016 17:18:30 +1200 Subject: [PATCH 039/264] Update scrollbar in tree view to match table scrollbar Used in Running Scripts and Asset Browser dialogs. --- interface/resources/qml/controls-uit/Tree.qml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 6d4d202e2c..aa1d10f030 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -1,5 +1,5 @@ // -// Table.qml +// Tree.qml // // Created by David Rowe on 17 Feb 2016 // Copyright 2016 High Fidelity, Inc. @@ -85,27 +85,18 @@ TreeView { bottomMargin: 3 // "" } radius: 3 - color: hifi.colors.tableScrollHandle + color: hifi.colors.tableScrollHandleDark } } scrollBarBackground: Item { - implicitWidth: 10 + implicitWidth: 9 Rectangle { anchors { fill: parent margins: -1 // Expand } - color: hifi.colors.baseGrayHighlight - } - - Rectangle { - anchors { - fill: parent - margins: 1 // Shrink - } - radius: 4 - color: hifi.colors.tableScrollBackground + color: hifi.colors.tableBackgroundDark } } From 7be33a85848fe9434201ff2ece0602604ae788a0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 12 May 2016 17:25:44 +1200 Subject: [PATCH 040/264] Don't show header or dates and sizes when choosing a directory --- interface/resources/qml/dialogs/FileDialog.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index ac0858c134..93c89e7393 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -196,7 +196,7 @@ ModalWindow { bottom: currentSelection.top bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } - headerVisible: true + headerVisible: !selectDirectory onDoubleClicked: navigateToRow(row); focus: true Keys.onReturnPressed: navigateToCurrentRow(); @@ -275,7 +275,7 @@ ModalWindow { id: fileNameColumn role: "fileName" title: "Name" - width: 0.5 * fileTableView.width + width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width resizable: true } TableViewColumn { @@ -284,12 +284,14 @@ ModalWindow { title: "Date" width: 0.3 * fileTableView.width resizable: true + visible: !selectDirectory } TableViewColumn { role: "fileSize" title: "Size" width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width resizable: true + visible: !selectDirectory } function navigateToRow(row) { From 63073301ee9735821af9301ee51402b2a763eb1c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 12 May 2016 11:16:42 -0700 Subject: [PATCH 041/264] try again to fix update logic --- .../entities/src/UpdateEntityOperator.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index cfd7498cdf..a48f3f7198 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -42,6 +42,9 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, _oldEntityCube = _existingEntity->getQueryAACube(); _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds + _newEntityCube = newQueryAACube; + _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds + // If our new properties don't have bounds details (no change to position, etc) or if this containing element would // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update @@ -49,23 +52,19 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. - if (!oldElementBestFit) { + if (oldElementBestFit) { + if (_wantDebug) { + qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit; + } + } else { + _oldEntityBox = _existingEntity->getElement()->getAACube(); _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { qCDebug(entities) << " **** UNUSUAL CASE **** no changes, but not best fit... consider it a move.... **"; } - } else { - _foundOld = true; - - if (_wantDebug) { - qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit; - } } - _newEntityCube = newQueryAACube; - _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds - if (_wantDebug) { qCDebug(entities) << " _entityItemID:" << _entityItemID; qCDebug(entities) << " _containingElementCube:" << _containingElementCube; From 820fdf09e2be812feef4cedc601d84e8ed5da2ec Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 12:42:29 -0700 Subject: [PATCH 042/264] Remove unused ScriptEngine::_wantSignals --- libraries/script-engine/src/ScriptEngine.cpp | 72 ++++++------------- libraries/script-engine/src/ScriptEngine.h | 5 +- libraries/script-engine/src/ScriptEngines.cpp | 2 +- 3 files changed, 24 insertions(+), 55 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f7ac7894ff..53ffd19869 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -138,10 +138,9 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName return false; } -ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) : +ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString) : _scriptContents(scriptContents), _timerFunctionMap(), - _wantSignals(wantSignals), _fileNameString(fileNameString), _arrayBufferClass(new ArrayBufferClass(this)) { @@ -231,15 +230,14 @@ void ScriptEngine::runDebuggable() { return; } stopAllTimers(); // make sure all our timers are stopped if the script is ending - if (_wantSignals) { - emit scriptEnding(); - emit finished(_fileNameString, this); - } + + emit scriptEnding(); + emit finished(_fileNameString, this); _isRunning = false; - if (_wantSignals) { - emit runningStateChanged(); - emit doneRunning(); - } + + emit runningStateChanged(); + emit doneRunning(); + timer->deleteLater(); return; } @@ -249,9 +247,7 @@ void ScriptEngine::runDebuggable() { if (_lastUpdate < now) { float deltaTime = (float)(now - _lastUpdate) / (float)USECS_PER_SECOND; if (!_isFinished) { - if (_wantSignals) { - emit update(deltaTime); - } + emit update(deltaTime); } } _lastUpdate = now; @@ -358,17 +354,13 @@ void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scrip if (QRegularExpression(DEBUG_FLAG).match(scriptContents).hasMatch()) { _debuggable = true; } - if (_wantSignals) { - emit scriptLoaded(url.toString()); - } + emit scriptLoaded(url.toString()); } // FIXME - switch this to the new model of ScriptCache callbacks void ScriptEngine::errorInLoadingScript(const QUrl& url) { qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__; - if (_wantSignals) { - emit errorLoadingScript(_fileNameString); // ?? - } + emit errorLoadingScript(_fileNameString); // ?? } // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of @@ -785,9 +777,7 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi --_evaluatesPending; const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName()); - if (_wantSignals) { - emit evaluationFinished(result, hadUncaughtException); - } + emit evaluationFinished(result, hadUncaughtException); return result; } @@ -801,9 +791,7 @@ void ScriptEngine::run() { } _isRunning = true; - if (_wantSignals) { - emit runningStateChanged(); - } + emit runningStateChanged(); QScriptValue result = evaluate(_scriptContents, _fileNameString); @@ -872,9 +860,7 @@ void ScriptEngine::run() { if (_lastUpdate < now) { float deltaTime = (float) (now - _lastUpdate) / (float) USECS_PER_SECOND; if (!_isFinished) { - if (_wantSignals) { - emit update(deltaTime); - } + emit update(deltaTime); } } _lastUpdate = now; @@ -884,9 +870,7 @@ void ScriptEngine::run() { } stopAllTimers(); // make sure all our timers are stopped if the script is ending - if (_wantSignals) { - emit scriptEnding(); - } + emit scriptEnding(); if (entityScriptingInterface->getEntityPacketSender()->serversExist()) { // release the queue of edit entity messages. @@ -904,15 +888,11 @@ void ScriptEngine::run() { } } - if (_wantSignals) { - emit finished(_fileNameString, this); - } + emit finished(_fileNameString, this); _isRunning = false; - if (_wantSignals) { - emit runningStateChanged(); - emit doneRunning(); - } + emit runningStateChanged(); + emit doneRunning(); } // NOTE: This is private because it must be called on the same thread that created the timers, which is why @@ -950,9 +930,7 @@ void ScriptEngine::stop() { return; } _isFinished = true; - if (_wantSignals) { - emit runningStateChanged(); - } + emit runningStateChanged(); } } @@ -1076,9 +1054,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const { } void ScriptEngine::print(const QString& message) { - if (_wantSignals) { - emit printedMessage(message); - } + emit printedMessage(message); } // If a callback is specified, the included files will be loaded asynchronously and the callback will be called @@ -1214,13 +1190,9 @@ void ScriptEngine::load(const QString& loadFile) { if (_isReloading) { auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(url.toString()); - if (_wantSignals) { - emit reloadScript(url.toString(), false); - } + emit reloadScript(url.toString(), false); } else { - if (_wantSignals) { - emit loadScript(url.toString(), false); - } + emit loadScript(url.toString(), false); } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index d37e3eb177..cc23e57fda 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -67,9 +67,7 @@ public: class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { Q_OBJECT public: - ScriptEngine(const QString& scriptContents = NO_SCRIPT, - const QString& fileNameString = QString(""), - bool wantSignals = true); + ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("")); ~ScriptEngine(); @@ -191,7 +189,6 @@ protected: bool _isInitialized { false }; QHash _timerFunctionMap; QSet _includedURLs; - bool _wantSignals { true }; QHash _entityScripts; bool _isThreaded { false }; QScriptEngineDebugger* _debugger { nullptr }; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 330a94cf0b..f91d7df9e6 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -428,7 +428,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL return scriptEngine; } - scriptEngine = new ScriptEngine(NO_SCRIPT, "", true); + scriptEngine = new ScriptEngine(NO_SCRIPT, ""); scriptEngine->setUserLoaded(isUserLoaded); connect(scriptEngine, &ScriptEngine::doneRunning, this, [scriptEngine] { scriptEngine->deleteLater(); From 13d602487ff4c57c2b4b2b2da7be6ff2392d14e9 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 12:43:16 -0700 Subject: [PATCH 043/264] Remove guard over atomic in ScriptEngine::stop --- libraries/script-engine/src/ScriptEngine.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 53ffd19869..0ad5087478 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -925,10 +925,6 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { void ScriptEngine::stop() { if (!_isFinished) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stop"); - return; - } _isFinished = true; emit runningStateChanged(); } From edf82c57ba1104773fdddbc96985fbd9a4a97053 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 12:45:02 -0700 Subject: [PATCH 044/264] Clean scripting thread deletion --- libraries/script-engine/src/ScriptEngine.cpp | 54 +++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 0ad5087478..a27dd7eb10 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -162,16 +162,15 @@ ScriptEngine::~ScriptEngine() { } else { qCWarning(scriptengine) << "Script destroyed after ScriptEngines!"; } - - waitTillDoneRunning(); } void ScriptEngine::disconnectNonEssentialSignals() { disconnect(); - QThread* receiver; + QThread* workerThread; // Ensure the thread should be running, and does exist - if (_isRunning && _isThreaded && (receiver = thread())) { - connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit); + if (_isRunning && _isThreaded && (workerThread = thread())) { + connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); } } @@ -268,30 +267,33 @@ void ScriptEngine::runInThread() { } _isThreaded = true; - QThread* workerThread = new QThread(); - QString scriptEngineName = QString("Script Thread:") + getFilename(); - workerThread->setObjectName(scriptEngineName); + // The thread interface cannot live on itself, and we want to move this into the thread, so + // the thread cannot have this as a parent. + QThread* workerThread = new QThread(); + workerThread->setObjectName(QString("Script Thread:") + getFilename()); + moveToThread(workerThread); + // NOTE: If you connect any essential signals for proper shutdown or cleanup of // the script engine, make sure to add code to "reconnect" them to the // disconnectNonEssentialSignals() method - - // when the worker thread is started, call our engine's run.. connect(workerThread, &QThread::started, this, &ScriptEngine::run); - - // tell the thread to stop when the script engine is done connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); - moveToThread(workerThread); - - // Starts an event loop, and emits workerThread->started() workerThread->start(); } void ScriptEngine::waitTillDoneRunning() { // If the script never started running or finished running before we got here, we don't need to wait for it auto workerThread = thread(); + if (_isThreaded && workerThread) { + // We should never be waiting (blocking) on our own thread + assert(workerThread != QThread::currentThread()); + + stop(); + QString scriptName = getFilename(); auto startedWaiting = usecTimestampNow(); @@ -301,24 +303,28 @@ void ScriptEngine::waitTillDoneRunning() { // the scripts will likely need to marshall messages across to the main thread, e.g. // if they access Settings or Menu in any of their shutdown code. So: // Process events for the main application thread, allowing invokeMethod calls to pass between threads. - QCoreApplication::processEvents(); // thread-safe :) + QCoreApplication::processEvents(); - // If we've been waiting a second or more, then tell the script engine to stop evaluating + // If the final evaluation takes too long, then tell the script engine to stop evaluating static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; + static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MAX_SCRIPT_EVALUATION_TIME; auto elapsedUsecs = usecTimestampNow() - startedWaiting; if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { - qCDebug(scriptengine) << - "Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] quitting."; - abortEvaluation(); // to allow the thread to quit - workerThread->quit(); - break; + qCDebug(scriptengine).nospace() << + "Script " << scriptName << " has been running too long [" << elapsedUsecs << "usecs] quitting."; + abortEvaluation(); // break out of current evaluation + + // Wait for the scripting thread to stop running + if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { + qCWarning(scriptengine).nospace() << + "Script " << scriptName << " has been quitting too long [" << elapsedUsecs << "usecs] terminating."; + workerThread->terminate(); + } } // Avoid a pure busy wait QThread::yieldCurrentThread(); } - - workerThread->deleteLater(); } } From d1d0bbdd2654d2a06f6b0f293c2c5d278a1f272f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 12 May 2016 16:46:58 -0700 Subject: [PATCH 045/264] tidy 2.0 --- .../DomainContent/Home/reset.js | 147 +++++++++++++++--- 1 file changed, 124 insertions(+), 23 deletions(-) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index 376ae843e1..fc3bd88bbf 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -66,26 +66,40 @@ var TRANSFORMER_URL_MATTHEW = 'atp:/dressingRoom/matthew.fbx'; + var BEAM_TRIGGER_THRESHOLD = 0.075; + + var BEAM_POSITION = { + x: 1103.3876, + y: 460.4591, + z: -74.2998 + }; + + + Reset.prototype = { tidying: false, - + eyesOn: false, + flickerInterval: null, preload: function(entityID) { _this.entityID = entityID; + _this.tidySound = SoundCache.getSound("atp:/sounds/tidy.wav"); + Script.update.connect(_this.update); }, showTidyingButton: function() { var data = { - "Texture.001": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Head-Housing-Texture.png", - "button.tidy": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Orange.png", - "button.tidy-active": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Orange.png", - "button.tidy-active.emit": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Orange-Emit.png", - "button.tidy.emit": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Orange-Emit.png", - "tex.button.blanks": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Button-Blanks.png", - "tex.button.blanks.normal": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Button-Blanks-Normal.png", - "tex.face.sceen": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/tidy-guy-face.png", - "tex.face.screen.emit": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/tidy-guy-face-Emit.png" + "Texture": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker.jpg", + "Texture.001": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Head-Housing-Texture.png", + "Texture.003": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker-Emit.jpg", + "tex.button.blanks": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks.png", + "tex.button.blanks.normal": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks-Normal.png", + "tex.face.sceen.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-default.jpg", + "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", + "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", + "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png" } + Entities.editEntity(_this.entityID, { textures: JSON.stringify(data) }); @@ -93,13 +107,15 @@ showTidyButton: function() { var data = { - "Texture.001": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Head-Housing-Texture.png", - "button.tidy": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Green.png", - "button.tidy.emit": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Tidy-Up-Button-Green-Emit.png", - "tex.button.blanks": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Button-Blanks.png", - "tex.button.blanks.normal": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/Button-Blanks-Normal.png", - "tex.face.sceen": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/tidy-guy-face.png", - "tex.face.screen.emit": "atp:/Tidyguy-7.fbx/Tidyguy-7.fbm/tidy-guy-face-Emit.png" + "Texture": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker.jpg", + "Texture.001": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Head-Housing-Texture.png", + "Texture.003": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker-Emit.jpg", + "tex.button.blanks": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks.png", + "tex.button.blanks.normal": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks-Normal.png", + "tex.face.sceen.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-default.jpg", + "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", + "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", + "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-default.png" } @@ -109,6 +125,18 @@ }, playTidyingSound: function() { + var location = { + x: 1102.7660, + y: 460.9814, + z: -78.6451 + } + + var injector = Audio.playSound(_this.tidySound, { + volume: 1, + position: location + }); + + print('INJECTOR IS: ' + injector) }, @@ -119,7 +147,7 @@ _this.tidying = true; _this.showTidyingButton(); _this.playTidyingSound(); - + _this.flickerEyes(); _this.findAndDeleteHomeEntities(); Script.setTimeout(function() { _this.showTidyButton(); @@ -130,12 +158,64 @@ _this.createKineticEntities(); _this.createScriptedEntities(); _this.setupDressingRoom(); - }, 750) - + }, 750); } }, + flickerEyes: function() { + + this.flickerInterval = Script.setInterval(function() { + if (_this.eyesOn === true) { + _this.turnEyesOff(); + _this.eyesOn = false; + } else if (_this.eyesOn === false) { + _this.turnEyesOn(); + _this.eyesOn = true + } + }, 100); + + Script.setTimeout(function() { + Script.clearInterval(_this.flickerInterval); + }, 2500) + }, + + turnEyesOn: function() { + var data = { + "Texture": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker.jpg", + "Texture.001": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Head-Housing-Texture.png", + "Texture.003": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker-Emit.jpg", + "tex.button.blanks": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks.png", + "tex.button.blanks.normal": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks-Normal.png", + "tex.face.sceen.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", + "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", + "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", + "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png" + }; + + Entities.editEntity(_this.entityID, { + textures: JSON.stringify(data) + }); + }, + + turnEyesOff: function() { + var data = { + "Texture": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker.jpg", + "Texture.001": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Head-Housing-Texture.png", + "Texture.003": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker-Emit.jpg", + "tex.button.blanks": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks.png", + "tex.button.blanks.normal": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Button-Blanks-Normal.png", + "tex.face.sceen.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-default.jpg", + "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", + "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", + "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png" + }; + + Entities.editEntity(_this.entityID, { + textures: JSON.stringify(data) + }); + }, + clickReleaseOnEntity: function(entityID, mouseEvent) { if (!mouseEvent.isLeftButton) { return; @@ -144,9 +224,9 @@ }, - startNearTrigger: function() { - _this.toggleButton(); - }, + // startNearTrigger: function() { + // _this.toggleButton(); + // }, findAndDeleteHomeEntities: function() { print('HOME trying to find home entities to delete') @@ -479,7 +559,28 @@ }, + update: function() { + if (_this.tidying === true) { + return + } + + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + + var rightDistance = Vec3.distance(leftHandPosition, BEAM_POSITION) + var leftDistance = Vec3.distance(leftHandPosition, BEAM_POSITION) + + if (rightDistance < BEAM_TRIGGER_THRESHOLD || leftDistance < BEAM_TRIGGER_THRESHOLD) { + _this.toggleButton(); + } else { + //too far to toggle swipe + } + }, + unload: function() { + Script.update.disconnect(_this.update); + + Script.clearInterval(_this.flickerInterval); // this.findAndDeleteHomeEntities(); } From 5442ffaf3eefee5ec8be5c9f687cc60b78cd4672 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 12 May 2016 16:58:29 -0700 Subject: [PATCH 046/264] glow light --- .../DomainContent/Home/reset.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index fc3bd88bbf..a488562a66 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -105,6 +105,57 @@ }); }, + tidyGlowActive: function() { + print('tidy glow active') + var res = Entities.findEntities(MyAvatar.position, 30); + var glow = null; + res.forEach(function(item) { + var props = Entities.getEntityProperties(item); + if (props.name === "Tidyguy-Glow") { + glow = item; + } + + + }) + if (glow !== null) { + print('editing glow' + glow) + Entities.editEntity(glow, { + color: { + red: 255, + green: 140, + blue: 20 + } + }) + } + }, + + tidyGlowDefault: function() { + print('tidy glow default') + + var res = Entities.findEntities(MyAvatar.position, 30); + var glow = null; + res.forEach(function(item) { + var props = Entities.getEntityProperties(item); + if (props.name === "Tidyguy-Glow") { + glow = item; + } + + }); + + if (glow !== null) { + print('editing glow' + glow) + + Entities.editEntity(glow, { + + color: { + red: 92, + green: 255, + blue: 209 + } + }) + } + }, + showTidyButton: function() { var data = { "Texture": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/Tidy-Swipe-Sticker.jpg", @@ -146,11 +197,13 @@ } else { _this.tidying = true; _this.showTidyingButton(); + _this.tidyGlowActive(); _this.playTidyingSound(); _this.flickerEyes(); _this.findAndDeleteHomeEntities(); Script.setTimeout(function() { _this.showTidyButton(); + _this.tidyGlowDefault(); _this.tidying = false; }, 2500); From 2cc788f98dce5d0f53e162edffb49a5d3e5ff419 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 12:45:35 -0700 Subject: [PATCH 047/264] Rename ScriptEngine::wait to match std threading --- libraries/script-engine/src/ScriptEngine.cpp | 3 +-- libraries/script-engine/src/ScriptEngine.h | 5 ++++- libraries/script-engine/src/ScriptEngines.cpp | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a27dd7eb10..c52848ad11 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -284,8 +284,7 @@ void ScriptEngine::runInThread() { workerThread->start(); } -void ScriptEngine::waitTillDoneRunning() { - // If the script never started running or finished running before we got here, we don't need to wait for it +void ScriptEngine::wait() { auto workerThread = thread(); if (_isThreaded && workerThread) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index cc23e57fda..2c0812210c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -197,7 +197,10 @@ protected: void init(); QString getFilename() const; - void waitTillDoneRunning(); + + // Stop any evaluating scripts and wait for the scripting thread to finish. + void wait(); + bool evaluatePending() const { return _evaluatesPending > 0; } void timerFired(); void stopAllTimers(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index f91d7df9e6..ba18a5019b 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -149,6 +149,7 @@ void ScriptEngines::shutdownScripting() { // NOTE: typically all script engines are running. But there's at least one known exception to this, the // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using // them. We don't need to stop scripts that aren't running. + // TODO: Scripts could be shut down faster if we spread them across a threadpool. if (scriptEngine->isRunning()) { qCDebug(scriptengine) << "about to shutdown script:" << scriptName; @@ -165,12 +166,12 @@ void ScriptEngines::shutdownScripting() { // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing // any application state after we leave this stopAllScripts() method qCDebug(scriptengine) << "waiting on script:" << scriptName; - scriptEngine->waitTillDoneRunning(); + scriptEngine->wait(); qCDebug(scriptengine) << "done waiting on script:" << scriptName; scriptEngine->deleteLater(); - // If the script is stopped, we can remove it from our set + // Once the script is stopped, we can remove it from our set i.remove(); } } From f40fe88ee7275b5008da01085aec084fb2e72f52 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 13:04:49 -0700 Subject: [PATCH 048/264] Clean up entity script engine deletion --- .../src/EntityTreeRenderer.cpp | 25 ++++++++++--------- .../src/EntityTreeRenderer.h | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 6 +++-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 814faa8874..1dfe2ab53c 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -77,9 +77,17 @@ EntityTreeRenderer::~EntityTreeRenderer() { int EntityTreeRenderer::_entitiesScriptEngineCount = 0; -void EntityTreeRenderer::setupEntitiesScriptEngine() { - QSharedPointer oldEngine = _entitiesScriptEngine; // save the old engine through this function, so the EntityScriptingInterface doesn't have problems with it. - _entitiesScriptEngine = QSharedPointer(new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)), &QObject::deleteLater); +void EntityTreeRenderer::resetEntitiesScriptEngine() { + // Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use + auto oldEngine = _entitiesScriptEngine; + + auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)); + _entitiesScriptEngine = QSharedPointer(newEngine, [](ScriptEngine* engine){ + engine->unloadAllEntityScripts(); + engine->stop(); + engine->deleteLater(); + }); + _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data()); _entitiesScriptEngine->runInThread(); DependencyManager::get()->setEntitiesScriptEngine(_entitiesScriptEngine.data()); @@ -87,16 +95,9 @@ void EntityTreeRenderer::setupEntitiesScriptEngine() { void EntityTreeRenderer::clear() { leaveAllEntities(); - if (_entitiesScriptEngine) { - _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(); - } if (_wantScripts && !_shuttingDown) { - // NOTE: you can't actually need to delete it here because when we call setupEntitiesScriptEngine it will - // assign a new instance to our shared pointer, which will deref the old instance and ultimately call - // the custom deleter which calls deleteLater - setupEntitiesScriptEngine(); + resetEntitiesScriptEngine(); } auto scene = _viewState->getMain3DScene(); @@ -125,7 +126,7 @@ void EntityTreeRenderer::init() { entityTree->setFBXService(this); if (_wantScripts) { - setupEntitiesScriptEngine(); + resetEntitiesScriptEngine(); } forceRecheckEntities(); // setup our state to force checking our inside/outsideness of entities diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index a6fc58e5f1..5c06c5f5cc 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -126,7 +126,7 @@ protected: } private: - void setupEntitiesScriptEngine(); + void resetEntitiesScriptEngine(); void addEntityToScene(EntityItemPointer entity); bool findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector* entitiesContainingAvatar); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index ba18a5019b..6e7c2bb839 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -158,8 +158,10 @@ void ScriptEngines::shutdownScripting() { // and stop. We can safely short circuit this because we know we're in the "quitting" process scriptEngine->disconnect(this); - // Calling stop on the script engine will set it's internal _isFinished state to true, and result - // in the ScriptEngine gracefully ending it's run() method. + // If this is an entity script, we need to unload any entities + scriptEngine->unloadAllEntityScripts(); + + // Gracefully stop the engine's scripting thread scriptEngine->stop(); // We need to wait for the engine to be done running before we proceed, because we don't From f2f89ca062c50fcb187c211e3b9ce8179946ab85 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 13:39:43 -0700 Subject: [PATCH 049/264] Add logging to ScriptEngine lifetime --- libraries/script-engine/src/ScriptEngine.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index c52848ad11..6ce8c56eb0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -154,7 +154,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam } ScriptEngine::~ScriptEngine() { - qCDebug(scriptengine) << "Script Engine shutting down (destructor) for script:" << getFilename(); + qCDebug(scriptengine) << "Script Engine shutting down:" << getFilename(); auto scriptEngines = DependencyManager::get(); if (scriptEngines) { @@ -291,11 +291,10 @@ void ScriptEngine::wait() { // We should never be waiting (blocking) on our own thread assert(workerThread != QThread::currentThread()); + // Engine should be stopped already, but be defensive stop(); - QString scriptName = getFilename(); auto startedWaiting = usecTimestampNow(); - while (workerThread->isRunning()) { // NOTE: This will be called on the main application thread from stopAllScripts. // The application thread will need to continue to process events, because @@ -310,13 +309,13 @@ void ScriptEngine::wait() { auto elapsedUsecs = usecTimestampNow() - startedWaiting; if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { qCDebug(scriptengine).nospace() << - "Script " << scriptName << " has been running too long [" << elapsedUsecs << "usecs] quitting."; + "Script Engine has been running too long, aborting:" << getFilename(); abortEvaluation(); // break out of current evaluation // Wait for the scripting thread to stop running if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { qCWarning(scriptengine).nospace() << - "Script " << scriptName << " has been quitting too long [" << elapsedUsecs << "usecs] terminating."; + "Script Engine has been aborting too long, terminating:" << getFilename(); workerThread->terminate(); } } @@ -324,6 +323,8 @@ void ScriptEngine::wait() { // Avoid a pure busy wait QThread::yieldCurrentThread(); } + + qCDebug(scriptengine) << "Script Engine has stopped:" << getFilename(); } } @@ -874,6 +875,8 @@ void ScriptEngine::run() { hadUncaughtExceptions(*this, _fileNameString); } + qCDebug(scriptengine) << "Script Engine stopping:" << getFilename(); + stopAllTimers(); // make sure all our timers are stopped if the script is ending emit scriptEnding(); From 7e82494a663b63d19f43caf2d1e8d1f42ab70fc5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 13:40:10 -0700 Subject: [PATCH 050/264] Add cap on entities scripting thread stop time --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 5 +++++ libraries/script-engine/src/ScriptEngine.cpp | 1 - libraries/script-engine/src/ScriptEngine.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1dfe2ab53c..c73a7ae6c7 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -83,8 +83,13 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)); _entitiesScriptEngine = QSharedPointer(newEngine, [](ScriptEngine* engine){ + // Gracefully exit engine->unloadAllEntityScripts(); engine->stop(); + + // Disgracefully exit, if necessary + QTimer::singleShot(ScriptEngine::MAX_SCRIPT_EVALUATION_TIME, engine, &ScriptEngine::abort); + engine->deleteLater(); }); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6ce8c56eb0..dab921e962 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -304,7 +304,6 @@ void ScriptEngine::wait() { QCoreApplication::processEvents(); // If the final evaluation takes too long, then tell the script engine to stop evaluating - static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MAX_SCRIPT_EVALUATION_TIME; auto elapsedUsecs = usecTimestampNow() - startedWaiting; if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 2c0812210c..bb4814eb94 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -67,6 +67,8 @@ public: class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { Q_OBJECT public: + static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; + ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("")); ~ScriptEngine(); @@ -164,6 +166,7 @@ public: public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); void updateMemoryCost(const qint64&); + void abort() { abortEvaluation(); } signals: void scriptLoaded(const QString& scriptFilename); From 1107882be288f8be23a27892c7087e108b89be0f Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 17:48:14 -0700 Subject: [PATCH 051/264] Throw to stop non-evaluating scripts --- libraries/script-engine/src/ScriptEngine.cpp | 24 +++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index dab921e962..b5077db240 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -304,17 +304,25 @@ void ScriptEngine::wait() { QCoreApplication::processEvents(); // If the final evaluation takes too long, then tell the script engine to stop evaluating - static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MAX_SCRIPT_EVALUATION_TIME; auto elapsedUsecs = usecTimestampNow() - startedWaiting; if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { - qCDebug(scriptengine).nospace() << - "Script Engine has been running too long, aborting:" << getFilename(); - abortEvaluation(); // break out of current evaluation + workerThread->quit(); - // Wait for the scripting thread to stop running - if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { - qCWarning(scriptengine).nospace() << - "Script Engine has been aborting too long, terminating:" << getFilename(); + if (isEvaluating()) { + qCWarning(scriptengine) << "Script Engine has been running too long, aborting:" << getFilename(); + abortEvaluation(); + } else { + qCWarning(scriptengine) << "Script Engine has been running too long, throwing:" << getFilename(); + auto context = currentContext(); + if (context) { + context->throwError("Timed out during shutdown"); + } + } + + // Wait for the scripting thread to stop running, as + // flooding it with aborts/exceptions will persist it longer + static const auto MAX_SCRIPT_QUITTING_TIME = 0.5 * MSECS_PER_SECOND; + if (workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) { workerThread->terminate(); } } From e1c130d02fe9fcb22e7c47dae143b6f2c3392a2f Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 17:48:34 -0700 Subject: [PATCH 052/264] Timeout long sandbox scripts --- libraries/script-engine/src/ScriptEngine.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b5077db240..6ed7f7f684 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1292,8 +1292,23 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co setParentURL(scriptOrURL); } + const int SANDBOX_TIMEOUT = 0.25 * MSECS_PER_SECOND; QScriptEngine sandbox; - QScriptValue testConstructor = sandbox.evaluate(program); + sandbox.setProcessEventsInterval(SANDBOX_TIMEOUT); + QScriptValue testConstructor; + { + QTimer timeout; + timeout.setSingleShot(true); + timeout.start(SANDBOX_TIMEOUT); + connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT]{ + auto context = sandbox.currentContext(); + if (context) { + // Guard against infinite loops and non-performant code + context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT)); + } + }); + testConstructor = sandbox.evaluate(program); + } if (hadUncaughtExceptions(sandbox, program.fileName())) { return; } From 806d06b5529104c6f41f1748381204c1f4c6b9fc Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 12 May 2016 17:49:47 -0700 Subject: [PATCH 053/264] Wait on old entity script engines in threadpool --- .../src/EntityTreeRenderer.cpp | 21 +++++++++++++------ libraries/script-engine/src/ScriptEngine.h | 14 ++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c73a7ae6c7..7643e446ec 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -83,14 +84,22 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)); _entitiesScriptEngine = QSharedPointer(newEngine, [](ScriptEngine* engine){ - // Gracefully exit + class WaitRunnable : public QRunnable { + public: + WaitRunnable(ScriptEngine* engine) : _engine(engine) {} + virtual void run() override { + _engine->wait(); + _engine->deleteLater(); + } + + private: + ScriptEngine* _engine; + }; + engine->unloadAllEntityScripts(); engine->stop(); - - // Disgracefully exit, if necessary - QTimer::singleShot(ScriptEngine::MAX_SCRIPT_EVALUATION_TIME, engine, &ScriptEngine::abort); - - engine->deleteLater(); + // Wait for the scripting thread from the thread pool to avoid hanging the main thread + QThreadPool::globalInstance()->start(new WaitRunnable(engine)); }); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data()); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index bb4814eb94..b107e741f4 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -83,6 +83,13 @@ public: /// run the script in the callers thread, exit when stop() is called. void run(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts + Q_INVOKABLE void stop(); + + // Stop any evaluating scripts and wait for the scripting thread to finish. + void wait(); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread @@ -138,10 +145,6 @@ public: Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(); - bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget @@ -201,9 +204,6 @@ protected: void init(); QString getFilename() const; - // Stop any evaluating scripts and wait for the scripting thread to finish. - void wait(); - bool evaluatePending() const { return _evaluatesPending > 0; } void timerFired(); void stopAllTimers(); From 30fb9349c46727a5700b4ca9e54a83eaa7767198 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 12 May 2016 17:52:40 -0700 Subject: [PATCH 054/264] dry up some code --- .../src/RenderableModelEntityItem.cpp | 50 +++++++++---------- .../src/RenderableModelEntityItem.h | 2 + 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c4ac9b09e5..a9aee1e527 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -162,6 +162,19 @@ void RenderableModelEntityItem::remapTextures() { } } +void RenderableModelEntityItem::doInitialModelSimulation() { + _model->setScaleToFit(true, getDimensions()); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + { + PerformanceTimer perfTimer("_model->simulate"); + _model->simulate(0.0f); + } + _needsInitialSimulation = false; +} + + // TODO: we need a solution for changes to the postion/rotation/etc of a model... // this current code path only addresses that in this setup case... not the changing/moving case bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) { @@ -172,22 +185,12 @@ bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) { getModel(renderer); } if (renderArgs && _model && _needsInitialSimulation && _model->isActive() && _model->isLoaded()) { - _model->setScaleToFit(true, getDimensions()); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - // make sure to simulate so everything gets set up correctly for rendering - { - PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - } - _needsInitialSimulation = false; - + doInitialModelSimulation(); _model->renderSetup(renderArgs); } bool ready = !_needsInitialSimulation && _model && _model->readyToAddToScene(renderArgs); - return ready; + return ready; } class RenderableModelEntityItemMeta { @@ -348,18 +351,7 @@ void RenderableModelEntityItem::updateModelBounds() { _model->getRotation() != getRotation() || _model->getRegistrationPoint() != getRegistrationPoint()) && _model->isActive() && _dimensionsInitialized) { - _model->setScaleToFit(true, dimensions); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - - // make sure to simulate so everything gets set up correctly for rendering - { - PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - } - - _needsInitialSimulation = false; + doInitialModelSimulation(); _needsJointSimulation = false; } } @@ -592,8 +584,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { if (_needsInitialSimulation) { // the _model's offset will be wrong until _needsInitialSimulation is false PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - _needsInitialSimulation = false; + doInitialModelSimulation(); } return true; @@ -807,6 +798,13 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) { if (_model && _model->isActive()) { _model->setRotation(getRotation()); _model->setTranslation(getPosition()); + + // { + // render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + // render::PendingChanges pendingChanges; + // pendingChanges.updateItem(_myMetaItem, [](RenderableModelEntityItemMeta& data){}); + // scene->enqueuePendingChanges(pendingChanges); + // } } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 59208d209d..5fd767c6ee 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -38,6 +38,8 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + void doInitialModelSimulation(); + virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; From dd2a29aace35ac6a30c9be6a9ba854a7e5839dae Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 12 May 2016 18:05:28 -0700 Subject: [PATCH 055/264] when a ModelEntityItem moves, also update its meta-render-item --- .../src/RenderableModelEntityItem.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c4ac9b09e5..e13c2eac98 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -807,6 +807,16 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) { if (_model && _model->isActive()) { _model->setRotation(getRotation()); _model->setTranslation(getPosition()); + + auto myMetaItemCopy = _myMetaItem; + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [_myMetaItem]() { + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + render::PendingChanges pendingChanges; + pendingChanges.updateItem(myMetaItemCopy, [](RenderableModelEntityItemMeta& data){}); + scene->enqueuePendingChanges(pendingChanges); + }); } } From 3b34b908d7f066635b055baa6c32dc9f78eb5c78 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 13 May 2016 17:39:15 +1200 Subject: [PATCH 056/264] Tweak level-up and home buttons --- interface/resources/qml/controls-uit/GlyphButton.qml | 7 +++++++ interface/resources/qml/dialogs/FileDialog.qml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml index 2625dda723..ac353b5a52 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -19,6 +19,7 @@ Original.Button { property int color: 0 property int colorScheme: hifi.colorSchemes.light property string glyph: "" + property int size: 32 width: 120 height: 28 @@ -65,7 +66,13 @@ Original.Button { : hifi.buttons.disabledTextColor[control.colorScheme] verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter + anchors { + // Tweak horizontal alignment so that it looks right. + left: parent.left + leftMargin: -0.5 + } text: control.glyph + size: control.size } } } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 93c89e7393..1175c8a14f 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -99,6 +99,7 @@ ModalWindow { id: upButton glyph: hifi.glyphs.levelUp width: height + size: 30 enabled: model.parentFolder && model.parentFolder !== "" onClicked: d.navigateUp(); } @@ -107,6 +108,7 @@ ModalWindow { id: homeButton property var destination: helper.home(); glyph: hifi.glyphs.home + size: 28 width: height enabled: d.homeDestination ? true : false onClicked: d.navigateHome(); From 79141e38698f38226ae266337f4700292ed87958 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 12 May 2016 17:52:40 -0700 Subject: [PATCH 057/264] dry up some code --- .../src/RenderableModelEntityItem.cpp | 43 ++++++++----------- .../src/RenderableModelEntityItem.h | 2 + 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e13c2eac98..d6f8eb23c8 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -162,6 +162,19 @@ void RenderableModelEntityItem::remapTextures() { } } +void RenderableModelEntityItem::doInitialModelSimulation() { + _model->setScaleToFit(true, getDimensions()); + _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); + _model->setRotation(getRotation()); + _model->setTranslation(getPosition()); + { + PerformanceTimer perfTimer("_model->simulate"); + _model->simulate(0.0f); + } + _needsInitialSimulation = false; +} + + // TODO: we need a solution for changes to the postion/rotation/etc of a model... // this current code path only addresses that in this setup case... not the changing/moving case bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) { @@ -172,22 +185,12 @@ bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) { getModel(renderer); } if (renderArgs && _model && _needsInitialSimulation && _model->isActive() && _model->isLoaded()) { - _model->setScaleToFit(true, getDimensions()); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - // make sure to simulate so everything gets set up correctly for rendering - { - PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - } - _needsInitialSimulation = false; - + doInitialModelSimulation(); _model->renderSetup(renderArgs); } bool ready = !_needsInitialSimulation && _model && _model->readyToAddToScene(renderArgs); - return ready; + return ready; } class RenderableModelEntityItemMeta { @@ -348,18 +351,7 @@ void RenderableModelEntityItem::updateModelBounds() { _model->getRotation() != getRotation() || _model->getRegistrationPoint() != getRegistrationPoint()) && _model->isActive() && _dimensionsInitialized) { - _model->setScaleToFit(true, dimensions); - _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); - _model->setRotation(getRotation()); - _model->setTranslation(getPosition()); - - // make sure to simulate so everything gets set up correctly for rendering - { - PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - } - - _needsInitialSimulation = false; + doInitialModelSimulation(); _needsJointSimulation = false; } } @@ -592,8 +584,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { if (_needsInitialSimulation) { // the _model's offset will be wrong until _needsInitialSimulation is false PerformanceTimer perfTimer("_model->simulate"); - _model->simulate(0.0f); - _needsInitialSimulation = false; + doInitialModelSimulation(); } return true; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 59208d209d..5fd767c6ee 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -38,6 +38,8 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + void doInitialModelSimulation(); + virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) override; From 574709824e924cba5da16a1ab474f48ec1ad4830 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 13 May 2016 09:41:19 -0700 Subject: [PATCH 058/264] oops --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d6f8eb23c8..6273b9f8df 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -802,7 +802,7 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) { auto myMetaItemCopy = _myMetaItem; void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [_myMetaItem]() { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [myMetaItemCopy]() { render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); render::PendingChanges pendingChanges; pendingChanges.updateItem(myMetaItemCopy, [](RenderableModelEntityItemMeta& data){}); From efe02c0fa120666b5296d0e559e272b67b2fb412 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 13 May 2016 10:57:41 -0700 Subject: [PATCH 059/264] make lock/unlock button easier to see --- scripts/system/assets/images/lock.svg | 32 ++++++++++++++++++----- scripts/system/assets/images/unlock.svg | 30 +++++++++++++++++---- scripts/system/attachedEntitiesManager.js | 4 +-- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/scripts/system/assets/images/lock.svg b/scripts/system/assets/images/lock.svg index 1480359f43..bb9658de00 100644 --- a/scripts/system/assets/images/lock.svg +++ b/scripts/system/assets/images/lock.svg @@ -2,11 +2,13 @@ + id="defs4"> + + + + + image/svg+xml - + @@ -54,15 +74,15 @@ id="layer1" transform="translate(0,-825.59055)"> diff --git a/scripts/system/assets/images/unlock.svg b/scripts/system/assets/images/unlock.svg index a7664c1f59..789a8b0ed5 100644 --- a/scripts/system/assets/images/unlock.svg +++ b/scripts/system/assets/images/unlock.svg @@ -2,11 +2,13 @@ + id="defs4"> + + + + + image/svg+xml - + @@ -54,14 +74,14 @@ id="layer1" transform="translate(0,-825.59055)"> Date: Fri, 13 May 2016 13:32:00 -0700 Subject: [PATCH 060/264] don't crash when a moving model entity is deleted --- .../src/RenderableModelEntityItem.cpp | 21 +++++++++++++++---- .../src/RenderableModelEntityItem.h | 2 ++ libraries/render/src/render/Scene.cpp | 5 +++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e13c2eac98..a7e14b4c07 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -808,13 +808,26 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) { _model->setRotation(getRotation()); _model->setTranslation(getPosition()); - auto myMetaItemCopy = _myMetaItem; - void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [_myMetaItem]() { + std::weak_ptr weakSelf = + std::static_pointer_cast(getThisPointer()); + + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() { + auto self = weakSelf.lock(); + if (!self) { + return; + } + + render::ItemID myMetaItem = self->getMetaRenderItem(); + + if (myMetaItem == render::Item::INVALID_ITEM_ID) { + return; + } + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); render::PendingChanges pendingChanges; - pendingChanges.updateItem(myMetaItemCopy, [](RenderableModelEntityItemMeta& data){}); + + pendingChanges.updateItem(myMetaItem); scene->enqueuePendingChanges(pendingChanges); }); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 59208d209d..9b21743395 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -87,6 +87,8 @@ public: bool hasRenderAnimation() const { return !_renderAnimationProperties.getURL().isEmpty(); } const QString& getRenderAnimationURL() const { return _renderAnimationProperties.getURL(); } + render::ItemID getMetaRenderItem() { return _myMetaItem; } + private: QVariantMap parseTexturesToMap(QString textures); void remapTextures(); diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index e091b4842c..0b1d8fb55f 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -157,6 +157,11 @@ void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) { auto updateFunctor = functors.begin(); for (auto updateID : ids) { + if (updateID == Item::INVALID_ITEM_ID) { + updateFunctor++; + continue; + } + // Access the true item auto& item = _items[updateID]; auto oldCell = item.getCell(); From 1f664671f91070e868f4a29b12835b588670d200 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 13 May 2016 14:32:54 -0700 Subject: [PATCH 061/264] Remove overlay when velocity first indicates travel, and restore it (with reset) when returning to reset. --- interface/src/ui/OverlayConductor.cpp | 48 ++++++++++++++++++++++----- interface/src/ui/OverlayConductor.h | 4 ++- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 95054869e5..543a500188 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -67,6 +67,32 @@ void OverlayConductor::update(float dt) { } void OverlayConductor::updateMode() { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + float speed = glm::length(myAvatar->getVelocity()); + bool nowDriving = _driving; + const float MIN_DRIVING = 0.2; + const float MAX_NOT_DRIVING = 0.01; + if (speed > MIN_DRIVING) { + nowDriving = true; + } + else if (speed < MAX_NOT_DRIVING) { + nowDriving = false; + } + if (nowDriving != _driving) { + if (nowDriving) { + _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); + } + else { // reset when coming out of driving + _mode = FLAT; // Seems appropriate to let things reset, below, after the following. + // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. + qApp->getActiveDisplayPlugin()->resetSensors(); + } + if (_wantsOverlays) { + qDebug() << "flipping" << !nowDriving; + setEnabled(!nowDriving, false); + } + _driving = nowDriving; + } Mode newMode; if (qApp->isHMDMode()) { @@ -84,10 +110,9 @@ void OverlayConductor::updateMode() { qApp->getApplicationCompositor().setModelTransform(identity); break; } - case STANDING: { + case STANDING: { // STANDING mode is not currently used. // enter the STANDING state // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); Transform t; t.setTranslation(extractTranslation(camMat)); @@ -103,15 +128,18 @@ void OverlayConductor::updateMode() { } _mode = newMode; + } -void OverlayConductor::setEnabled(bool enabled) { +void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) { if (enabled == _enabled) { return; } - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled); + if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway. + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled); + } _enabled = enabled; // set the new value @@ -124,8 +152,10 @@ void OverlayConductor::setEnabled(bool enabled) { qApp->getOverlays().enable(); // enable QML events - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(true); + if (toggleQmlEvents) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootItem()->setEnabled(true); + } if (_mode == STANDING) { // place the overlay at the current hmd position in world space @@ -144,8 +174,10 @@ void OverlayConductor::setEnabled(bool enabled) { qApp->getOverlays().disable(); // disable QML events - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(false); + if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501 + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootItem()->setEnabled(false); + } } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index b94c5be7dd..02b2035b07 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,7 +17,7 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable); + void setEnabled(bool enable, bool toggleQmlEvents = true); bool getEnabled() const; private: @@ -31,6 +31,8 @@ private: Mode _mode { FLAT }; bool _enabled { false }; + bool _driving { false }; + bool _wantsOverlays { true }; }; #endif From 654ed33dd4e870a6f1973ef6efe0b3b31b4b93dd Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 13 May 2016 15:32:59 -0700 Subject: [PATCH 062/264] Fix warnings. --- interface/src/ui/OverlayConductor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 543a500188..b44ea91a60 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -70,8 +70,8 @@ void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); float speed = glm::length(myAvatar->getVelocity()); bool nowDriving = _driving; - const float MIN_DRIVING = 0.2; - const float MAX_NOT_DRIVING = 0.01; + const float MIN_DRIVING = 0.2f; + const float MAX_NOT_DRIVING = 0.01f; if (speed > MIN_DRIVING) { nowDriving = true; } From 2140dc77b3ccbb284c1c018e30d23589ae12378e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 13 May 2016 16:14:22 -0700 Subject: [PATCH 063/264] Rename wait and unload in best thread --- .../entities-renderer/src/EntityTreeRenderer.cpp | 12 ++++++++---- libraries/script-engine/src/ScriptEngine.cpp | 5 +++-- libraries/script-engine/src/ScriptEngine.h | 6 +----- libraries/script-engine/src/ScriptEngines.cpp | 5 +---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7643e446ec..d7f9f1343b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -88,16 +88,13 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { public: WaitRunnable(ScriptEngine* engine) : _engine(engine) {} virtual void run() override { - _engine->wait(); + _engine->waitTillDoneRunning(); _engine->deleteLater(); } private: ScriptEngine* _engine; }; - - engine->unloadAllEntityScripts(); - engine->stop(); // Wait for the scripting thread from the thread pool to avoid hanging the main thread QThreadPool::globalInstance()->start(new WaitRunnable(engine)); }); @@ -110,6 +107,13 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { void EntityTreeRenderer::clear() { leaveAllEntities(); + if (_entitiesScriptEngine) { + // Unload and stop the engine here (instead of in its deleter) to + // avoid marshalling unload signals back to this thread + _entitiesScriptEngine->unloadAllEntityScripts(); + _entitiesScriptEngine->stop(); + } + if (_wantScripts && !_shuttingDown) { resetEntitiesScriptEngine(); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6ed7f7f684..843d714973 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -284,7 +284,7 @@ void ScriptEngine::runInThread() { workerThread->start(); } -void ScriptEngine::wait() { +void ScriptEngine::waitTillDoneRunning() { auto workerThread = thread(); if (_isThreaded && workerThread) { @@ -303,8 +303,9 @@ void ScriptEngine::wait() { // Process events for the main application thread, allowing invokeMethod calls to pass between threads. QCoreApplication::processEvents(); - // If the final evaluation takes too long, then tell the script engine to stop evaluating + // If the final evaluation takes too long, then tell the script engine to stop running auto elapsedUsecs = usecTimestampNow() - startedWaiting; + static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { workerThread->quit(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index b107e741f4..789958d57a 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -67,10 +67,7 @@ public: class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { Q_OBJECT public: - static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; - ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString("")); - ~ScriptEngine(); /// run the script in a dedicated thread. This will have the side effect of evalulating @@ -88,7 +85,7 @@ public: Q_INVOKABLE void stop(); // Stop any evaluating scripts and wait for the scripting thread to finish. - void wait(); + void waitTillDoneRunning(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can @@ -169,7 +166,6 @@ public: public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); void updateMemoryCost(const qint64&); - void abort() { abortEvaluation(); } signals: void scriptLoaded(const QString& scriptFilename); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 6e7c2bb839..b31c7ac07b 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -158,9 +158,6 @@ void ScriptEngines::shutdownScripting() { // and stop. We can safely short circuit this because we know we're in the "quitting" process scriptEngine->disconnect(this); - // If this is an entity script, we need to unload any entities - scriptEngine->unloadAllEntityScripts(); - // Gracefully stop the engine's scripting thread scriptEngine->stop(); @@ -168,7 +165,7 @@ void ScriptEngines::shutdownScripting() { // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing // any application state after we leave this stopAllScripts() method qCDebug(scriptengine) << "waiting on script:" << scriptName; - scriptEngine->wait(); + scriptEngine->waitTillDoneRunning(); qCDebug(scriptengine) << "done waiting on script:" << scriptName; scriptEngine->deleteLater(); From c21087ad8726f754e215c8fabd7c0c899b9faeee Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 14 May 2016 13:04:18 +1200 Subject: [PATCH 064/264] Add icon to title --- interface/resources/qml/dialogs/FileDialog.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 1175c8a14f..a3c5c02f9b 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -49,6 +49,8 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property int options; // <-- FIXME unused + property string iconText: text !== "" ? hifi.glyphs.scriptUpload : "" + property int iconSize: 40 property bool selectDirectory: false; property bool showHidden: false; @@ -69,6 +71,8 @@ ModalWindow { drivesSelector.onCurrentTextChanged.connect(function(){ root.dir = helper.pathToUrl(drivesSelector.currentText); }) + + iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; } Item { From 14c733cd6e382fc4db961bc6c72eec5d937aa87d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 14 May 2016 17:19:18 +1200 Subject: [PATCH 065/264] Make columns sortable --- .../resources/qml/controls-uit/Table.qml | 18 +++++++++++++++-- .../resources/qml/dialogs/FileDialog.qml | 20 ++++++++++++++++++- .../qml/styles-uit/HifiConstants.qml | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index 2a0fe545ef..35029ad8bf 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -29,6 +29,7 @@ TableView { color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark RalewayRegular { + id: titleText text: styleData.value size: hifi.fontSizes.tableHeading font.capitalization: Font.AllUppercase @@ -36,12 +37,25 @@ TableView { anchors { left: parent.left leftMargin: hifi.dimensions.tablePadding - right: parent.right - rightMargin: hifi.dimensions.tablePadding verticalCenter: parent.verticalCenter } } + HiFiGlyphs { + id: titleSort + text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn + color: hifi.colors.baseGrayHighlight + size: hifi.fontSizes.tableHeadingIcon + anchors { + left: titleText.right + leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 + right: parent.right + rightMargin: hifi.dimensions.tablePadding + verticalCenter: titleText.verticalCenter + } + visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + } + Rectangle { width: 1 anchors { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index a3c5c02f9b..f185508457 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -208,6 +208,10 @@ ModalWindow { Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); + sortIndicatorColumn: 0 + sortIndicatorOrder: Qt.AscendingOrder + sortIndicatorVisible: true + model: FolderListModel { id: model nameFilters: selectionType.currentFilter @@ -228,7 +232,18 @@ ModalWindow { } } - onActiveFocusChanged: { + function updateSort() { + model.sortReversed = sortIndicatorColumn == 0 + ? (sortIndicatorOrder == Qt.DescendingOrder) + : (sortIndicatorOrder == Qt.AscendingOrder); // Date and size fields have opposite sense + model.sortField = sortIndicatorColumn + 1; + } + + onSortIndicatorColumnChanged: { updateSort(); } + + onSortIndicatorOrderChanged: { updateSort(); } + + onActiveFocusChanged: { if (activeFocus && currentRow == -1) { fileTableView.selection.select(0) } @@ -282,6 +297,7 @@ ModalWindow { role: "fileName" title: "Name" width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width + movable: false resizable: true } TableViewColumn { @@ -289,6 +305,7 @@ ModalWindow { role: "fileModified" title: "Date" width: 0.3 * fileTableView.width + movable: false resizable: true visible: !selectDirectory } @@ -296,6 +313,7 @@ ModalWindow { role: "fileSize" title: "Size" width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width + movable: false resizable: true visible: !selectDirectory } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 4d5052f086..640fe8625b 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -162,6 +162,7 @@ Item { readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9 readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24 readonly property real tableHeading: dimensions.largeScreen ? 12 : 10 + readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33 readonly property real tableText: dimensions.largeScreen ? 15 : 12 readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9 readonly property real iconButton: dimensions.largeScreen ? 13 : 9 From 38f74c184339267f33073ca31ead51a5c0038424 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 9 May 2016 17:45:30 -0700 Subject: [PATCH 066/264] Remove the last of the glow level --- interface/resources/qml/hifi/MenuOption.qml | 1 - interface/resources/shaders/glow_add.frag | 20 -------- .../resources/shaders/glow_add_separate.frag | 23 --------- interface/src/Menu.h | 1 - interface/src/ui/overlays/Overlay.cpp | 48 +++++-------------- interface/src/ui/overlays/Overlay.h | 7 --- libraries/entities/src/BoxEntityItem.cpp | 3 -- libraries/entities/src/EntityItem.cpp | 3 -- libraries/entities/src/EntityItem.h | 4 -- .../entities/src/EntityItemProperties.cpp | 5 -- libraries/entities/src/EntityItemProperties.h | 5 -- .../src/EntityItemPropertiesDefaults.h | 1 - libraries/entities/src/LineEntityItem.cpp | 4 -- libraries/entities/src/ModelEntityItem.cpp | 1 - .../entities/src/ParticleEffectEntityItem.cpp | 2 - libraries/entities/src/PolyLineEntityItem.cpp | 3 -- 16 files changed, 11 insertions(+), 120 deletions(-) delete mode 100644 interface/resources/shaders/glow_add.frag delete mode 100644 interface/resources/shaders/glow_add_separate.frag diff --git a/interface/resources/qml/hifi/MenuOption.qml b/interface/resources/qml/hifi/MenuOption.qml index 9d18b56d57..46cf5d9662 100644 --- a/interface/resources/qml/hifi/MenuOption.qml +++ b/interface/resources/qml/hifi/MenuOption.qml @@ -76,7 +76,6 @@ QtObject { readonly property string forward: "Forward"; readonly property string frameTimer: "Show Timer"; readonly property string fullscreenMirror: "Mirror"; - readonly property string glowWhenSpeaking: "Glow When Speaking"; readonly property string help: "Help..."; readonly property string increaseAvatarSize: "Increase Avatar Size"; readonly property string independentMode: "Independent Mode"; diff --git a/interface/resources/shaders/glow_add.frag b/interface/resources/shaders/glow_add.frag deleted file mode 100644 index e8a1b504ea..0000000000 --- a/interface/resources/shaders/glow_add.frag +++ /dev/null @@ -1,20 +0,0 @@ -#version 120 - -// -// glow_add.frag -// fragment shader -// -// Created by Andrzej Kapolka on 8/14/13. -// Copyright 2013 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 -// - -// the texture containing the original color -uniform sampler2D originalTexture; - -void main(void) { - vec4 color = texture2D(originalTexture, gl_TexCoord[0].st); - gl_FragColor = color * (1.0 + color.a); -} diff --git a/interface/resources/shaders/glow_add_separate.frag b/interface/resources/shaders/glow_add_separate.frag deleted file mode 100644 index e798a3b11f..0000000000 --- a/interface/resources/shaders/glow_add_separate.frag +++ /dev/null @@ -1,23 +0,0 @@ -#version 120 - -// -// glow_add_separate.frag -// fragment shader -// -// Created by Andrzej Kapolka on 8/14/13. -// Copyright 2013 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 -// - -// the texture containing the original color -uniform sampler2D originalTexture; - -// the texture containing the blurred color -uniform sampler2D blurredTexture; - -void main(void) { - vec4 blurred = texture2D(blurredTexture, gl_TexCoord[0].st); - gl_FragColor = blurred * blurred.a + texture2D(originalTexture, gl_TexCoord[0].st) * (1.0 + blurred.a * 0.5); -} diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fdb136b900..adb3b41bed 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -111,7 +111,6 @@ namespace MenuOption { const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; const QString FullscreenMirror = "Mirror"; - const QString GlowWhenSpeaking = "Glow When Speaking"; const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index a53daf7d14..5d2c315a92 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -20,14 +20,12 @@ Overlay::Overlay() : _renderItemID(render::Item::INVALID_ITEM_ID), _isLoaded(true), _alpha(DEFAULT_ALPHA), - _glowLevel(0.0f), _pulse(0.0f), _pulseMax(0.0f), _pulseMin(0.0f), _pulsePeriod(1.0f), _pulseDirection(1.0f), _lastPulseUpdate(usecTimestampNow()), - _glowLevelPulse(0.0f), _alphaPulse(0.0f), _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), @@ -40,14 +38,12 @@ Overlay::Overlay(const Overlay* overlay) : _renderItemID(render::Item::INVALID_ITEM_ID), _isLoaded(overlay->_isLoaded), _alpha(overlay->_alpha), - _glowLevel(overlay->_glowLevel), _pulse(overlay->_pulse), _pulseMax(overlay->_pulseMax), _pulseMin(overlay->_pulseMin), _pulsePeriod(overlay->_pulsePeriod), _pulseDirection(overlay->_pulseDirection), _lastPulseUpdate(usecTimestampNow()), - _glowLevelPulse(overlay->_glowLevelPulse), _alphaPulse(overlay->_alphaPulse), _colorPulse(overlay->_colorPulse), _color(overlay->_color), @@ -69,10 +65,6 @@ void Overlay::setProperties(const QVariantMap& properties) { if (properties["alpha"].isValid()) { setAlpha(properties["alpha"].toFloat()); } - - if (properties["glowLevel"].isValid()) { - setGlowLevel(properties["glowLevel"].toFloat()); - } if (properties["pulseMax"].isValid()) { setPulseMax(properties["pulseMax"].toFloat()); @@ -86,10 +78,6 @@ void Overlay::setProperties(const QVariantMap& properties) { setPulsePeriod(properties["pulsePeriod"].toFloat()); } - if (properties["glowLevelPulse"].isValid()) { - setGlowLevelPulse(properties["glowLevelPulse"].toFloat()); - } - if (properties["alphaPulse"].isValid()) { setAlphaPulse(properties["alphaPulse"].toFloat()); } @@ -118,9 +106,6 @@ QVariant Overlay::getProperty(const QString& property) { if (property == "alpha") { return _alpha; } - if (property == "glowLevel") { - return _glowLevel; - } if (property == "pulseMax") { return _pulseMax; } @@ -130,9 +115,6 @@ QVariant Overlay::getProperty(const QString& property) { if (property == "pulsePeriod") { return _pulsePeriod; } - if (property == "glowLevelPulse") { - return _glowLevelPulse; - } if (property == "alphaPulse") { return _alphaPulse; } @@ -176,16 +158,8 @@ float Overlay::getAlpha() { return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); } -float Overlay::getGlowLevel() { - if (_glowLevelPulse == 0.0f) { - return _glowLevel; - } - float pulseLevel = updatePulse(); - return (_glowLevelPulse >= 0.0f) ? _glowLevel * pulseLevel : _glowLevel * (1.0f - pulseLevel); -} - -// glow level travels from min to max, then max to min in one period. +// pulse travels from min to max, then max to min in one period. float Overlay::updatePulse() { if (_pulsePeriod <= 0.0f) { return _pulse; @@ -196,25 +170,25 @@ float Overlay::updatePulse() { float elapsedPeriods = elapsedSeconds / _pulsePeriod; // we can safely remove any "full" periods, since those just rotate us back - // to our final glow level + // to our final pulse level elapsedPeriods = fmod(elapsedPeriods, 1.0f); _lastPulseUpdate = now; - float glowDistance = (_pulseMax - _pulseMin); - float glowDistancePerPeriod = glowDistance * 2.0f; + float pulseDistance = (_pulseMax - _pulseMin); + float pulseDistancePerPeriod = pulseDistance * 2.0f; - float glowDelta = _pulseDirection * glowDistancePerPeriod * elapsedPeriods; - float newGlow = _pulse + glowDelta; + float pulseDelta = _pulseDirection * pulseDistancePerPeriod * elapsedPeriods; + float newPulse = _pulse + pulseDelta; float limit = (_pulseDirection > 0.0f) ? _pulseMax : _pulseMin; - float passedLimit = (_pulseDirection > 0.0f) ? (newGlow >= limit) : (newGlow <= limit); + float passedLimit = (_pulseDirection > 0.0f) ? (newPulse >= limit) : (newPulse <= limit); if (passedLimit) { - float glowDeltaToLimit = newGlow - limit; - float glowDeltaFromLimitBack = glowDelta - glowDeltaToLimit; - glowDelta = -glowDeltaFromLimitBack; + float pulseDeltaToLimit = newPulse - limit; + float pulseDeltaFromLimitBack = pulseDelta - pulseDeltaToLimit; + pulseDelta = -pulseDeltaFromLimitBack; _pulseDirection *= -1.0f; } - _pulse += glowDelta; + _pulse += pulseDelta; return _pulse; } diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index a2cf7a30f2..466ec0e913 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -49,7 +49,6 @@ public: bool getVisible() const { return _visible; } xColor getColor(); float getAlpha(); - float getGlowLevel(); Anchor getAnchor() const { return _anchor; } float getPulseMax() const { return _pulseMax; } @@ -57,7 +56,6 @@ public: float getPulsePeriod() const { return _pulsePeriod; } float getPulseDirection() const { return _pulseDirection; } - float getGlowLevelPulse() const { return _glowLevelPulse; } float getColorPulse() const { return _colorPulse; } float getAlphaPulse() const { return _alphaPulse; } @@ -65,7 +63,6 @@ public: void setVisible(bool visible) { _visible = visible; } void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } - void setGlowLevel(float value) { _glowLevel = value; } void setAnchor(Anchor anchor) { _anchor = anchor; } void setPulseMax(float value) { _pulseMax = value; } @@ -73,8 +70,6 @@ public: void setPulsePeriod(float value) { _pulsePeriod = value; } void setPulseDirection(float value) { _pulseDirection = value; } - - void setGlowLevelPulse(float value) { _glowLevelPulse = value; } void setColorPulse(float value) { _colorPulse = value; } void setAlphaPulse(float value) { _alphaPulse = value; } @@ -92,7 +87,6 @@ protected: bool _isLoaded; float _alpha; - float _glowLevel; float _pulse; float _pulseMax; @@ -101,7 +95,6 @@ protected: float _pulseDirection; quint64 _lastPulseUpdate; - float _glowLevelPulse; // ratio of the pulse to the glow level float _alphaPulse; // ratio of the pulse to the alpha float _colorPulse; // ratio of the pulse to the color diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp index 061c5b3854..bf02d383ab 100644 --- a/libraries/entities/src/BoxEntityItem.cpp +++ b/libraries/entities/src/BoxEntityItem.cpp @@ -36,9 +36,6 @@ EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredPro properties._color = getXColor(); properties._colorChanged = false; - properties._glowLevel = getGlowLevel(); - properties._glowLevelChanged = false; - return properties; } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 187c4f51be..bd4292f75d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -46,7 +46,6 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _lastEditedFromRemoteInRemoteTime(0), _created(UNKNOWN_CREATED_TIME), _changedOnServer(0), - _glowLevel(ENTITY_ITEM_DEFAULT_GLOW_LEVEL), _localRenderAlpha(ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA), _density(ENTITY_ITEM_DEFAULT_DENSITY), _volumeMultiplier(1.0f), @@ -1116,7 +1115,6 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint); COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless); @@ -1211,7 +1209,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptTimestamp, setScriptTimestamp); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8868830c8a..4d3d93e40f 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -186,9 +186,6 @@ public: inline const glm::vec3 getDimensions() const { return getScale(); } virtual void setDimensions(const glm::vec3& value); - float getGlowLevel() const { return _glowLevel; } - void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; } - float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float localRenderAlpha) { _localRenderAlpha = localRenderAlpha; } @@ -460,7 +457,6 @@ protected: mutable bool _recalcMinAACube = true; mutable bool _recalcMaxAACube = true; - float _glowLevel; float _localRenderAlpha; float _density = ENTITY_ITEM_DEFAULT_DENSITY; // kg/m^3 // NOTE: _volumeMultiplier is used to allow some mass properties code exist in the EntityItem base class diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 738e8910fe..7893fe4a37 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -38,10 +38,8 @@ _idSet(false), _lastEdited(0), _type(EntityTypes::Unknown), -_glowLevel(0.0f), _localRenderAlpha(1.0f), -_glowLevelChanged(false), _localRenderAlphaChanged(false), _defaultSettings(true), @@ -542,7 +540,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); // FIXME - I don't think these properties are supported any more - //COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); return properties; @@ -582,7 +579,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(emitterShouldTrail , bool, setEmitterShouldTrail); COPY_PROPERTY_FROM_QSCRIPTVALUE(modelURL, QString, setModelURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(glowLevel, float, setGlowLevel); COPY_PROPERTY_FROM_QSCRIPTVALUE(localRenderAlpha, float, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionless, bool, setCollisionless); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ignoreForCollisions, bool, setCollisionless, getCollisionless); // legacy support @@ -1468,7 +1464,6 @@ void EntityItemProperties::markAllChanged() { _alphaChanged = true; _modelURLChanged = true; _compoundShapeURLChanged = true; - _glowLevelChanged = true; _localRenderAlphaChanged = true; _isSpotlightChanged = true; _collisionlessChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2cf31e5632..49981913b5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -218,9 +218,7 @@ public: bool containsPositionChange() const { return _positionChanged; } bool containsDimensionsChange() const { return _dimensionsChanged; } - float getGlowLevel() const { return _glowLevel; } float getLocalRenderAlpha() const { return _localRenderAlpha; } - void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, @@ -231,7 +229,6 @@ public: static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes, EntityItemID& entityID, EntityItemProperties& properties); - bool glowLevelChanged() const { return _glowLevelChanged; } bool localRenderAlphaChanged() const { return _localRenderAlphaChanged; } void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; } @@ -287,9 +284,7 @@ private: EntityTypes::EntityType _type; void setType(const QString& typeName) { _type = EntityTypes::getEntityTypeFromName(typeName); } - float _glowLevel; float _localRenderAlpha; - bool _glowLevelChanged; bool _localRenderAlphaChanged; bool _defaultSettings; bool _dimensionsInitialized = true; // Only false if creating an entity localy with no dimensions properties diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index d5d44bb4a8..4ec0bea7df 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -31,7 +31,6 @@ const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid(); const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; -const float ENTITY_ITEM_DEFAULT_GLOW_LEVEL = 0.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 78b6107d88..7b36856794 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -53,10 +53,6 @@ EntityItemProperties LineEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints); - - properties._glowLevel = getGlowLevel(); - properties._glowLevelChanged = false; - return properties; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 2ec1f5e9ed..40faf2c3c3 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -58,7 +58,6 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotationsSet, getJointRotationsSet); diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 02a86ab952..a7bd0038e6 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -304,7 +304,6 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); // FIXME - this doesn't appear to get used COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan); @@ -343,7 +342,6 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index b0b78bc9c5..91fa6c4103 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -58,9 +58,6 @@ EntityItemProperties PolyLineEntityItem::getProperties(EntityPropertyFlags desir COPY_ENTITY_PROPERTY_TO_PROPERTIES(normals, getNormals); COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); - - properties._glowLevel = getGlowLevel(); - properties._glowLevelChanged = false; return properties; } From 5acbed647ca1f47473b6ca37cc6ecd40ebe54021 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 11 May 2016 15:48:44 -0700 Subject: [PATCH 067/264] Remove glowLevel references in scripts --- script-archive/libraries/overlayManager.js | 2 +- script-archive/sit.js | 2 -- scripts/system/edit.js | 6 ------ 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/script-archive/libraries/overlayManager.js b/script-archive/libraries/overlayManager.js index 4438193313..7e25cc04ec 100644 --- a/script-archive/libraries/overlayManager.js +++ b/script-archive/libraries/overlayManager.js @@ -205,7 +205,7 @@ }; return generateOverlayClass(that, ABSTRACT, [ - "alpha", "glowLevel", "pulseMax", "pulseMin", "pulsePeriod", "glowLevelPulse", + "alpha", "pulseMax", "pulseMin", "pulsePeriod", "alphaPulse", "colorPulse", "visible", "anchor" ]); })(); diff --git a/script-archive/sit.js b/script-archive/sit.js index 70cb086e36..7d86e2cc75 100644 --- a/script-archive/sit.js +++ b/script-archive/sit.js @@ -319,8 +319,6 @@ function addIndicators(modelID) { } models[modelID] = modelID; - } else { - Entities.editEntity(modelID, { glowLevel: 0.0 }); } } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a8774db228..5f0317da7c 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -63,7 +63,6 @@ var MIN_ANGULAR_SIZE = 2; var MAX_ANGULAR_SIZE = 45; var allowLargeModels = true; var allowSmallModels = true; -var wantEntityGlow = false; var SPAWN_DISTANCE = 1; var DEFAULT_DIMENSION = 0.20; @@ -800,11 +799,6 @@ function highlightEntityUnderCursor(position, accurateRay) { var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); if (entityIntersection.entityID && sizeOK) { - if (wantEntityGlow) { - Entities.editEntity(entityIntersection.entityID, { - glowLevel: 0.25 - }); - } highlightedEntityID = entityIntersection.entityID; selectionDisplay.highlightSelectable(entityIntersection.entityID); } From 0878d87ac7d1a5fe8b1e529796eeaeba6855dc6a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 12 May 2016 11:09:52 -0700 Subject: [PATCH 068/264] remove some unnecessary dependencies --- libraries/animation/CMakeLists.txt | 2 +- libraries/entities-renderer/CMakeLists.txt | 2 +- libraries/entities/CMakeLists.txt | 2 +- libraries/fbx/CMakeLists.txt | 2 +- libraries/model-networking/CMakeLists.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/animation/CMakeLists.txt b/libraries/animation/CMakeLists.txt index 3cf7bfa295..dd2fdaaabc 100644 --- a/libraries/animation/CMakeLists.txt +++ b/libraries/animation/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME animation) setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu model fbx) +link_hifi_libraries(shared model fbx) diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 7023d285ff..bb90c04c95 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME entities-renderer) AUTOSCRIBE_SHADER_LIB(gpu model render render-utils) setup_hifi_library(Widgets Network Script) -link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils gl) +link_hifi_libraries(shared gpu procedural model model-networking script-engine render render-utils) target_bullet() diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 7bea5c6088..1230fe8146 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) -link_hifi_libraries(avatars shared audio octree gpu model fbx networking animation) +link_hifi_libraries(avatars shared audio octree model fbx networking animation) target_bullet() diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt index e10b6bcfd5..d9f4aaf03e 100644 --- a/libraries/fbx/CMakeLists.txt +++ b/libraries/fbx/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME fbx) setup_hifi_library() -link_hifi_libraries(shared gpu model networking octree) +link_hifi_libraries(shared model networking) diff --git a/libraries/model-networking/CMakeLists.txt b/libraries/model-networking/CMakeLists.txt index 29ab17f2ec..ed8cd7b5f9 100644 --- a/libraries/model-networking/CMakeLists.txt +++ b/libraries/model-networking/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME model-networking) setup_hifi_library() -link_hifi_libraries(shared networking gpu model fbx) +link_hifi_libraries(shared networking model fbx) From 419ff3bbeaf7dbf2d11408c1a442c6f976166702 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 12 May 2016 13:49:43 -0700 Subject: [PATCH 069/264] Track request lifetime in lambda callbacks --- libraries/networking/src/AssetClient.cpp | 4 +--- libraries/networking/src/AssetRequest.cpp | 14 +++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 080b0c9b90..c4ec0ad61e 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -202,7 +202,7 @@ AssetUpload* AssetClient::createUpload(const QByteArray& data) { } MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end, - ReceivedAssetCallback callback, ProgressCallback progressCallback) { + ReceivedAssetCallback callback, ProgressCallback progressCallback) { Q_ASSERT(QThread::currentThread() == thread()); if (hash.length() != SHA256_HASH_HEX_LENGTH) { @@ -238,8 +238,6 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse callback(false, AssetServerError::NoError, QByteArray()); return INVALID_MESSAGE_ID; } - - } MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) { diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 594c471196..4f0e812031 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -106,9 +106,13 @@ void AssetRequest::start() { int start = 0, end = _info.size; auto assetClient = DependencyManager::get(); + auto that = QPointer(this); // Used to track the request's lifetime _assetRequestID = assetClient->getAsset(_hash, start, end, - [this, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) { - + [this, that, start, end](bool responseReceived, AssetServerError serverError, const QByteArray& data) { + if (!that) { + // If the request is dead, return + return; + } _assetRequestID = AssetClient::INVALID_MESSAGE_ID; if (!responseReceived) { @@ -148,7 +152,11 @@ void AssetRequest::start() { _state = Finished; emit finished(this); - }, [this](qint64 totalReceived, qint64 total) { + }, [this, that](qint64 totalReceived, qint64 total) { + if (!that) { + // If the request is dead, return + return; + } emit progress(totalReceived, total); }); }); From 880b52aad288ec91d73fdfc478acbe57a110eb3e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 16 May 2016 09:52:48 -0700 Subject: [PATCH 070/264] update attachedEntitiesManager to work better with avatarEntities --- scripts/system/attachedEntitiesManager.js | 104 ++++++++++++---------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/scripts/system/attachedEntitiesManager.js b/scripts/system/attachedEntitiesManager.js index 780cfe06fb..79c5973a6e 100644 --- a/scripts/system/attachedEntitiesManager.js +++ b/scripts/system/attachedEntitiesManager.js @@ -117,10 +117,11 @@ function AttachedEntitiesManager() { this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) { // if this is still equipped, just rewrite the position information. var grabData = getEntityCustomData('grabKey', grabbedEntity, {}); - if ("refCount" in grabData && grabData.refCount > 0) { - manager.updateRelativeOffsets(grabbedEntity); - return; - } + // if ("refCount" in grabData && grabData.refCount > 0) { + // // for adjusting things in your other hand + // manager.updateRelativeOffsets(grabbedEntity); + // return; + // } var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; @@ -156,9 +157,11 @@ function AttachedEntitiesManager() { wearProps.parentID = MyAvatar.sessionUUID; wearProps.parentJointIndex = bestJointIndex; + var updatePresets = false; if (bestJointOffset && bestJointOffset.constructor === Array) { if (!clothingLocked || bestJointOffset.length < 2) { - this.updateRelativeOffsets(grabbedEntity); + // we're unlocked or this thing didn't have a preset position, so update it + updatePresets = true; } else { // don't snap the entity to the preferred position if unlocked wearProps.localPosition = bestJointOffset[0]; @@ -167,7 +170,10 @@ function AttachedEntitiesManager() { } Entities.deleteEntity(grabbedEntity); - Entities.addEntity(wearProps, true); + grabbedEntity = Entities.addEntity(wearProps, true); + if (updatePresets) { + this.updateRelativeOffsets(grabbedEntity); + } } else if (props.parentID != NULL_UUID) { // drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand. if (props.parentID === MyAvatar.sessionUUID && @@ -225,20 +231,20 @@ function AttachedEntitiesManager() { } } - this.saveAttachedEntities = function() { - print("--- saving attached entities ---"); - saveData = []; - var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); - for (i = 0; i < nearbyEntities.length; i++) { - var entityID = nearbyEntities[i]; - if (this.updateRelativeOffsets(entityID)) { - var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them - this.scrubProperties(props); - saveData.push(props); - } - } - Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); - } + // this.saveAttachedEntities = function() { + // print("--- saving attached entities ---"); + // saveData = []; + // var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE); + // for (i = 0; i < nearbyEntities.length; i++) { + // var entityID = nearbyEntities[i]; + // if (this.updateRelativeOffsets(entityID)) { + // var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them + // this.scrubProperties(props); + // saveData.push(props); + // } + // } + // Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData)); + // } this.scrubProperties = function(props) { var toScrub = ["queryAACube", "position", "rotation", @@ -262,37 +268,37 @@ function AttachedEntitiesManager() { } } - this.loadAttachedEntities = function(grabbedEntity) { - print("--- loading attached entities ---"); - jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); - var loadData = []; - try { - loadData = JSON.parse(jsonAttachmentData); - } catch (e) { - print('error parsing saved attachment data'); - return; - } + // this.loadAttachedEntities = function(grabbedEntity) { + // print("--- loading attached entities ---"); + // jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY); + // var loadData = []; + // try { + // loadData = JSON.parse(jsonAttachmentData); + // } catch (e) { + // print('error parsing saved attachment data'); + // return; + // } - for (i = 0; i < loadData.length; i ++) { - var savedProps = loadData[ i ]; - var currentProps = Entities.getEntityProperties(savedProps.id); - if (currentProps.id == savedProps.id && - // TODO -- also check that parentJointIndex matches? - currentProps.parentID == MyAvatar.sessionUUID) { - // entity is already in-world. TODO -- patch it up? - continue; - } - this.scrubProperties(savedProps); - delete savedProps["id"]; - savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions - var loadedEntityID = Entities.addEntity(savedProps, true); + // for (i = 0; i < loadData.length; i ++) { + // var savedProps = loadData[ i ]; + // var currentProps = Entities.getEntityProperties(savedProps.id); + // if (currentProps.id == savedProps.id && + // // TODO -- also check that parentJointIndex matches? + // currentProps.parentID == MyAvatar.sessionUUID) { + // // entity is already in-world. TODO -- patch it up? + // continue; + // } + // this.scrubProperties(savedProps); + // delete savedProps["id"]; + // savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions + // var loadedEntityID = Entities.addEntity(savedProps, true); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'loaded', - grabbedEntity: loadedEntityID - })); - } - } + // Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + // action: 'loaded', + // grabbedEntity: loadedEntityID + // })); + // } + // } } var manager = new AttachedEntitiesManager(); From 61fbb7022c1b251e6a8f784a475f38814fccf664 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 16 May 2016 10:40:11 -0700 Subject: [PATCH 071/264] Mv entities script engine deleter to fn --- .../src/EntityTreeRenderer.cpp | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index d7f9f1343b..bec2fa9b8d 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -78,13 +78,8 @@ EntityTreeRenderer::~EntityTreeRenderer() { int EntityTreeRenderer::_entitiesScriptEngineCount = 0; -void EntityTreeRenderer::resetEntitiesScriptEngine() { - // Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use - auto oldEngine = _entitiesScriptEngine; - - auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)); - _entitiesScriptEngine = QSharedPointer(newEngine, [](ScriptEngine* engine){ - class WaitRunnable : public QRunnable { +void entitiesScriptEngineDeleter(ScriptEngine* engine) { + class WaitRunnable : public QRunnable { public: WaitRunnable(ScriptEngine* engine) : _engine(engine) {} virtual void run() override { @@ -94,10 +89,18 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { private: ScriptEngine* _engine; - }; - // Wait for the scripting thread from the thread pool to avoid hanging the main thread - QThreadPool::globalInstance()->start(new WaitRunnable(engine)); - }); + }; + + // Wait for the scripting thread from the thread pool to avoid hanging the main thread + QThreadPool::globalInstance()->start(new WaitRunnable(engine)); +} + +void EntityTreeRenderer::resetEntitiesScriptEngine() { + // Keep a ref to oldEngine until newEngine is ready so EntityScriptingInterface has something to use + auto oldEngine = _entitiesScriptEngine; + + auto newEngine = new ScriptEngine(NO_SCRIPT, QString("Entities %1").arg(++_entitiesScriptEngineCount)); + _entitiesScriptEngine = QSharedPointer(newEngine, entitiesScriptEngineDeleter); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine.data()); _entitiesScriptEngine->runInThread(); From acb56d0603d1d7ddac7c3290010c9850ca72f20d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 16 May 2016 12:35:05 -0700 Subject: [PATCH 072/264] Larger edit handles, and more tolerance in click (vs drag). --- scripts/system/edit.js | 4 ++-- scripts/system/libraries/entitySelectionTool.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a8774db228..22cfa9d330 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -720,7 +720,7 @@ var mouseCapturedByTool = false; var lastMousePosition = null; var idleMouseTimerId = null; var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms -var CLICK_MOVE_DISTANCE_THRESHOLD = 8; +var CLICK_MOVE_DISTANCE_THRESHOLD = 20; var IDLE_MOUSE_TIMEOUT = 200; var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0; @@ -1877,4 +1877,4 @@ entityListTool.webView.webEventReceived.connect(function(data) { } } } -}); \ No newline at end of file +}); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 5b6c15def6..e46306ca31 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -304,7 +304,7 @@ SelectionDisplay = (function() { var previousHandleColor; var previousHandleAlpha; - var grabberSizeCorner = 0.025; + var grabberSizeCorner = 0.025; // These get resized by updateHandleSizes(). var grabberSizeEdge = 0.015; var grabberSizeFace = 0.025; var grabberAlpha = 1; @@ -4320,7 +4320,7 @@ SelectionDisplay = (function() { that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO; + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 2; var dimensions = SelectionManager.worldDimensions; var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; grabberSize = Math.min(grabberSize, avgDimension / 10); @@ -4402,4 +4402,4 @@ SelectionDisplay = (function() { return that; -}()); \ No newline at end of file +}()); From 5ba8372c2136b7cf95f2e3a58c26e1d94784cf6b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 14 May 2016 10:01:34 -0700 Subject: [PATCH 073/264] Moving GL backend out of GPU --- interface/CMakeLists.txt | 2 +- interface/src/Application.cpp | 5 +- interface/src/ui/ApplicationOverlay.cpp | 9 +- libraries/display-plugins/CMakeLists.txt | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 8 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 1 - .../stereo/StereoDisplayPlugin.cpp | 1 - libraries/gl/src/gl/Config.h | 36 +--- libraries/gl/src/gl/OpenGLVersionChecker.cpp | 6 +- libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 11 ++ libraries/gl/src/gl/QOpenGLContextWrapper.h | 3 + libraries/gpu-gl/CMakeLists.txt | 8 + .../gpu => gpu-gl/src/gpu/gl}/GLBackend.cpp | 180 ++++++++---------- .../src/gpu => gpu-gl/src/gpu/gl}/GLBackend.h | 46 ++++- .../src/gpu/gl}/GLBackendBuffer.cpp | 2 + .../src/gpu/gl}/GLBackendInput.cpp | 2 + .../src/gpu/gl}/GLBackendOutput.cpp | 40 ++-- .../src/gpu/gl}/GLBackendPipeline.cpp | 22 +-- .../src/gpu/gl}/GLBackendQuery.cpp | 34 +--- .../src/gpu/gl}/GLBackendShader.cpp | 60 ++---- .../src/gpu/gl/GLBackendShared.cpp} | 76 ++++++-- .../src/gpu/gl}/GLBackendShared.h | 18 +- .../src/gpu/gl}/GLBackendState.cpp | 4 +- .../src/gpu/gl}/GLBackendTexture.cpp | 9 +- .../src/gpu/gl}/GLBackendTextureTransfer.cpp | 9 +- .../src/gpu/gl}/GLBackendTextureTransfer.h | 5 +- .../src/gpu/gl}/GLBackendTransform.cpp | 4 +- libraries/gpu/CMakeLists.txt | 3 - libraries/gpu/src/gpu/Forward.h | 6 + libraries/gpu/src/gpu/Resource.h | 2 +- libraries/gpu/src/gpu/Texture.cpp | 9 + libraries/gpu/src/gpu/Texture.h | 2 + libraries/procedural/CMakeLists.txt | 2 +- .../src/OculusLegacyDisplayPlugin.cpp | 1 - tests/gpu-test/CMakeLists.txt | 2 +- tests/gpu-test/src/main.cpp | 6 +- tests/render-utils/CMakeLists.txt | 2 +- tests/render-utils/src/main.cpp | 4 +- tests/shaders/CMakeLists.txt | 2 +- tests/shaders/src/main.cpp | 4 +- 40 files changed, 320 insertions(+), 328 deletions(-) create mode 100644 libraries/gpu-gl/CMakeLists.txt rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackend.cpp (83%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackend.h (96%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendBuffer.cpp (98%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendInput.cpp (99%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendOutput.cpp (85%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendPipeline.cpp (92%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendQuery.cpp (68%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendShader.cpp (92%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu/GLBackendTexelFormat.cpp => gpu-gl/src/gpu/gl/GLBackendShared.cpp} (81%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendShared.h (76%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendState.cpp (99%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendTexture.cpp (98%) mode change 100755 => 100644 rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendTextureTransfer.cpp (98%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendTextureTransfer.h (94%) rename libraries/{gpu/src/gpu => gpu-gl/src/gpu/gl}/GLBackendTransform.cpp (99%) mode change 100755 => 100644 diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 84aa0328b0..4f58cf73db 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -139,7 +139,7 @@ if (WIN32) endif() # link required hifi libraries -link_hifi_libraries(shared octree gpu gl procedural model render +link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render recording fbx networking model-networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 10694eb5d4..9465ed1535 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -60,7 +60,7 @@ #include #include #include -#include +#include #include #include #include @@ -1262,8 +1262,7 @@ void Application::initializeGL() { _isGLInitialized = true; } - // Where the gpuContext is initialized and where the TRUE Backend is created and assigned - gpu::Context::init(); + gpu::Context::init(); _gpuContext = std::make_shared(); // The gpu context can make child contexts for transfers, so // we need to restore primary rendering context diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 02b2de8838..2395f62468 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -13,12 +13,12 @@ #include #include -#include #include #include #include #include #include +#include #include "AudioClient.h" #include "audio/AudioScope.h" @@ -55,7 +55,6 @@ ApplicationOverlay::~ApplicationOverlay() { // Renders the overlays either to a texture or to the screen void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { PROFILE_RANGE(__FUNCTION__); - CHECK_GL_ERROR(); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); buildFramebufferObject(); @@ -89,8 +88,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { }); renderArgs->_batch = nullptr; // so future users of renderArgs don't try to use our batch - - CHECK_GL_ERROR(); } void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { @@ -295,10 +292,6 @@ gpu::TexturePointer ApplicationOverlay::acquireOverlay() { return gpu::TexturePointer(); } auto result = _overlayFramebuffer->getRenderBuffer(0); - auto textureId = gpu::GLBackend::getTextureID(result, false); - if (!textureId) { - qDebug() << "Missing texture"; - } _overlayFramebuffer->setRenderBuffer(0, gpu::TexturePointer()); return result; } diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index cfa2ac0710..f2d58d825e 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins gl ui) +link_hifi_libraries(shared plugins gl gpu-gl ui) target_opengl() diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index a0b6ae3939..d34b698410 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -17,7 +17,7 @@ #include #include - +#include #include #include #include @@ -26,7 +26,6 @@ #include #include #include -#include #include #include "CompositorHelper.h" @@ -629,14 +628,15 @@ uint32_t OpenGLDisplayPlugin::getSceneTextureId() const { if (!_currentSceneTexture) { return 0; } - return gpu::GLBackend::getTextureID(_currentSceneTexture, false); + + return _currentSceneTexture->getHardwareId(); } uint32_t OpenGLDisplayPlugin::getOverlayTextureId() const { if (!_currentOverlayTexture) { return 0; } - return gpu::GLBackend::getTextureID(_currentOverlayTexture, false); + return _currentOverlayTexture->getHardwareId(); } void OpenGLDisplayPlugin::eyeViewport(Eye eye) const { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 372337ae4d..4e594d89ed 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include #include diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 66a4ba4835..6c6716c8fa 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -13,7 +13,6 @@ #include #include -#include #include #include #include diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index 8f7582c271..593537a291 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -12,47 +12,21 @@ #ifndef hifi_gpu_GPUConfig_h #define hifi_gpu_GPUConfig_h - #define GL_GLEXT_PROTOTYPES 1 -#define GPU_CORE 1 -#define GPU_LEGACY 0 -#define GPU_CORE_41 410 -#define GPU_CORE_43 430 -#define GPU_CORE_MINIMUM GPU_CORE_41 - -#if defined(__APPLE__) - #include -#define GPU_FEATURE_PROFILE GPU_CORE -#define GPU_INPUT_PROFILE GPU_CORE_41 +#if defined(__APPLE__) #include #include -#elif defined(WIN32) -#include +#endif + +#if defined(WIN32) + #include -#define GPU_FEATURE_PROFILE GPU_CORE -#define GPU_INPUT_PROFILE GPU_CORE_43 - -#else - -#include - -#define GPU_FEATURE_PROFILE GPU_CORE -#define GPU_INPUT_PROFILE GPU_CORE_43 - #endif - -#if (GPU_INPUT_PROFILE == GPU_CORE_43) -// Deactivate SSBO for now, we've run into some issues -// on GL 4.3 capable GPUs not behaving as expected -//#define GPU_SSBO_DRAW_CALL_INFO -#endif - - #endif // hifi_gpu_GPUConfig_h diff --git a/libraries/gl/src/gl/OpenGLVersionChecker.cpp b/libraries/gl/src/gl/OpenGLVersionChecker.cpp index 843385f720..ed33dc1ab9 100644 --- a/libraries/gl/src/gl/OpenGLVersionChecker.cpp +++ b/libraries/gl/src/gl/OpenGLVersionChecker.cpp @@ -19,6 +19,8 @@ #include "GLWidget.h" #include "GLHelpers.h" +#define MINIMUM_GL_VERSION 410 + OpenGLVersionChecker::OpenGLVersionChecker(int& argc, char** argv) : QApplication(argc, argv) { @@ -58,8 +60,8 @@ QJsonObject OpenGLVersionChecker::checkVersion(bool& valid, bool& override) { QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]")); int majorNumber = versionParts[0].toInt(); int minorNumber = versionParts[1].toInt(); - int minimumMajorNumber = GPU_CORE_MINIMUM / 100; - int minimumMinorNumber = (GPU_CORE_MINIMUM - minimumMajorNumber * 100) / 10; + int minimumMajorNumber = MINIMUM_GL_VERSION / 100; + int minimumMinorNumber = (MINIMUM_GL_VERSION - minimumMajorNumber * 100) / 10; valid = (majorNumber > minimumMajorNumber || (majorNumber == minimumMajorNumber && minorNumber >= minimumMinorNumber)); diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 8c9a1ba113..bc8afc3927 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,6 +13,17 @@ #include +uint32_t QOpenGLContextWrapper::currentContextVersion() { + QOpenGLContext* context = QOpenGLContext::currentContext(); + if (!context) { + return 0; + } + auto format = context->format(); + auto version = (format.majorVersion() << 8) + format.minorVersion(); + return version; +} + + QOpenGLContext* QOpenGLContextWrapper::currentContext() { return QOpenGLContext::currentContext(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index 33cc0b25e9..d097284e68 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -12,6 +12,8 @@ #ifndef hifi_QOpenGLContextWrapper_h #define hifi_QOpenGLContextWrapper_h +#include + class QOpenGLContext; class QSurface; class QSurfaceFormat; @@ -30,6 +32,7 @@ public: void moveToThread(QThread* thread); static QOpenGLContext* currentContext(); + static uint32_t currentContextVersion(); QOpenGLContext* getContext() { return _context; diff --git a/libraries/gpu-gl/CMakeLists.txt b/libraries/gpu-gl/CMakeLists.txt new file mode 100644 index 0000000000..dfac6dd516 --- /dev/null +++ b/libraries/gpu-gl/CMakeLists.txt @@ -0,0 +1,8 @@ +set(TARGET_NAME gpu-gl) +AUTOSCRIBE_SHADER_LIB(gpu) +setup_hifi_library() +link_hifi_libraries(shared gl gpu) +GroupSources("src") + +target_glew() +target_opengl() \ No newline at end of file diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp similarity index 83% rename from libraries/gpu/src/gpu/GLBackend.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 728d18bd8e..69699e673b 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -8,112 +8,133 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackendShared.h" +#include "GLBackend.h" #include #include #include +#include #include -#include -#include #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" #endif +#include +#include +#include "GLBackendShared.h" using namespace gpu; +using namespace gpu::gl; GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = { - (&::gpu::GLBackend::do_draw), - (&::gpu::GLBackend::do_drawIndexed), - (&::gpu::GLBackend::do_drawInstanced), - (&::gpu::GLBackend::do_drawIndexedInstanced), - (&::gpu::GLBackend::do_multiDrawIndirect), - (&::gpu::GLBackend::do_multiDrawIndexedIndirect), + (&::gpu::gl::GLBackend::do_draw), + (&::gpu::gl::GLBackend::do_drawIndexed), + (&::gpu::gl::GLBackend::do_drawInstanced), + (&::gpu::gl::GLBackend::do_drawIndexedInstanced), + (&::gpu::gl::GLBackend::do_multiDrawIndirect), + (&::gpu::gl::GLBackend::do_multiDrawIndexedIndirect), - (&::gpu::GLBackend::do_setInputFormat), - (&::gpu::GLBackend::do_setInputBuffer), - (&::gpu::GLBackend::do_setIndexBuffer), - (&::gpu::GLBackend::do_setIndirectBuffer), + (&::gpu::gl::GLBackend::do_setInputFormat), + (&::gpu::gl::GLBackend::do_setInputBuffer), + (&::gpu::gl::GLBackend::do_setIndexBuffer), + (&::gpu::gl::GLBackend::do_setIndirectBuffer), - (&::gpu::GLBackend::do_setModelTransform), - (&::gpu::GLBackend::do_setViewTransform), - (&::gpu::GLBackend::do_setProjectionTransform), - (&::gpu::GLBackend::do_setViewportTransform), - (&::gpu::GLBackend::do_setDepthRangeTransform), + (&::gpu::gl::GLBackend::do_setModelTransform), + (&::gpu::gl::GLBackend::do_setViewTransform), + (&::gpu::gl::GLBackend::do_setProjectionTransform), + (&::gpu::gl::GLBackend::do_setViewportTransform), + (&::gpu::gl::GLBackend::do_setDepthRangeTransform), - (&::gpu::GLBackend::do_setPipeline), - (&::gpu::GLBackend::do_setStateBlendFactor), - (&::gpu::GLBackend::do_setStateScissorRect), + (&::gpu::gl::GLBackend::do_setPipeline), + (&::gpu::gl::GLBackend::do_setStateBlendFactor), + (&::gpu::gl::GLBackend::do_setStateScissorRect), - (&::gpu::GLBackend::do_setUniformBuffer), - (&::gpu::GLBackend::do_setResourceTexture), + (&::gpu::gl::GLBackend::do_setUniformBuffer), + (&::gpu::gl::GLBackend::do_setResourceTexture), - (&::gpu::GLBackend::do_setFramebuffer), - (&::gpu::GLBackend::do_clearFramebuffer), - (&::gpu::GLBackend::do_blit), - (&::gpu::GLBackend::do_generateTextureMips), + (&::gpu::gl::GLBackend::do_setFramebuffer), + (&::gpu::gl::GLBackend::do_clearFramebuffer), + (&::gpu::gl::GLBackend::do_blit), + (&::gpu::gl::GLBackend::do_generateTextureMips), - (&::gpu::GLBackend::do_beginQuery), - (&::gpu::GLBackend::do_endQuery), - (&::gpu::GLBackend::do_getQuery), + (&::gpu::gl::GLBackend::do_beginQuery), + (&::gpu::gl::GLBackend::do_endQuery), + (&::gpu::gl::GLBackend::do_getQuery), - (&::gpu::GLBackend::do_resetStages), + (&::gpu::gl::GLBackend::do_resetStages), - (&::gpu::GLBackend::do_runLambda), + (&::gpu::gl::GLBackend::do_runLambda), - (&::gpu::GLBackend::do_startNamedCall), - (&::gpu::GLBackend::do_stopNamedCall), + (&::gpu::gl::GLBackend::do_startNamedCall), + (&::gpu::gl::GLBackend::do_stopNamedCall), - (&::gpu::GLBackend::do_glActiveBindTexture), + (&::gpu::gl::GLBackend::do_glActiveBindTexture), - (&::gpu::GLBackend::do_glUniform1i), - (&::gpu::GLBackend::do_glUniform1f), - (&::gpu::GLBackend::do_glUniform2f), - (&::gpu::GLBackend::do_glUniform3f), - (&::gpu::GLBackend::do_glUniform4f), - (&::gpu::GLBackend::do_glUniform3fv), - (&::gpu::GLBackend::do_glUniform4fv), - (&::gpu::GLBackend::do_glUniform4iv), - (&::gpu::GLBackend::do_glUniformMatrix4fv), + (&::gpu::gl::GLBackend::do_glUniform1i), + (&::gpu::gl::GLBackend::do_glUniform1f), + (&::gpu::gl::GLBackend::do_glUniform2f), + (&::gpu::gl::GLBackend::do_glUniform3f), + (&::gpu::gl::GLBackend::do_glUniform4f), + (&::gpu::gl::GLBackend::do_glUniform3fv), + (&::gpu::gl::GLBackend::do_glUniform4fv), + (&::gpu::gl::GLBackend::do_glUniform4iv), + (&::gpu::gl::GLBackend::do_glUniformMatrix4fv), - (&::gpu::GLBackend::do_glColor4f), + (&::gpu::gl::GLBackend::do_glColor4f), - (&::gpu::GLBackend::do_pushProfileRange), - (&::gpu::GLBackend::do_popProfileRange), + (&::gpu::gl::GLBackend::do_pushProfileRange), + (&::gpu::gl::GLBackend::do_popProfileRange), }; +extern std::function TEXTURE_ID_RESOLVER; + void GLBackend::init() { static std::once_flag once; std::call_once(once, [] { + + TEXTURE_ID_RESOLVER = [](const Texture& texture)->uint32 { + auto object = Backend::getGPUObject(texture); + if (!object) { + return 0; + } + + if (object->getSyncState() != GLTexture::Idle) { + if (object->_downsampleSource) { + return object->_downsampleSource->_texture; + } + return 0; + } + return object->_texture; + }; + QString vendor{ (const char*)glGetString(GL_VENDOR) }; QString renderer{ (const char*)glGetString(GL_RENDERER) }; - qCDebug(gpulogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); - qCDebug(gpulogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); - qCDebug(gpulogging) << "GL Vendor: " << vendor; - qCDebug(gpulogging) << "GL Renderer: " << renderer; + qCDebug(gpugllogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); + qCDebug(gpugllogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); + qCDebug(gpugllogging) << "GL Vendor: " << vendor; + qCDebug(gpugllogging) << "GL Renderer: " << renderer; GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer); // From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers. - qCDebug(gpulogging) << "GPU:"; - qCDebug(gpulogging) << "\tcard:" << gpu->getName(); - qCDebug(gpulogging) << "\tdriver:" << gpu->getDriver(); - qCDebug(gpulogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; + qCDebug(gpugllogging) << "GPU:"; + qCDebug(gpugllogging) << "\tcard:" << gpu->getName(); + qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver(); + qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB"; glewExperimental = true; GLenum err = glewInit(); glGetError(); // clear the potential error from glewExperimental if (GLEW_OK != err) { // glewInit failed, something is seriously wrong. - qCDebug(gpulogging, "Error: %s\n", glewGetErrorString(err)); + qCDebug(gpugllogging, "Error: %s\n", glewGetErrorString(err)); } - qCDebug(gpulogging, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); + qCDebug(gpugllogging, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); #if defined(Q_OS_WIN) if (wglewGetExtension("WGL_EXT_swap_control")) { int swapInterval = wglGetSwapIntervalEXT(); - qCDebug(gpulogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); } #endif @@ -121,7 +142,7 @@ void GLBackend::init() { // TODO: Write the correct code for Linux... /* if (wglewGetExtension("WGL_EXT_swap_control")) { int swapInterval = wglGetSwapIntervalEXT(); - qCDebug(gpulogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); + qCDebug(gpugllogging, "V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF")); }*/ #endif }); @@ -316,47 +337,6 @@ void GLBackend::render(Batch& batch) { _stereo._enable = savedStereo; } -bool GLBackend::checkGLError(const char* name) { - GLenum error = glGetError(); - if (!error) { - return false; - } - else { - switch (error) { - case GL_INVALID_ENUM: - qCDebug(gpulogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_INVALID_VALUE: - qCDebug(gpulogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; - break; - case GL_INVALID_OPERATION: - qCDebug(gpulogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; - break; - case GL_INVALID_FRAMEBUFFER_OPERATION: - qCDebug(gpulogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_OUT_OF_MEMORY: - qCDebug(gpulogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; - break; - case GL_STACK_UNDERFLOW: - qCDebug(gpulogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; - break; - case GL_STACK_OVERFLOW: - qCDebug(gpulogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; - break; - } - return true; - } -} - -bool GLBackend::checkGLErrorDebug(const char* name) { -#ifdef DEBUG - return checkGLError(name); -#else - Q_UNUSED(name); - return false; -#endif -} void GLBackend::syncCache() { syncTransformStateCache(); diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h similarity index 96% rename from libraries/gpu/src/gpu/GLBackend.h rename to libraries/gpu-gl/src/gpu/gl/GLBackend.h index 672a29538b..16bb1cc394 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -19,18 +19,46 @@ #include #include +#include + #include -#include "Context.h" +#include +#include -namespace gpu { + +#define GPU_CORE 1 +#define GPU_LEGACY 0 +#define GPU_CORE_41 410 +#define GPU_CORE_43 430 +#define GPU_CORE_MINIMUM GPU_CORE_41 +#define GPU_FEATURE_PROFILE GPU_CORE + +#if defined(__APPLE__) + +#define GPU_INPUT_PROFILE GPU_CORE_41 + +#else + +#define GPU_INPUT_PROFILE GPU_CORE_43 + +#endif + + +#if (GPU_INPUT_PROFILE == GPU_CORE_43) +// Deactivate SSBO for now, we've run into some issues +// on GL 4.3 capable GPUs not behaving as expected +//#define GPU_SSBO_DRAW_CALL_INFO +#endif + +namespace gpu { namespace gl { class GLTextureTransferHelper; class GLBackend : public Backend { // Context Backend static interface required - friend class Context; + friend class gpu::Context; static void init(); static Backend* createBackend(); static bool makeProgram(Shader& shader, const Shader::BindingSet& bindings); @@ -54,11 +82,6 @@ public: // Just avoid using it, it's ugly and will break performances virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage); - static bool checkGLError(const char* name = nullptr); - - // Only checks in debug builds - static bool checkGLErrorDebug(const char* name = nullptr); - static void checkGLStackStable(std::function f); @@ -72,7 +95,7 @@ public: GLBuffer(const Buffer& buffer, GLBuffer* original = nullptr); ~GLBuffer(); - void transfer(); + virtual void transfer(); private: bool getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const; @@ -626,6 +649,9 @@ protected: }; -}; +} } + +Q_DECLARE_LOGGING_CATEGORY(gpugllogging) + #endif diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp old mode 100755 new mode 100644 similarity index 98% rename from libraries/gpu/src/gpu/GLBackendBuffer.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp index 7098fd1feb..0057b3d702 --- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp @@ -8,9 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" using namespace gpu; +using namespace gpu::gl; GLuint allocateSingleBuffer() { GLuint result; diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp old mode 100755 new mode 100644 similarity index 99% rename from libraries/gpu/src/gpu/GLBackendInput.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp index 75f4be3cbe..7849da42a0 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp @@ -8,9 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" using namespace gpu; +using namespace gpu::gl; void GLBackend::do_setInputFormat(Batch& batch, size_t paramOffset) { Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint); diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp old mode 100755 new mode 100644 similarity index 85% rename from libraries/gpu/src/gpu/GLBackendOutput.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index d40c5f9b97..f0a29a19ba --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -8,13 +8,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "GLBackend.h" + +#include -#include "GPULogging.h" #include "GLBackendShared.h" - using namespace gpu; +using namespace gpu::gl; GLBackend::GLFramebuffer::GLFramebuffer() {} @@ -97,19 +98,6 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe unit++; } } - #if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer... - else { - GLuint renderBuffer = 0; - glGenRenderbuffers(1, &renderBuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight()); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer); - (void) CHECK_GL_ERROR(); - } - // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - #endif object->_colorStamps = framebuffer.getColorStamps(); } @@ -157,19 +145,19 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe result = true; break; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : - qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : - qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; break; case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER : - qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; break; case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER : - qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; break; case GL_FRAMEBUFFER_UNSUPPORTED : - qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; } if (!result && object->_fbo) { @@ -354,26 +342,26 @@ void GLBackend::do_blit(Batch& batch, size_t paramOffset) { } void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) { - auto readFBO = gpu::GLBackend::getFramebufferID(srcFramebuffer); + auto readFBO = getFramebufferID(srcFramebuffer); if (srcFramebuffer && readFBO) { if ((srcFramebuffer->getWidth() < (region.x + region.z)) || (srcFramebuffer->getHeight() < (region.y + region.w))) { - qCDebug(gpulogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; + qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : srcFramebuffer is too small to provide the region queried"; return; } } if ((destImage.width() < region.z) || (destImage.height() < region.w)) { - qCDebug(gpulogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; + qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage is too small to receive the region of the framebuffer"; return; } GLenum format = GL_BGRA; if (destImage.format() != QImage::Format_ARGB32) { - qCDebug(gpulogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; + qCDebug(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer"; return; } - glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(srcFramebuffer)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcFramebuffer)); glReadPixels(region.x, region.y, region.z, region.w, format, GL_UNSIGNED_BYTE, destImage.bits()); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp old mode 100755 new mode 100644 similarity index 92% rename from libraries/gpu/src/gpu/GLBackendPipeline.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index 0b539f75b9..b66b672850 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -8,11 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" -#include "Format.h" - using namespace gpu; +using namespace gpu::gl; GLBackend::GLPipeline::GLPipeline() : _program(nullptr), @@ -157,7 +157,6 @@ void GLBackend::resetPipelineStage() { void GLBackend::releaseUniformBuffer(uint32_t slot) { -#if (GPU_FEATURE_PROFILE == GPU_CORE) auto& buf = _uniform._buffers[slot]; if (buf) { auto* object = Backend::getGPUObject(*buf); @@ -168,7 +167,6 @@ void GLBackend::releaseUniformBuffer(uint32_t slot) { } buf.reset(); } -#endif } void GLBackend::resetUniformStage() { @@ -186,7 +184,6 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { -#if (GPU_FEATURE_PROFILE == GPU_CORE) if (!uniformBuffer) { releaseUniformBuffer(slot); return; @@ -208,21 +205,6 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { releaseResourceTexture(slot); return; } -#else - // because we rely on the program uniform mechanism we need to have - // the program bound, thank you MacOSX Legacy profile. - updatePipeline(); - - GLfloat* data = (GLfloat*) (uniformBuffer->getData() + rangeStart); - glUniform4fv(slot, rangeSize / sizeof(GLfloat[4]), data); - - // NOT working so we ll stick to the uniform float array until we move to core profile - // GLuint bo = getBufferID(*uniformBuffer); - //glUniformBufferEXT(_shader._program, slot, bo); - - (void) CHECK_GL_ERROR(); - -#endif } void GLBackend::releaseResourceTexture(uint32_t slot) { diff --git a/libraries/gpu/src/gpu/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp similarity index 68% rename from libraries/gpu/src/gpu/GLBackendQuery.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp index f3a81d504c..4f5b2ff1bf 100644 --- a/libraries/gpu/src/gpu/GLBackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp @@ -8,10 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" - using namespace gpu; +using namespace gpu::gl; GLBackend::GLQuery::GLQuery() {} @@ -64,12 +65,7 @@ void GLBackend::do_beginQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - #if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // (EXT_TIMER_QUERY) - glBeginQuery(GL_TIME_ELAPSED_EXT, glquery->_qo); - #else - glBeginQuery(GL_TIME_ELAPSED, glquery->_qo); - #endif + glBeginQuery(GL_TIME_ELAPSED, glquery->_qo); (void)CHECK_GL_ERROR(); } } @@ -78,12 +74,7 @@ void GLBackend::do_endQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - #if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // (EXT_TIMER_QUERY) - glEndQuery(GL_TIME_ELAPSED_EXT); - #else - glEndQuery(GL_TIME_ELAPSED); - #endif + glEndQuery(GL_TIME_ELAPSED); (void)CHECK_GL_ERROR(); } } @@ -92,18 +83,11 @@ void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); if (glquery) { - #if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // (EXT_TIMER_QUERY) - #if !defined(Q_OS_LINUX) - glGetQueryObjectui64vEXT(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); - #endif - #else - glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); - if (glquery->_result == GL_TRUE) { - glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); - query->triggerReturnHandler(glquery->_result); - } - #endif + glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT_AVAILABLE, &glquery->_result); + if (glquery->_result == GL_TRUE) { + glGetQueryObjectui64v(glquery->_qo, GL_QUERY_RESULT, &glquery->_result); + query->triggerReturnHandler(glquery->_result); + } (void)CHECK_GL_ERROR(); } } diff --git a/libraries/gpu/src/gpu/GLBackendShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp old mode 100755 new mode 100644 similarity index 92% rename from libraries/gpu/src/gpu/GLBackendShader.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp index 2c4d9e3179..8ebf7751d1 --- a/libraries/gpu/src/gpu/GLBackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp @@ -8,10 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" -#include "Format.h" using namespace gpu; +using namespace gpu::gl; GLBackend::GLShader::GLShader() { @@ -30,14 +31,14 @@ GLBackend::GLShader::~GLShader() { bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject) { if (shaderSource.empty()) { - qCDebug(gpulogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create"; + qCDebug(gpugllogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create"; return false; } // Create the shader object GLuint glshader = glCreateShader(shaderDomain); if (!glshader) { - qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader object"; + qCDebug(gpugllogging) << "GLShader::compileShader - failed to create the gl shader object"; return false; } @@ -79,12 +80,12 @@ bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const s } */ - qCWarning(gpulogging) << "GLShader::compileShader - failed to compile the gl shader object:"; + qCWarning(gpugllogging) << "GLShader::compileShader - failed to compile the gl shader object:"; for (auto s : srcstr) { - qCWarning(gpulogging) << s; + qCWarning(gpugllogging) << s; } - qCWarning(gpulogging) << "GLShader::compileShader - errors:"; - qCWarning(gpulogging) << temp; + qCWarning(gpugllogging) << "GLShader::compileShader - errors:"; + qCWarning(gpugllogging) << temp; delete[] temp; glDeleteShader(glshader); @@ -96,7 +97,7 @@ bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const s // so far so good, program is almost done, need to link: GLuint glprogram = glCreateProgram(); if (!glprogram) { - qCDebug(gpulogging) << "GLShader::compileShader - failed to create the gl shader & gl program object"; + qCDebug(gpugllogging) << "GLShader::compileShader - failed to create the gl shader & gl program object"; return false; } @@ -124,8 +125,8 @@ bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const s char* temp = new char[infoLength]; glGetProgramInfoLog(glprogram, infoLength, NULL, temp); - qCDebug(gpulogging) << "GLShader::compileShader - failed to LINK the gl program object :"; - qCDebug(gpulogging) << temp; + qCDebug(gpugllogging) << "GLShader::compileShader - failed to LINK the gl program object :"; + qCDebug(gpugllogging) << temp; /* filestream.open("debugshader.glsl.info.txt"); @@ -152,7 +153,7 @@ GLuint compileProgram(const std::vector& glshaders) { // A brand new program: GLuint glprogram = glCreateProgram(); if (!glprogram) { - qCDebug(gpulogging) << "GLShader::compileProgram - failed to create the gl program object"; + qCDebug(gpugllogging) << "GLShader::compileProgram - failed to create the gl program object"; return 0; } @@ -185,8 +186,8 @@ GLuint compileProgram(const std::vector& glshaders) { char* temp = new char[infoLength]; glGetProgramInfoLog(glprogram, infoLength, NULL, temp); - qCDebug(gpulogging) << "GLShader::compileProgram - failed to LINK the gl program object :"; - qCDebug(gpulogging) << temp; + qCDebug(gpugllogging) << "GLShader::compileProgram - failed to LINK the gl program object :"; + qCDebug(gpugllogging) << temp; /* filestream.open("debugshader.glsl.info.txt"); @@ -264,7 +265,7 @@ void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) { GLint linked = 0; glGetProgramiv(glprogram, GL_LINK_STATUS, &linked); if (!linked) { - qCDebug(gpulogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; + qCDebug(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?"; } // now assign the ubo binding, then DON't relink! @@ -358,7 +359,7 @@ GLBackend::GLShader* compileBackendProgram(const Shader& program) { if (object) { shaderGLObjects.push_back(object->_shaderObjects[version].glshader); } else { - qCDebug(gpulogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; + qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; return nullptr; } } @@ -559,19 +560,9 @@ ElementResource getFormatFromGLUniform(GLenum gltype) { int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - , Shader::SlotSet& fakeBuffers -#endif -) { + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { GLint uniformsCount = 0; -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - GLint currentProgram = 0; - glGetIntegerv(GL_CURRENT_PROGRAM, ¤tProgram); - glUseProgram(glprogram); -#endif - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); for (int i = 0; i < uniformsCount; i++) { @@ -604,15 +595,6 @@ int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, } if (elementResource._resource == Resource::BUFFER) { -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // if in legacy profile, we fake the uniform buffer with an array - // this is where we detect it assuming it's explicitely assinged a binding - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - // found one buffer! - fakeBuffers.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } -#endif uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); } else { // For texture/Sampler, the location is the actual binding value @@ -623,11 +605,7 @@ int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, if (requestedBinding != slotBindings.end()) { if (binding != (*requestedBinding)._location) { binding = (*requestedBinding)._location; -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - glUniform1i(location, binding); -#else glProgramUniform1i(glprogram, location, binding); -#endif } } @@ -637,10 +615,6 @@ int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, } } -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - glUseProgram(currentProgram); -#endif - return uniformsCount; } diff --git a/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp similarity index 81% rename from libraries/gpu/src/gpu/GLBackendTexelFormat.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp index 00528d0030..614de60001 100644 --- a/libraries/gpu/src/gpu/GLBackendTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp @@ -1,14 +1,60 @@ // -// Created by Bradley Austin Davis on 2016/04/03 +// Created by Bradley Austin Davis on 2016/05/14 // Copyright 2013-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 // - #include "GLBackendShared.h" -using namespace gpu; +#include + +Q_DECLARE_LOGGING_CATEGORY(gpugllogging) +Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") + +namespace gpu { namespace gl { + +bool checkGLError(const char* name) { + GLenum error = glGetError(); + if (!error) { + return false; + } else { + switch (error) { + case GL_INVALID_ENUM: + qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_INVALID_VALUE: + qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; + break; + case GL_INVALID_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_OUT_OF_MEMORY: + qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + break; + case GL_STACK_UNDERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; + break; + case GL_STACK_OVERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; + break; + } + return true; + } +} + +bool checkGLErrorDebug(const char* name) { +#ifdef DEBUG + return checkGLError(name); +#else + Q_UNUSED(name); + return false; +#endif +} + GLTexelFormat GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { GLTexelFormat texel = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE }; @@ -43,7 +89,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -58,7 +104,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_RG8; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; @@ -81,7 +127,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; @@ -123,7 +169,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::COMPRESSED_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA; - + break; // FIXME: WE will want to support this later @@ -144,13 +190,13 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E */ default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } return texel; } else { @@ -286,7 +332,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH24_STENCIL8; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; @@ -302,7 +348,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_RG8; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; @@ -329,7 +375,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; } @@ -411,14 +457,16 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA; break; default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } break; } default: - qCDebug(gpulogging) << "Unknown combination of texel format"; + qCDebug(gpugllogging) << "Unknown combination of texel format"; } return texel; } } + +} } \ No newline at end of file diff --git a/libraries/gpu/src/gpu/GLBackendShared.h b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h similarity index 76% rename from libraries/gpu/src/gpu/GLBackendShared.h rename to libraries/gpu-gl/src/gpu/gl/GLBackendShared.h index aef670e6b0..0f7215f364 100644 --- a/libraries/gpu/src/gpu/GLBackendShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h @@ -12,11 +12,10 @@ #define hifi_gpu_GLBackend_Shared_h #include +#include +#include -#include - -#include "GPULogging.h" -#include "GLBackend.h" +namespace gpu { namespace gl { static const GLenum _primitiveToGLmode[gpu::NUM_PRIMITIVES] = { GL_POINTS, @@ -59,10 +58,11 @@ public: static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat, const gpu::Element& srcFormat); }; -// Stupid preprocessor trick to turn the line macro into a string -#define CHECK_GL_ERROR_HELPER(x) #x -// FIXME doesn't build on Linux or Mac. Hmmmm -// #define CHECK_GL_ERROR() gpu::GLBackend::checkGLErrorDebug(__FUNCTION__ ":" CHECK_GL_ERROR_HELPER(__LINE__)) -#define CHECK_GL_ERROR() gpu::GLBackend::checkGLErrorDebug(__FUNCTION__) +bool checkGLError(const char* name = nullptr); +bool checkGLErrorDebug(const char* name = nullptr); + +} } + +#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__) #endif diff --git a/libraries/gpu/src/gpu/GLBackendState.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp similarity index 99% rename from libraries/gpu/src/gpu/GLBackendState.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp index 5ef77773e8..1c1e0b38a4 100644 --- a/libraries/gpu/src/gpu/GLBackendState.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp @@ -8,11 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" -#include "Format.h" - using namespace gpu; +using namespace gpu::gl; GLBackend::GLState::GLState() {} diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp old mode 100755 new mode 100644 similarity index 98% rename from libraries/gpu/src/gpu/GLBackendTexture.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp index 24b9544168..952e5bad8f --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp @@ -8,16 +8,19 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GPULogging.h" +#include "GLBackend.h" #include #include #include #include "GLBackendShared.h" + #include "GLBackendTextureTransfer.h" using namespace gpu; +using namespace gpu::gl; + GLenum gpuToGLTextureType(const Texture& texture) { switch (texture.getType()) { @@ -408,7 +411,7 @@ void GLBackend::GLTexture::transfer() const { break; default: - qCWarning(gpulogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; + qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; break; } } @@ -449,7 +452,7 @@ void GLBackend::GLTexture::postTransfer() { break; default: - qCWarning(gpulogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; + qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; break; } } diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp similarity index 98% rename from libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp index 80848f6073..a9635e8307 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp @@ -5,17 +5,20 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - #include "GLBackendTextureTransfer.h" -#include "GLBackendShared.h" - #ifdef THREADED_TEXTURE_TRANSFER #include #include #endif + +#include "GLBackendShared.h" + using namespace gpu; +using namespace gpu::gl; + +#include "GLBackend.h" GLTextureTransferHelper::GLTextureTransferHelper() { #ifdef THREADED_TEXTURE_TRANSFER diff --git a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h similarity index 94% rename from libraries/gpu/src/gpu/GLBackendTextureTransfer.h rename to libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h index 9dc9679628..f344827e53 100644 --- a/libraries/gpu/src/gpu/GLBackendTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h @@ -10,6 +10,7 @@ #include #include #include "GLBackendShared.h" +#include "GLBackend.h" #ifdef Q_OS_WIN #define THREADED_TEXTURE_TRANSFER @@ -17,7 +18,7 @@ class OffscreenGLCanvas; -namespace gpu { +namespace gpu { namespace gl { struct TextureTransferPackage { std::weak_ptr texture; @@ -41,4 +42,4 @@ private: QSharedPointer _canvas; }; -} +} } diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp old mode 100755 new mode 100644 similarity index 99% rename from libraries/gpu/src/gpu/GLBackendTransform.cpp rename to libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp index ae8f28fbe3..7c60eac10e --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp @@ -8,11 +8,11 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "GLBackend.h" #include "GLBackendShared.h" -#include "Format.h" - using namespace gpu; +using namespace gpu::gl; // Transform Stage void GLBackend::do_setModelTransform(Batch& batch, size_t paramOffset) { diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 29115af22f..ca442bbcec 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -2,6 +2,3 @@ set(TARGET_NAME gpu) AUTOSCRIBE_SHADER_LIB(gpu) setup_hifi_library() link_hifi_libraries(shared gl) - -target_glew() -target_opengl() \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 07cef0925a..621ccee2f9 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -31,6 +31,7 @@ namespace gpu { using int8 = int8_t; using Byte = uint8; + using Size = size_t; using Offset = size_t; using Offsets = std::vector; @@ -80,6 +81,11 @@ namespace gpu { using Textures = std::vector; class TextureView; using TextureViews = std::vector; + + namespace gl { + class GLBuffer; + class GLBackend; + } } #endif diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 70292f215b..21164955d6 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -208,7 +208,7 @@ protected: Sysmem _sysmem; // FIXME find a more generic way to do this. - friend class GLBackend; + friend class gl::GLBackend; friend class BufferView; }; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 2362926c84..a5061c62f3 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -884,3 +884,12 @@ Vec3u Texture::evalMipDimensions(uint16 level) const { dimensions >>= level; return glm::max(dimensions, Vec3u(1)); } + +std::function TEXTURE_ID_RESOLVER; + +uint32 Texture::getHardwareId() const { + if (TEXTURE_ID_RESOLVER) { + return TEXTURE_ID_RESOLVER(*this); + } + return 0; +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 8f075d906b..bceb4b196a 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -449,6 +449,8 @@ public: const GPUObjectPointer gpuObject {}; + uint32 getHardwareId() const; + protected: std::unique_ptr< Storage > _storage; diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt index 37c4fbfbe8..7145f7de5c 100644 --- a/libraries/procedural/CMakeLists.txt +++ b/libraries/procedural/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME procedural) AUTOSCRIBE_SHADER_LIB(gpu model) setup_hifi_library() -link_hifi_libraries(shared gpu networking model model-networking) +link_hifi_libraries(shared gpu gpu-gl networking model model-networking) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 6ceddec11b..753ff923dd 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 6b985da4c9..4fc6143ff5 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -3,5 +3,5 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils) # This is not a testcase -- just set it up as a regular hifi project setup_hifi_project(Quick Gui OpenGL Script Widgets) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") -link_hifi_libraries(networking gl gpu procedural shared fbx model model-networking animation script-engine render render-utils ) +link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils ) package_libraries_for_deployment() \ No newline at end of file diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 91b61fb289..b80539b33a 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include @@ -87,7 +87,6 @@ uint32_t toCompactColor(const glm::vec4& color); const char* VERTEX_SHADER = R"SHADER( -#version 450 core layout(location = 0) in vec4 inPosition; layout(location = 3) in vec2 inTexCoord0; @@ -157,7 +156,6 @@ void main(void) { })SHADER"; const char* FRAGMENT_SHADER = R"SHADER( -#version 450 core uniform sampler2D originalTexture; @@ -246,7 +244,7 @@ public: makeCurrent(); setupDebugLogger(this); - gpu::Context::init(); + gpu::Context::init(); _context = std::make_shared(); makeCurrent(); auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); diff --git a/tests/render-utils/CMakeLists.txt b/tests/render-utils/CMakeLists.txt index 25221a1e86..e7e80f7726 100644 --- a/tests/render-utils/CMakeLists.txt +++ b/tests/render-utils/CMakeLists.txt @@ -6,6 +6,6 @@ setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(render-utils gl gpu shared) +link_hifi_libraries(render-utils gl gpu gpu-gl shared) package_libraries_for_deployment() diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 6973ca075e..db6598c43d 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -111,7 +111,7 @@ public: show(); makeCurrent(); - gpu::Context::init(); + gpu::Context::init(); setupDebugLogger(this); diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index e95970dd53..4aa66b92db 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -8,7 +8,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") #include_oglplus() # link in the shared libraries -link_hifi_libraries(shared octree gl gpu model render fbx networking entities +link_hifi_libraries(shared octree gl gpu gpu-gl model render fbx networking entities script-engine physics render-utils entities-renderer) diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index c50f5769e6..2ef7bbdd02 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -114,7 +114,7 @@ public: show(); makeCurrent(); - gpu::Context::init(); + gpu::Context::init(); setupDebugLogger(this); makeCurrent(); resize(QSize(800, 600)); From d77b3bf59a6edec6b557628c15e33cc83f3d8fa2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 16 May 2016 14:12:05 -0700 Subject: [PATCH 074/264] fix some comments and debug pritns --- libraries/entities/src/UpdateEntityOperator.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index a48f3f7198..84f801b059 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -45,23 +45,17 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, _newEntityCube = newQueryAACube; _newEntityBox = _newEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds - // If our new properties don't have bounds details (no change to position, etc) or if this containing element would - // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will - // be the same for both parts of the update + // set oldElementBestFit true if the entity was in the correct element before this operator was run. bool oldElementBestFit = _containingElement->bestFitBounds(_oldEntityBox); // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. - if (oldElementBestFit) { - if (_wantDebug) { - qCDebug(entities) << " **** TYPICAL NO MOVE CASE **** oldElementBestFit:" << oldElementBestFit; - } - } else { + if (!oldElementBestFit) { _oldEntityBox = _existingEntity->getElement()->getAACube(); _removeOld = true; // our properties are going to move us, so remember this for later processing if (_wantDebug) { - qCDebug(entities) << " **** UNUSUAL CASE **** no changes, but not best fit... consider it a move.... **"; + qCDebug(entities) << " **** UNUSUAL CASE **** not best fit.... **"; } } From eb8a764f42b0321ef413b00e393c3de267d912de Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 16 May 2016 14:45:04 -0700 Subject: [PATCH 075/264] update LICENSE to include "and other platform" --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 53c5ccf39a..60e86a1cc7 100644 --- a/LICENSE +++ b/LICENSE @@ -6,7 +6,7 @@ Licensed under the Apache License version 2.0 (the "License"); You may not use this software except in compliance with the License. You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 -This software includes third-party software. +This software includes third-party and other platform software. Please see each individual software license for additional details. This software is distributed "as-is" without any warranties, conditions, or representations whether express or implied, including without limitation the implied warranties and conditions of merchantability, merchantable quality, fitness for a particular purpose, performance, durability, title, non-infringement, and those arising from statute or from custom or usage of trade or course of dealing. From 0d6b0eef59511ab5ce273596277135217cbd3ff8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 16 May 2016 15:27:14 -0700 Subject: [PATCH 076/264] PR feedback --- libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp | 2 +- libraries/gpu/CMakeLists.txt | 2 +- libraries/gpu/src/gpu/Texture.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp index 614de60001..deb48be1ec 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp @@ -469,4 +469,4 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } } -} } \ No newline at end of file +} } diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index ca442bbcec..ae1e9b4427 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME gpu) AUTOSCRIBE_SHADER_LIB(gpu) setup_hifi_library() -link_hifi_libraries(shared gl) +link_hifi_libraries(shared) diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index a5061c62f3..3c8d39a838 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -892,4 +892,4 @@ uint32 Texture::getHardwareId() const { return TEXTURE_ID_RESOLVER(*this); } return 0; -} \ No newline at end of file +} From 1fc3d9229c9735e445533f678a1b7777df65de81 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 17 May 2016 10:51:42 +1200 Subject: [PATCH 077/264] Fix initial drive change and go-up not working properly on first screen --- interface/resources/qml/dialogs/FileDialog.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index f185508457..9c68767e2b 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -72,6 +72,12 @@ ModalWindow { root.dir = helper.pathToUrl(drivesSelector.currentText); }) + // HACK: The following two lines force the model to initialize properly such that: + // - Selecting a different drive at the initial screen updates the path displayed. + // - The go-up button works properly from the initial screen. + root.dir = helper.pathToUrl(drivesSelector.currentText); + root.dir = helper.pathToUrl(currentDirectory.lastValidFolder); + iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; } From 4b1497fdf4ddd04bffeac34aba16d6ee3a75e277 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 16 May 2016 16:25:40 -0700 Subject: [PATCH 078/264] Remove dead code highlight entity in edit.js --- scripts/system/edit.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 5f0317da7c..baf70033f9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -714,7 +714,6 @@ function mousePressEvent(event) { } } -var highlightedEntityID = null; var mouseCapturedByTool = false; var lastMousePosition = null; var idleMouseTimerId = null; @@ -780,33 +779,6 @@ function handleIdleMouse() { idleMouseTimerId = null; } -function highlightEntityUnderCursor(position, accurateRay) { - var pickRay = Camera.computePickRay(position.x, position.y); - var entityIntersection = Entities.findRayIntersection(pickRay, accurateRay); - if (entityIntersection.accurate) { - if (highlightedEntityID && highlightedEntityID != entityIntersection.entityID) { - selectionDisplay.unhighlightSelectable(highlightedEntityID); - highlightedEntityID = { - id: -1 - }; - } - - var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0; - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), - entityIntersection.properties.position)) * 180 / 3.14; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (entityIntersection.entityID && sizeOK) { - highlightedEntityID = entityIntersection.entityID; - selectionDisplay.highlightSelectable(entityIntersection.entityID); - } - - } -} - - function mouseReleaseEvent(event) { mouseDown = false; From 55b0060df937c3d6d9a2b50073688cbb83c48662 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 12 May 2016 14:40:27 -0700 Subject: [PATCH 079/264] Convert JointData from relative frame to absolute. --- libraries/animation/src/Rig.cpp | 82 +++++++++++++------- libraries/animation/src/Rig.h | 12 +-- libraries/avatars/src/AvatarData.cpp | 6 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/render-utils/src/Model.cpp | 4 - libraries/render-utils/src/Model.h | 4 - 6 files changed, 63 insertions(+), 48 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 67dfbec24a..378bd55667 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -165,6 +165,7 @@ void Rig::destroyAnimGraph() { void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); @@ -193,6 +194,7 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff void Rig::reset(const FBXGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); + _invGeometryOffset = _geometryOffset.inverse(); _animSkeleton = std::make_shared(geometry); _internalPoseSet._relativePoses.clear(); @@ -272,24 +274,6 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { } } -bool Rig::getJointStateRotation(int index, glm::quat& rotation) const { - if (isIndexValid(index)) { - rotation = _internalPoseSet._relativePoses[index].rot; - return !isEqual(rotation, _animSkeleton->getRelativeDefaultPose(index).rot); - } else { - return false; - } -} - -bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const { - if (isIndexValid(index)) { - translation = _internalPoseSet._relativePoses[index].trans; - return !isEqual(translation, _animSkeleton->getRelativeDefaultPose(index).trans); - } else { - return false; - } -} - void Rig::clearJointState(int index) { if (isIndexValid(index)) { _internalPoseSet._overrideFlags[index] = false; @@ -446,6 +430,23 @@ void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, *alphaOut = alpha; } +bool Rig::getJointData(int index, JointData& jointDataOut) const { + if (isIndexValid(index)) { + jointDataOut.rotation = _internalPoseSet._absolutePoses[index].rot; + jointDataOut.rotationSet = !isEqual(jointDataOut.rotation, _animSkeleton->getAbsoluteDefaultPose(index).rot); + + // geometry offset is used here so that translations are in meters, this is what the avatar mixer expects + jointDataOut.translation = _geometryOffset * _internalPoseSet._absolutePoses[index].trans; + jointDataOut.translationSet = !isEqual(jointDataOut.translation, _animSkeleton->getAbsoluteDefaultPose(index).trans); + return true; + } else { + jointDataOut.translationSet = false; + jointDataOut.rotationSet = false; + return false; + } +} + + void Rig::setEnableInverseKinematics(bool enable) { _enableInverseKinematics = enable; } @@ -1232,21 +1233,44 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { jointDataVec.resize((int)getJointStateCount()); for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; - data.rotationSet |= getJointStateRotation(i, data.rotation); - // geometry offset is used here so that translations are in meters. - // this is what the avatar mixer expects - data.translationSet |= getJointStateTranslation(i, data.translation); - data.translation = _geometryOffset * data.translation; + getJointData(i, data); } } void Rig::copyJointsFromJointData(const QVector& jointDataVec) { - AnimPose invGeometryOffset = _geometryOffset.inverse(); - for (int i = 0; i < jointDataVec.size(); i++) { - const JointData& data = jointDataVec.at(i); - setJointRotation(i, data.rotationSet, data.rotation, 1.0f); - // geometry offset is used here to undo the fact that avatar mixer translations are in meters. - setJointTranslation(i, data.translationSet, invGeometryOffset * data.translation, 1.0f); + + if (_animSkeleton) { + + std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); + AnimPoseVec overridePoses = _animSkeleton->getAbsoluteDefaultPoses(); // start with the default poses. + + ASSERT(overrideFlags.size() == absoluteOverridePoses.size()); + + for (int i = 0; i < jointDataVec.size(); i++) { + if (isIndexValid(i)) { + const JointData& data = jointDataVec.at(i); + if (data.rotationSet) { + overrideFlags[i] = true; + overridePoses[i].rot = data.rotation; + } + if (data.translationSet) { + overrideFlags[i] = true; + // convert from meters back into geometry units. + overridePoses[i].trans = _invGeometryOffset * data.translation; + } + } + } + + ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + + _animSkeleton->convertAbsolutePosesToRelative(overridePoses); + + for (int i = 0; i < jointDataVec.size(); i++) { + if (overrideFlags[i]) { + _internalPoseSet._overrideFlags[i] = true; + _internalPoseSet._overridePoses[i] = overridePoses[i]; + } + } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 363006d48c..7446fddc47 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -104,12 +104,6 @@ public: void setModelOffset(const glm::mat4& modelOffsetMat); - // geometry space - bool getJointStateRotation(int index, glm::quat& rotation) const; - - // geometry space - bool getJointStateTranslation(int index, glm::vec3& translation) const; - void clearJointState(int index); void clearJointStates(); void clearJointAnimationPriority(int index); @@ -119,8 +113,6 @@ public: // geometry space void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); - - // geometry space void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); // legacy @@ -237,8 +229,12 @@ protected: void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; + // geometry space + bool getJointData(int index, JointData& jointDataOut) const; + AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) + AnimPose _invGeometryOffset; struct PoseSet { AnimPoseVec _relativePoses; // geometry space relative to parent. diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9c556dc42b..784044da2e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -121,6 +121,8 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) { _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); } +#define WANT_DEBUG + QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { // TODO: DRY this up to a shared method // that can pack any type given the number of bytes @@ -329,7 +331,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } #ifdef WANT_DEBUG - if (sendAll) { + //if (sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll << "rotations:" << rotationSentCount << "translations:" << translationSentCount << "largest:" << maxTranslationDimension @@ -339,7 +341,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { << (beforeTranslations - beforeRotations) << "+" << (destinationBuffer - beforeTranslations) << "=" << (destinationBuffer - startPosition); - } + //} #endif return avatarDataByteArray.left(destinationBuffer - startPosition); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b98a87e439..e0d854ab71 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -174,7 +174,8 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, - SoftAttachmentSupport + SoftAttachmentSupport, + AbsoluteFortyEightBitRotations }; #endif // hifi_PacketHeaders_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2fe4427333..e4c414d30b 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -726,10 +726,6 @@ glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { return translatedPoint; } -bool Model::getJointState(int index, glm::quat& rotation) const { - return _rig->getJointStateRotation(index, rotation); -} - void Model::clearJointState(int index) { _rig->clearJointState(index); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 581184918d..6a7c9ec560 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -252,10 +252,6 @@ protected: /// Returns the scaled equivalent of a point in model space. glm::vec3 calculateScaledOffsetPoint(const glm::vec3& point) const; - /// Fetches the joint state at the specified index. - /// \return whether or not the joint state is "valid" (that is, non-default) - bool getJointState(int index, glm::quat& rotation) const; - /// Clear the joint states void clearJointState(int index); From 36c175d4ccf22b5c64b22b4a10714e2f31bd147c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 12 May 2016 15:55:48 -0700 Subject: [PATCH 080/264] Ensure that JointData is in the absolute rig coordinate frame. --- libraries/animation/src/Rig.cpp | 42 ++++++++++++++++----------------- libraries/animation/src/Rig.h | 3 --- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 378bd55667..34c917da74 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -430,23 +430,6 @@ void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, *alphaOut = alpha; } -bool Rig::getJointData(int index, JointData& jointDataOut) const { - if (isIndexValid(index)) { - jointDataOut.rotation = _internalPoseSet._absolutePoses[index].rot; - jointDataOut.rotationSet = !isEqual(jointDataOut.rotation, _animSkeleton->getAbsoluteDefaultPose(index).rot); - - // geometry offset is used here so that translations are in meters, this is what the avatar mixer expects - jointDataOut.translation = _geometryOffset * _internalPoseSet._absolutePoses[index].trans; - jointDataOut.translationSet = !isEqual(jointDataOut.translation, _animSkeleton->getAbsoluteDefaultPose(index).trans); - return true; - } else { - jointDataOut.translationSet = false; - jointDataOut.rotationSet = false; - return false; - } -} - - void Rig::setEnableInverseKinematics(bool enable) { _enableInverseKinematics = enable; } @@ -1230,10 +1213,22 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { } void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { + + const AnimPose geometryToRigPose(_geometryToRigTransform); + jointDataVec.resize((int)getJointStateCount()); for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; - getJointData(i, data); + if (isIndexValid(i)) { + AnimPose defaultPose = geometryToRigPose * _animSkeleton->getAbsoluteDefaultPose(i); + data.rotation = _internalPoseSet._absolutePoses[i].rot; + data.rotationSet = !isEqual(data.rotation, defaultPose.rot); + data.translation = _internalPoseSet._absolutePoses[i].trans; + data.translationSet = !isEqual(data.translation, defaultPose.trans); + } else { + data.translationSet = false; + data.rotationSet = false; + } } } @@ -1243,8 +1238,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); AnimPoseVec overridePoses = _animSkeleton->getAbsoluteDefaultPoses(); // start with the default poses. + for (int i = 0; i < overridePoses.size(); i++) { + overridePoses[i] = AnimPose(_geometryToRigTransform) * overridePoses[i]; + } - ASSERT(overrideFlags.size() == absoluteOverridePoses.size()); + ASSERT(overrideFlags.size() == overridePoses.size()); for (int i = 0; i < jointDataVec.size(); i++) { if (isIndexValid(i)) { @@ -1255,8 +1253,7 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { } if (data.translationSet) { overrideFlags[i] = true; - // convert from meters back into geometry units. - overridePoses[i].trans = _invGeometryOffset * data.translation; + overridePoses[i].trans = data.translation; } } } @@ -1265,10 +1262,11 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { _animSkeleton->convertAbsolutePosesToRelative(overridePoses); + AnimPose rigToGeometryPose(_rigToGeometryTransform); for (int i = 0; i < jointDataVec.size(); i++) { if (overrideFlags[i]) { _internalPoseSet._overrideFlags[i] = true; - _internalPoseSet._overridePoses[i] = overridePoses[i]; + _internalPoseSet._overridePoses[i] = rigToGeometryPose * overridePoses[i]; } } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7446fddc47..891d9fdb92 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -229,9 +229,6 @@ protected: void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; - // geometry space - bool getJointData(int index, JointData& jointDataOut) const; - AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _invGeometryOffset; From d86a608825bcf189057a794e68f8a9e2d990200c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 12 May 2016 17:10:48 -0700 Subject: [PATCH 081/264] Properly convert from absolute rig frame to relative geom frame --- libraries/animation/src/Rig.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 34c917da74..289612d712 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1236,14 +1236,17 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { if (_animSkeleton) { + // transform all the default poses into rig space. + const AnimPose geometryToRigPose(_geometryToRigTransform); std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); AnimPoseVec overridePoses = _animSkeleton->getAbsoluteDefaultPoses(); // start with the default poses. - for (int i = 0; i < overridePoses.size(); i++) { - overridePoses[i] = AnimPose(_geometryToRigTransform) * overridePoses[i]; + for (auto& pose : overridePoses) { + pose = geometryToRigPose * pose; } ASSERT(overrideFlags.size() == overridePoses.size()); + // copy over data from the jointDataVec, which is also in rig space. for (int i = 0; i < jointDataVec.size(); i++) { if (isIndexValid(i)) { const JointData& data = jointDataVec.at(i); @@ -1260,13 +1263,20 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); + // convert resulting poses into geometry space. + const AnimPose rigToGeometryPose(_rigToGeometryTransform); + for (auto& pose : overridePoses) { + pose = rigToGeometryPose * pose; + } + + // convert all poses from absolute to parent relative. _animSkeleton->convertAbsolutePosesToRelative(overridePoses); - AnimPose rigToGeometryPose(_rigToGeometryTransform); + // copy the geometry space parent relative poses into _overridePoses for (int i = 0; i < jointDataVec.size(); i++) { if (overrideFlags[i]) { _internalPoseSet._overrideFlags[i] = true; - _internalPoseSet._overridePoses[i] = rigToGeometryPose * overridePoses[i]; + _internalPoseSet._overridePoses[i] = overridePoses[i]; } } } From a251b9e3dfc1be0d6e47408a4fa5b549634c7e1e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 13 May 2016 11:16:31 -0700 Subject: [PATCH 082/264] Moved translations back into parent relative frame. --- libraries/animation/src/AnimSkeleton.cpp | 12 ++++++ libraries/animation/src/AnimSkeleton.h | 2 + libraries/animation/src/Rig.cpp | 54 ++++++++++++++++-------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 2d37be9b87..351c09beee 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -107,6 +107,18 @@ void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { } } +void AnimSkeleton::convertAbsoluteRotationsToRelative(std::vector& rotations) const { + // poses start off absolute and leave in relative frame + int lastIndex = std::min((int)rotations.size(), (int)_joints.size()); + for (int i = lastIndex - 1; i >= 0; --i) { + int parentIndex = _joints[i].parentIndex; + if (parentIndex != -1) { + rotations[i] = glm::inverse(rotations[parentIndex]) * rotations[i]; + } + } +} + + void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { convertRelativePosesToAbsolute(poses); mirrorAbsolutePoses(poses); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index e2cd20d63e..68cce11326 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -55,6 +55,8 @@ public: void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; + void convertAbsoluteRotationsToRelative(std::vector& rotations) const; + void mirrorRelativePoses(AnimPoseVec& poses) const; void mirrorAbsolutePoses(AnimPoseVec& poses) const; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 289612d712..9bba9ffc33 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1220,11 +1220,16 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { for (auto i = 0; i < jointDataVec.size(); i++) { JointData& data = jointDataVec[i]; if (isIndexValid(i)) { - AnimPose defaultPose = geometryToRigPose * _animSkeleton->getAbsoluteDefaultPose(i); + // rotations are in absolute rig frame. + glm::quat defaultAbsRot = geometryToRigPose.rot * _animSkeleton->getAbsoluteDefaultPose(i).rot; data.rotation = _internalPoseSet._absolutePoses[i].rot; - data.rotationSet = !isEqual(data.rotation, defaultPose.rot); - data.translation = _internalPoseSet._absolutePoses[i].trans; - data.translationSet = !isEqual(data.translation, defaultPose.trans); + data.rotationSet = !isEqual(data.rotation, defaultAbsRot); + + // translations are in relative frame but scaled so that they are in meters, + // instead of geometry units. + glm::vec3 defaultRelTrans = _geometryOffset.scale * _animSkeleton->getRelativeDefaultPose(i).trans; + data.translation = _geometryOffset.scale * _internalPoseSet._relativePoses[i].trans; + data.translationSet = !isEqual(data.translation, defaultRelTrans); } else { data.translationSet = false; data.rotationSet = false; @@ -1239,44 +1244,57 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { // transform all the default poses into rig space. const AnimPose geometryToRigPose(_geometryToRigTransform); std::vector overrideFlags(_internalPoseSet._overridePoses.size(), false); - AnimPoseVec overridePoses = _animSkeleton->getAbsoluteDefaultPoses(); // start with the default poses. - for (auto& pose : overridePoses) { - pose = geometryToRigPose * pose; + + // start with the default rotations in absolute rig frame + std::vector rotations; + rotations.reserve(_animSkeleton->getAbsoluteDefaultPoses().size()); + for (auto& pose : _animSkeleton->getAbsoluteDefaultPoses()) { + rotations.push_back(geometryToRigPose.rot * pose.rot); } - ASSERT(overrideFlags.size() == overridePoses.size()); + // start translations in relative frame but scaled to meters. + std::vector translations; + translations.reserve(_animSkeleton->getRelativeDefaultPoses().size()); + for (auto& pose : _animSkeleton->getRelativeDefaultPoses()) { + translations.push_back(_geometryOffset.scale * pose.trans); + } - // copy over data from the jointDataVec, which is also in rig space. + ASSERT(overrideFlags.size() == rotations.size()); + + // copy over rotations from the jointDataVec, which is also in absolute rig frame for (int i = 0; i < jointDataVec.size(); i++) { if (isIndexValid(i)) { const JointData& data = jointDataVec.at(i); if (data.rotationSet) { overrideFlags[i] = true; - overridePoses[i].rot = data.rotation; + rotations[i] = data.rotation; } if (data.translationSet) { overrideFlags[i] = true; - overridePoses[i].trans = data.translation; + translations[i] = data.translation; } } } ASSERT(_internalPoseSet._overrideFlags.size() == _internalPoseSet._overridePoses.size()); - // convert resulting poses into geometry space. - const AnimPose rigToGeometryPose(_rigToGeometryTransform); - for (auto& pose : overridePoses) { - pose = rigToGeometryPose * pose; + // convert resulting rotations into geometry space. + const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); + for (auto& rot : rotations) { + rot = rigToGeometryRot * rot; } - // convert all poses from absolute to parent relative. - _animSkeleton->convertAbsolutePosesToRelative(overridePoses); + // convert all rotations from absolute to parent relative. + _animSkeleton->convertAbsoluteRotationsToRelative(rotations); // copy the geometry space parent relative poses into _overridePoses for (int i = 0; i < jointDataVec.size(); i++) { if (overrideFlags[i]) { _internalPoseSet._overrideFlags[i] = true; - _internalPoseSet._overridePoses[i] = overridePoses[i]; + _internalPoseSet._overridePoses[i].scale = Vectors::ONE; + _internalPoseSet._overridePoses[i].rot = rotations[i]; + // scale translations from meters back into geometry units. + _internalPoseSet._overridePoses[i].trans = _invGeometryOffset.scale * translations[i]; } } } From 818d1f4601507256321dbacdef89c72d367495b0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 13 May 2016 16:19:32 -0700 Subject: [PATCH 083/264] Added six byte quaternion compression routines & tests --- libraries/shared/src/GLMHelpers.cpp | 81 ++++++++++++++++++++++++++++ libraries/shared/src/GLMHelpers.h | 8 +++ tests/shared/src/GLMHelpersTests.cpp | 47 ++++++++++++++++ tests/shared/src/GLMHelpersTests.h | 1 + 4 files changed, 137 insertions(+) diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index bd8bffefd2..e81958f407 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -135,6 +135,87 @@ int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatO return sizeof(quatParts); } +#define HI_BYTE(x) (uint8_t)(x >> 8) +#define LO_BYTE(x) (uint8_t)(0xff & x) + +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput) { + + // find largest component + uint8_t largestComponent = 0; + for (int i = 1; i < 4; i++) { + if (fabs(quatInput[i]) > fabs(quatInput[largestComponent])) { + largestComponent = i; + } + } + + // ensure that the sign of the dropped component is always negative. + glm::quat q = quatInput[largestComponent] > 0 ? -quatInput : quatInput; + + const float MAGNITUDE = 1.0f / sqrt(2.0f); + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const uint32_t RANGE = (1 << NUM_BITS_PER_COMPONENT) - 1; + + // quantize the smallest three components into integers + uint16_t components[3]; + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + // transform component into 0..1 range. + float value = (q[i] + MAGNITUDE) / (2.0f * MAGNITUDE); + + // quantize 0..1 into 0..range + components[j] = (uint16_t)(value * RANGE); + j++; + } + } + + // encode the largestComponent into the high bits of the first two components + components[0] = (0x7fff & components[0]) | ((0x01 & largestComponent) << 15); + components[1] = (0x7fff & components[1]) | ((0x02 & largestComponent) << 14); + + buffer[0] = HI_BYTE(components[0]); + buffer[1] = LO_BYTE(components[0]); + buffer[2] = HI_BYTE(components[1]); + buffer[3] = LO_BYTE(components[1]); + buffer[4] = HI_BYTE(components[2]); + buffer[5] = LO_BYTE(components[2]); + + return 6; +} + +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput) { + + uint16_t components[3]; + components[0] = ((uint16_t)(0x7f & buffer[0]) << 8) | buffer[1]; + components[1] = ((uint16_t)(0x7f & buffer[2]) << 8) | buffer[3]; + components[2] = ((uint16_t)(0x7f & buffer[4]) << 8) | buffer[5]; + + // largestComponent is encoded into the highest bits of the first 2 components + uint8_t largestComponent = ((0x80 & buffer[2]) >> 6) | ((0x80 & buffer[0]) >> 7); + + const uint32_t NUM_BITS_PER_COMPONENT = 15; + const float RANGE = (float)((1 << NUM_BITS_PER_COMPONENT) - 1); + const float MAGNITUDE = 1.0f / sqrtf(2.0f); + float floatComponents[3]; + for (int i = 0; i < 3; i++) { + floatComponents[i] = ((float)components[i] / RANGE) * (2.0f * MAGNITUDE) - MAGNITUDE; + } + + // missingComponent is always negative. + float missingComponent = -sqrtf(1.0f - floatComponents[0] * floatComponents[0] - floatComponents[1] * floatComponents[1] - floatComponents[2] * floatComponents[2]); + + for (int i = 0, j = 0; i < 4; i++) { + if (i != largestComponent) { + quatOutput[i] = floatComponents[j]; + j++; + } else { + quatOutput[i] = missingComponent; + } + } + + return 6; +} + + // Safe version of glm::eulerAngles; uses the factorization method described in David Eberly's // http://www.geometrictools.com/Documentation/EulerAngles.pdf (via Clyde, // https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java) diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 8b1446d4e5..ae9ec25195 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -97,6 +97,14 @@ int unpackFloatAngleFromTwoByte(const uint16_t* byteAnglePointer, float* destina int packOrientationQuatToBytes(unsigned char* buffer, const glm::quat& quatInput); int unpackOrientationQuatFromBytes(const unsigned char* buffer, glm::quat& quatOutput); +// alternate compression method that picks the smallest three quaternion components. +// and packs them into 15 bits each. An additional 2 bits are used to encode which component +// was omitted. Also because the components are encoded from the -1/sqrt(2) to 1/sqrt(2) which +// gives us some extra precision over the -1 to 1 range. The final result will have a maximum +// error of +- 4.3e-5 error per compoenent. +int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatInput); +int unpackOrientationQuatFromSixBytes(const unsigned char* buffer, glm::quat& quatOutput); + // Ratios need the be highly accurate when less than 10, but not very accurate above 10, and they // are never greater than 1000 to 1, this allows us to encode each component in 16bits int packFloatRatioToTwoByte(unsigned char* buffer, float ratio); diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp index afb634ecbd..a796d62ba5 100644 --- a/tests/shared/src/GLMHelpersTests.cpp +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -54,4 +54,51 @@ void GLMHelpersTests::testEulerDecomposition() { } } +static void testQuatCompression(glm::quat testQuat) { + float MAX_COMPONENT_ERROR = 4.3e-5f; + + glm::quat q; + uint8_t bytes[6]; + packOrientationQuatToSixBytes(bytes, testQuat); + unpackOrientationQuatFromSixBytes(bytes, q); + if (glm::dot(q, testQuat) < 0.0f) { + q = -q; + } + QCOMPARE_WITH_ABS_ERROR(q.x, testQuat.x, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.y, testQuat.y, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.z, testQuat.z, MAX_COMPONENT_ERROR); + QCOMPARE_WITH_ABS_ERROR(q.w, testQuat.w, MAX_COMPONENT_ERROR); +} + +void GLMHelpersTests::testSixByteOrientationCompression() { + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + testQuatCompression(ROT_X_90); + testQuatCompression(ROT_Y_180); + testQuatCompression(ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_X_90 * ROT_Z_30); + testQuatCompression(ROT_Z_30 * ROT_Y_180); + testQuatCompression(ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_X_90 * ROT_Y_180 * ROT_Z_30); + testQuatCompression(ROT_Y_180 * ROT_Z_30 * ROT_X_90); + testQuatCompression(ROT_Z_30 * ROT_X_90 * ROT_Y_180); + + testQuatCompression(-ROT_X_90); + testQuatCompression(-ROT_Y_180); + testQuatCompression(-ROT_Z_30); + testQuatCompression(-(ROT_X_90 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_X_90 * ROT_Z_30)); + testQuatCompression(-(ROT_Z_30 * ROT_Y_180)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_X_90 * ROT_Y_180 * ROT_Z_30)); + testQuatCompression(-(ROT_Y_180 * ROT_Z_30 * ROT_X_90)); + testQuatCompression(-(ROT_Z_30 * ROT_X_90 * ROT_Y_180)); +} diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h index 5e880899e8..40d552a07b 100644 --- a/tests/shared/src/GLMHelpersTests.h +++ b/tests/shared/src/GLMHelpersTests.h @@ -19,6 +19,7 @@ class GLMHelpersTests : public QObject { Q_OBJECT private slots: void testEulerDecomposition(); + void testSixByteOrientationCompression(); }; float getErrorDifference(const float& a, const float& b); From 3d91c5b54def5317b7f4e6f6ee6097f85654ca5f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 13 May 2016 16:24:20 -0700 Subject: [PATCH 084/264] AvatarData.cpp: hooked up 6 byte quat compression --- libraries/avatars/src/AvatarData.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 784044da2e..3db3cea4cf 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -263,7 +263,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { for (int i = 0; i < _jointData.size(); i ++) { const JointData& data = _jointData[ i ]; if (validity & (1 << validityBit)) { - destinationBuffer += packOrientationQuatToBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -650,10 +650,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (validRotations[i]) { _hasNewJointRotations = true; data.rotationSet = true; - sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation); + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); } } - } // numJoints * 8 bytes + } // numJoints * 6 bytes // joint translations // get translation validity bits -- these indicate which translations were packed From 424517e3de2a9647cc6c99503c1f6b45066cd629 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 16 May 2016 19:11:50 -0700 Subject: [PATCH 085/264] Fix for Malformed packet errors --- libraries/avatars/src/AvatarData.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 3db3cea4cf..251d83c19b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -37,6 +37,8 @@ #include "AvatarLogging.h" +#define WANT_DEBUG + quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; using namespace std; @@ -121,8 +123,6 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) { _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); } -#define WANT_DEBUG - QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { // TODO: DRY this up to a shared method // that can pack any type given the number of bytes @@ -331,7 +331,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } #ifdef WANT_DEBUG - //if (sendAll) { + if (sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll << "rotations:" << rotationSentCount << "translations:" << translationSentCount << "largest:" << maxTranslationDimension @@ -341,7 +341,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { << (beforeTranslations - beforeRotations) << "+" << (destinationBuffer - beforeTranslations) << "=" << (destinationBuffer - startPosition); - //} + } #endif return avatarDataByteArray.left(destinationBuffer - startPosition); @@ -372,6 +372,12 @@ void AvatarData::doneEncoding(bool cullSmallChanges) { } bool AvatarData::shouldLogError(const quint64& now) { +#ifdef WANT_DEBUG + if (now > 0) { + return true; + } +#endif + if (now > _errorLogExpiry) { _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; return true; @@ -631,9 +637,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bytesOfValidity bytes - // each joint rotation component is stored in two bytes (sizeof(uint16_t)) - int COMPONENTS_PER_QUATERNION = 4; - minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); + // each joint rotation is stored in 6 bytes. + const size_t COMPRESSED_QUATERNION_SIZE = 6; + minPossibleSize += numValidJointRotations * COMPRESSED_QUATERNION_SIZE; if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" From e0483585b85822a4d6220fefc8e08683a89d236f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 17 May 2016 14:43:49 +1200 Subject: [PATCH 086/264] Consistently capitalize drive letter for Windows --- interface/resources/qml/dialogs/FileDialog.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 9c68767e2b..e8b6b9b2b7 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -144,7 +144,16 @@ ModalWindow { leftMargin: hifi.dimensions.contentSpacing.x right: parent.right } - onLastValidFolderChanged: text = lastValidFolder; + + function capitalizeDrive(path) { + // Consistently capitalize drive letter for Windows. + if (/[a-zA-Z]:/.test(path)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + return path; + } + + onLastValidFolderChanged: text = capitalizeDrive(lastValidFolder); // FIXME add support auto-completion onAccepted: { From ef6d758e7f19084b3d25a507a3aaed0827c86226 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 10 May 2016 10:30:38 -0700 Subject: [PATCH 087/264] Fix JurisdictionMap multithreading issues Make octal code pointers use shared_ptr, add locks around access. --- interface/src/Application.cpp | 14 +- interface/src/ui/OctreeStatsDialog.cpp | 6 +- libraries/octree/src/JurisdictionMap.cpp | 187 ++++++++---------- libraries/octree/src/JurisdictionMap.h | 18 +- libraries/octree/src/OctreeHeadlessViewer.cpp | 8 +- libraries/octree/src/OctreeSceneStats.cpp | 96 +++------ libraries/octree/src/OctreeSceneStats.h | 9 +- libraries/shared/src/OctalCode.cpp | 9 +- libraries/shared/src/OctalCode.h | 7 +- 9 files changed, 138 insertions(+), 216 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7a40c0b554..4d772d4b19 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3656,11 +3656,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), @@ -3720,11 +3720,11 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x * TREE_SCALE, rootDetails.y * TREE_SCALE, rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE), @@ -4251,9 +4251,9 @@ void Application::nodeKilled(SharedNodePointer node) { return; } - unsigned char* rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); + auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); @@ -4378,7 +4378,7 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer } VoxelPositionSize rootDetails; - voxelDetailsForCode(octreeStats.getJurisdictionRoot(), rootDetails); + voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails); qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", qPrintable(serverType), diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index 6b6d600e20..fc0e6781d7 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -433,13 +433,13 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser } const JurisdictionMap& map = serverJurisdictions[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (rootCode) { - QString rootCodeHex = octalCodeToHexString(rootCode); + QString rootCodeHex = octalCodeToHexString(rootCode.get()); VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); serverDetails << " jurisdiction: " << qPrintable(rootCodeHex) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index cac2211d29..86de7467d3 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -17,90 +17,11 @@ #include #include #include -#include #include "OctreeLogging.h" #include "JurisdictionMap.h" - -// standard assignment -// copy assignment -JurisdictionMap& JurisdictionMap::operator=(const JurisdictionMap& other) { - copyContents(other); - return *this; -} - -// Copy constructor -JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode(NULL) { - copyContents(other); -} - -void JurisdictionMap::copyContents(unsigned char* rootCodeIn, const std::vector& endNodesIn) { - unsigned char* rootCode; - std::vector endNodes; - if (rootCodeIn) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(rootCodeIn)); - rootCode = new unsigned char[bytes]; - memcpy(rootCode, rootCodeIn, bytes); - } else { - rootCode = new unsigned char[1]; - *rootCode = 0; - } - - for (size_t i = 0; i < endNodesIn.size(); i++) { - if (endNodesIn[i]) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodesIn[i])); - unsigned char* endNodeCode = new unsigned char[bytes]; - memcpy(endNodeCode, endNodesIn[i], bytes); - endNodes.push_back(endNodeCode); - } - } - init(rootCode, endNodes); -} - -void JurisdictionMap::copyContents(const JurisdictionMap& other) { - _nodeType = other._nodeType; - copyContents(other._rootOctalCode, other._endNodes); -} - -JurisdictionMap::~JurisdictionMap() { - clear(); -} - -void JurisdictionMap::clear() { - if (_rootOctalCode) { - delete[] _rootOctalCode; - _rootOctalCode = NULL; - } - - for (size_t i = 0; i < _endNodes.size(); i++) { - if (_endNodes[i]) { - delete[] _endNodes[i]; - } - } - _endNodes.clear(); -} - -JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(NULL) { - _nodeType = type; - unsigned char* rootCode = new unsigned char[1]; - *rootCode = 0; - - std::vector emptyEndNodes; - init(rootCode, emptyEndNodes); -} - -JurisdictionMap::JurisdictionMap(const char* filename) : _rootOctalCode(NULL) { - clear(); // clean up our own memory - readFromFile(filename); -} - -JurisdictionMap::JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes) - : _rootOctalCode(NULL) { - init(rootOctalCode, endNodes); -} - -void myDebugoutputBits(unsigned char byte, bool withNewLine) { +void myDebugOutputBits(unsigned char byte, bool withNewLine) { if (isalnum(byte)) { printf("[ %d (%c): ", byte, byte); } else { @@ -117,13 +38,12 @@ void myDebugoutputBits(unsigned char byte, bool withNewLine) { } } - void myDebugPrintOctalCode(const unsigned char* octalCode, bool withNewLine) { if (!octalCode) { - printf("NULL"); + printf("nullptr"); } else { for (size_t i = 0; i < bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octalCode)); i++) { - myDebugoutputBits(octalCode[i],false); + myDebugOutputBits(octalCode[i], false); } } if (withNewLine) { @@ -131,16 +51,53 @@ void myDebugPrintOctalCode(const unsigned char* octalCode, bool withNewLine) { } } +// standard assignment +// copy assignment +JurisdictionMap& JurisdictionMap::operator=(const JurisdictionMap& other) { + copyContents(other); + return *this; +} + +// Copy constructor +JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode(nullptr) { + copyContents(other); +} + +void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const std::vector& endNodesIn) { + init(rootCodeIn, endNodesIn); +} + +void JurisdictionMap::copyContents(const JurisdictionMap& other) { + _nodeType = other._nodeType; + + init(other.getRootOctalCode(), other.getEndNodeOctalCodes()); +} + +JurisdictionMap::~JurisdictionMap() { +} + +JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(nullptr) { + _nodeType = type; + auto rootCode = std::shared_ptr(new unsigned char[1], std::default_delete()); + *rootCode = 0; + + std::vector emptyEndNodes; + init(rootCode, emptyEndNodes); +} + +JurisdictionMap::JurisdictionMap(const char* filename) : _rootOctalCode(nullptr) { + readFromFile(filename); +} JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHexCodes) { qCDebug(octree, "JurisdictionMap::JurisdictionMap(const char* rootHexCode=[%p] %s, const char* endNodesHexCodes=[%p] %s)", rootHexCode, rootHexCode, endNodesHexCodes, endNodesHexCodes); - _rootOctalCode = hexStringToOctalCode(QString(rootHexCode)); + _rootOctalCode = std::shared_ptr(hexStringToOctalCode(QString(rootHexCode))); - qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode); - myDebugPrintOctalCode(_rootOctalCode, true); + qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode.get()); + myDebugPrintOctalCode(_rootOctalCode.get(), true); QString endNodesHexStrings(endNodesHexCodes); QString delimiterPattern(","); @@ -149,7 +106,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe for (int i = 0; i < endNodeList.size(); i++) { QString endNodeHexString = endNodeList.at(i); - unsigned char* endNodeOctcode = hexStringToOctalCode(endNodeHexString); + auto endNodeOctcode = hexStringToOctalCode(endNodeHexString); qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeList(%d)=%s", i, endNodeHexString.toLocal8Bit().constData()); @@ -158,14 +115,14 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe _endNodes.push_back(endNodeOctcode); qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode); - myDebugPrintOctalCode(endNodeOctcode, true); + myDebugPrintOctalCode(endNodeOctcode.get(), true); } } -void JurisdictionMap::init(unsigned char* rootOctalCode, const std::vector& endNodes) { - clear(); // clean up our own memory +void JurisdictionMap::init(OctalCodePtr rootOctalCode, const std::vector& endNodes) { + std::lock_guard lock(_octalCodeMutex); _rootOctalCode = rootOctalCode; _endNodes = endNodes; } @@ -173,17 +130,19 @@ void JurisdictionMap::init(unsigned char* rootOctalCode, const std::vector lock(_octalCodeMutex); + // if the node is an ancestor of my root, then we return ABOVE - if (isAncestorOf(nodeOctalCode, _rootOctalCode)) { + if (isAncestorOf(nodeOctalCode, _rootOctalCode.get())) { return ABOVE; } // otherwise... - bool isInJurisdiction = isAncestorOf(_rootOctalCode, nodeOctalCode, childIndex); + bool isInJurisdiction = isAncestorOf(_rootOctalCode.get(), nodeOctalCode, childIndex); // if we're under the root, then we can't be under any of the endpoints if (isInJurisdiction) { for (size_t i = 0; i < _endNodes.size(); i++) { - bool isUnderEndNode = isAncestorOf(_endNodes[i], nodeOctalCode); + bool isUnderEndNode = isAncestorOf(_endNodes[i].get(), nodeOctalCode); if (isUnderEndNode) { isInJurisdiction = false; break; @@ -200,8 +159,9 @@ bool JurisdictionMap::readFromFile(const char* filename) { QString rootCode = settings.value("root","00").toString(); qCDebug(octree) << "rootCode=" << rootCode; - _rootOctalCode = hexStringToOctalCode(rootCode); - printOctalCode(_rootOctalCode); + std::lock_guard lock(_octalCodeMutex); + _rootOctalCode = std::shared_ptr(hexStringToOctalCode(rootCode)); + printOctalCode(_rootOctalCode.get()); settings.beginGroup("endNodes"); const QStringList childKeys = settings.childKeys(); @@ -211,8 +171,8 @@ bool JurisdictionMap::readFromFile(const char* filename) { values.insert(childKey, childValue); qCDebug(octree) << childKey << "=" << childValue; - unsigned char* octcode = hexStringToOctalCode(childValue); - printOctalCode(octcode); + auto octcode = hexStringToOctalCode(childValue); + printOctalCode(octcode.get()); _endNodes.push_back(octcode); } @@ -221,30 +181,34 @@ bool JurisdictionMap::readFromFile(const char* filename) { } void JurisdictionMap::displayDebugDetails() const { - QString rootNodeValue = octalCodeToHexString(_rootOctalCode); + std::lock_guard lock(_octalCodeMutex); + + QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); qCDebug(octree) << "root:" << rootNodeValue; for (size_t i = 0; i < _endNodes.size(); i++) { - QString value = octalCodeToHexString(_endNodes[i]); + QString value = octalCodeToHexString(_endNodes[i].get()); qCDebug(octree) << "End node[" << i << "]: " << rootNodeValue; } } bool JurisdictionMap::writeToFile(const char* filename) { + std::lock_guard lock(_octalCodeMutex); + QString settingsFile(filename); QSettings settings(settingsFile, QSettings::IniFormat); - QString rootNodeValue = octalCodeToHexString(_rootOctalCode); + QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); settings.setValue("root", rootNodeValue); settings.beginGroup("endNodes"); for (size_t i = 0; i < _endNodes.size(); i++) { QString key = QString("endnode%1").arg(i); - QString value = octalCodeToHexString(_endNodes[i]); + QString value = octalCodeToHexString(_endNodes[i].get()); settings.setValue(key, value); } settings.endGroup(); @@ -271,18 +235,19 @@ std::unique_ptr JurisdictionMap::packIntoPacket() { packet->writePrimitive(type); // add the root jurisdiction + std::lock_guard lock(_octalCodeMutex); if (_rootOctalCode) { - size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode)); + size_t bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_rootOctalCode.get())); // No root or end node details to pack! packet->writePrimitive(bytes); - packet->write(reinterpret_cast(_rootOctalCode), bytes); + packet->write(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes int endNodeCount = (int)_endNodes.size(); packet->writePrimitive(endNodeCount); for (int i=0; i < endNodeCount; i++) { - unsigned char* endNodeCode = _endNodes[i]; + auto endNodeCode = _endNodes[i].get(); size_t bytes = 0; if (endNodeCode) { bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); @@ -299,15 +264,17 @@ std::unique_ptr JurisdictionMap::packIntoPacket() { } int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { - clear(); - // read the root jurisdiction int bytes = 0; message.readPrimitive(&bytes); + std::lock_guard lock(_octalCodeMutex); + _rootOctalCode = nullptr; + _endNodes.clear(); + if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { - _rootOctalCode = new unsigned char[bytes]; - message.read(reinterpret_cast(_rootOctalCode), bytes); + _rootOctalCode = std::shared_ptr(new unsigned char[bytes], std::default_delete()); + message.read(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes int endNodeCount = 0; @@ -318,8 +285,8 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { message.readPrimitive(&bytes); if (bytes <= message.getBytesLeftToRead()) { - unsigned char* endNodeCode = new unsigned char[bytes]; - message.read(reinterpret_cast(endNodeCode), bytes); + auto endNodeCode = std::shared_ptr(new unsigned char[bytes], std::default_delete()); + message.read(reinterpret_cast(endNodeCode.get()), bytes); // if the endNodeCode was 0 length then don't add it if (bytes > 0) { diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index fb217e59db..0deedb41e1 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -23,6 +23,7 @@ #include #include +#include class JurisdictionMap { public: @@ -41,7 +42,7 @@ public: // application constructors JurisdictionMap(const char* filename); - JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes); +// JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes); JurisdictionMap(const char* rootHextString, const char* endNodesHextString); ~JurisdictionMap(); @@ -50,11 +51,10 @@ public: bool writeToFile(const char* filename); bool readFromFile(const char* filename); - unsigned char* getRootOctalCode() const { return _rootOctalCode; } - unsigned char* getEndNodeOctalCode(int index) const { return _endNodes[index]; } - int getEndNodeCount() const { return (int)_endNodes.size(); } + OctalCodePtr getRootOctalCode() const { return _rootOctalCode; } + std::vector getEndNodeOctalCodes() const { return _endNodes; } - void copyContents(unsigned char* rootCodeIn, const std::vector& endNodesIn); + void copyContents(const OctalCodePtr& rootCodeIn, const std::vector& endNodesIn); int unpackFromPacket(ReceivedMessage& message); std::unique_ptr packIntoPacket(); @@ -69,11 +69,11 @@ public: private: void copyContents(const JurisdictionMap& other); // use assignment instead - void clear(); - void init(unsigned char* rootOctalCode, const std::vector& endNodes); + void init(OctalCodePtr rootOctalCode, const std::vector& endNodes); - unsigned char* _rootOctalCode; - std::vector _endNodes; + mutable std::mutex _octalCodeMutex; + OctalCodePtr _rootOctalCode { nullptr }; + std::vector _endNodes; NodeType_t _nodeType; }; diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 4a0a76cd02..28d3794a54 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -78,12 +78,12 @@ void OctreeHeadlessViewer::queryOctree() { } const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (!rootCode) { return; } - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); foundRootDetails = true; }); @@ -146,7 +146,7 @@ void OctreeHeadlessViewer::queryOctree() { } const JurisdictionMap& map = (jurisdictions)[nodeUUID]; - unsigned char* rootCode = map.getRootOctalCode(); + auto rootCode = map.getRootOctalCode(); if (!rootCode) { if (wantExtraDebugging) { @@ -154,7 +154,7 @@ void OctreeHeadlessViewer::queryOctree() { } return; } - voxelDetailsForCode(rootCode, rootDetails); + voxelDetailsForCode(rootCode.get(), rootDetails); foundRootDetails = true; }); diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 578d35b687..fd770ccd67 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -110,29 +110,21 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { _treesRemoved = other._treesRemoved; // before copying the jurisdictions, delete any current values... - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); // Now copy the values from the other if (other._jurisdictionRoot) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, other._jurisdictionRoot, bytes); + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot.get())); + _jurisdictionRoot = OctalCodePtr(new unsigned char[bytes]); + memcpy(_jurisdictionRoot.get(), other._jurisdictionRoot.get(), bytes); } for (size_t i = 0; i < other._jurisdictionEndNodes.size(); i++) { - unsigned char* endNodeCode = other._jurisdictionEndNodes[i]; + auto& endNodeCode = other._jurisdictionEndNodes[i]; if (endNodeCode) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); + auto endNodeCodeCopy = OctalCodePtr(new unsigned char[bytes]); + memcpy(endNodeCodeCopy.get(), endNodeCode.get(), bytes); _jurisdictionEndNodes.push_back(endNodeCodeCopy); } } @@ -162,37 +154,15 @@ void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, OctreeEleme _isFullScene = isFullScene; _isMoving = isMoving; - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } + _jurisdictionRoot = nullptr; + // clear existing endNodes before copying new ones... - for (size_t i=0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } _jurisdictionEndNodes.clear(); // setup jurisdictions if (jurisdictionMap) { - unsigned char* jurisdictionRoot = jurisdictionMap->getRootOctalCode(); - if (jurisdictionRoot) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(jurisdictionRoot)); - _jurisdictionRoot = new unsigned char[bytes]; - memcpy(_jurisdictionRoot, jurisdictionRoot, bytes); - } - - // copy new endNodes... - for (int i = 0; i < jurisdictionMap->getEndNodeCount(); i++) { - unsigned char* endNodeCode = jurisdictionMap->getEndNodeOctalCode(i); - if (endNodeCode) { - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); - unsigned char* endNodeCodeCopy = new unsigned char[bytes]; - memcpy(endNodeCodeCopy, endNodeCode, bytes); - _jurisdictionEndNodes.push_back(endNodeCodeCopy); - } - } + _jurisdictionRoot = jurisdictionMap->getRootOctalCode(); + _jurisdictionEndNodes = jurisdictionMap->getEndNodeOctalCodes(); } } @@ -270,15 +240,7 @@ void OctreeSceneStats::reset() { _existsInPacketBitsWritten = 0; _treesRemoved = 0; - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } @@ -418,9 +380,9 @@ int OctreeSceneStats::packIntoPacket() { // add the root jurisdiction if (_jurisdictionRoot) { // copy the - int bytes = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot)); + int bytes = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(_jurisdictionRoot.get())); _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(_jurisdictionRoot), bytes); + _statsPacket->write(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements int endNodeCount = (int)_jurisdictionEndNodes.size(); @@ -428,10 +390,10 @@ int OctreeSceneStats::packIntoPacket() { _statsPacket->writePrimitive(endNodeCount); for (int i=0; i < endNodeCount; i++) { - unsigned char* endNodeCode = _jurisdictionEndNodes[i]; - auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode)); + auto& endNodeCode = _jurisdictionEndNodes[i]; + auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); _statsPacket->writePrimitive(bytes); - _statsPacket->write(reinterpret_cast(endNodeCode), bytes); + _statsPacket->write(reinterpret_cast(endNodeCode.get()), bytes); } } else { int bytes = 0; @@ -500,17 +462,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&_existsInPacketBitsWritten); packet.readPrimitive(&_treesRemoved); // before allocating new juridiction, clean up existing ones - if (_jurisdictionRoot) { - delete[] _jurisdictionRoot; - _jurisdictionRoot = NULL; - } - - // clear existing endNodes before copying new ones... - for (size_t i = 0; i < _jurisdictionEndNodes.size(); i++) { - if (_jurisdictionEndNodes[i]) { - delete[] _jurisdictionEndNodes[i]; - } - } + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); // read the root jurisdiction @@ -518,11 +470,11 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); if (bytes == 0) { - _jurisdictionRoot = NULL; + _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } else { - _jurisdictionRoot = new unsigned char[bytes]; - packet.read(reinterpret_cast(_jurisdictionRoot), bytes); + _jurisdictionRoot = OctalCodePtr(new unsigned char[bytes]); + packet.read(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements _jurisdictionEndNodes.clear(); @@ -535,8 +487,8 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); - unsigned char* endNodeCode = new unsigned char[bytes]; - packet.read(reinterpret_cast(endNodeCode), bytes); + auto endNodeCode = OctalCodePtr(new unsigned char[bytes]); + packet.read(reinterpret_cast(endNodeCode.get()), bytes); _jurisdictionEndNodes.push_back(endNodeCode); } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 11aa0b82b2..a888ad2485 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -20,6 +20,7 @@ #include "JurisdictionMap.h" #include "OctreePacketData.h" #include "SequenceNumberStats.h" +#include "OctalCode.h" #define GREENISH 0x40ff40d0 #define YELLOWISH 0xffef40c0 @@ -143,10 +144,10 @@ public: const char* getItemValue(Item item); /// Returns OctCode for root element of the jurisdiction of this particular octree server - unsigned char* getJurisdictionRoot() const { return _jurisdictionRoot; } + OctalCodePtr getJurisdictionRoot() const { return _jurisdictionRoot; } /// Returns list of OctCodes for end elements of the jurisdiction of this particular octree server - const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } + const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } bool isMoving() const { return _isMoving; } bool isFullScene() const { return _isFullScene; } @@ -277,8 +278,8 @@ private: static const int MAX_ITEM_VALUE_LENGTH = 128; char _itemValueBuffer[MAX_ITEM_VALUE_LENGTH]; - unsigned char* _jurisdictionRoot; - std::vector _jurisdictionEndNodes; + OctalCodePtr _jurisdictionRoot; + std::vector _jurisdictionEndNodes; }; /// Map between element IDs and their reported OctreeSceneStats. Typically used by classes that need to know which elements sent diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 1fa18903cb..802ddbbb64 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -304,14 +304,14 @@ bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* po return true; } -unsigned char* hexStringToOctalCode(const QString& input) { +std::shared_ptr hexStringToOctalCode(const QString& input) { const int HEX_NUMBER_BASE = 16; const int HEX_BYTE_SIZE = 2; int stringIndex = 0; int byteArrayIndex = 0; // allocate byte array based on half of string length - unsigned char* bytes = new unsigned char[(input.length()) / HEX_BYTE_SIZE]; + auto bytes = std::shared_ptr(new unsigned char[(input.length()) / HEX_BYTE_SIZE]); // loop through the string - 2 bytes at a time converting // it to decimal equivalent and store in byte array @@ -321,15 +321,14 @@ unsigned char* hexStringToOctalCode(const QString& input) { if (!ok) { break; } - bytes[byteArrayIndex] = (unsigned char)value; + bytes.get()[byteArrayIndex] = (unsigned char)value; stringIndex += HEX_BYTE_SIZE; byteArrayIndex++; } // something went wrong if (!ok) { - delete[] bytes; - return NULL; + return nullptr; } return bytes; } diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 09766b685a..e8892fb40b 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -12,9 +12,10 @@ #ifndef hifi_OctalCode_h #define hifi_OctalCode_h -#include #include +#include + const int BITS_IN_OCTAL = 3; const int NUMBER_OF_COLORS = 3; // RGB! const int SIZE_OF_COLOR_DATA = NUMBER_OF_COLORS * sizeof(unsigned char); // size in bytes @@ -22,6 +23,8 @@ const int RED_INDEX = 0; const int GREEN_INDEX = 1; const int BLUE_INDEX = 2; +using OctalCodePtr = std::shared_ptr; + void printOctalCode(const unsigned char* octalCode); size_t bytesRequiredForCodeLength(unsigned char threeBitCodes); int branchIndexWithDescendant(const unsigned char* ancestorOctalCode, const unsigned char* descendantOctalCode); @@ -58,6 +61,6 @@ typedef enum { OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned char* code2); QString octalCodeToHexString(const unsigned char* octalCode); -unsigned char* hexStringToOctalCode(const QString& input); +std::shared_ptr hexStringToOctalCode(const QString& input); #endif // hifi_OctalCode_h From e819ab8475bfed2a6365defee8f0b4fa3fb9ecae Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 09:59:02 -0700 Subject: [PATCH 088/264] Add mutex protection around octal code getters --- libraries/octree/src/JurisdictionMap.cpp | 26 +++++++++++++++++++---- libraries/octree/src/JurisdictionMap.h | 9 ++++---- libraries/octree/src/OctreeSceneStats.cpp | 11 ++++------ libraries/shared/src/OctalCode.cpp | 2 +- libraries/shared/src/OctalCode.h | 3 ++- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 86de7467d3..30a8de1bda 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -70,7 +70,12 @@ void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const std::ve void JurisdictionMap::copyContents(const JurisdictionMap& other) { _nodeType = other._nodeType; - init(other.getRootOctalCode(), other.getEndNodeOctalCodes()); + OctalCodePtr rootOctalCode; + OctalCodePtrList endNodes; + + std::tie(rootOctalCode, endNodes) = other.getRootOctalCodeAndEndNodes(); + + init(rootOctalCode, endNodes); } JurisdictionMap::~JurisdictionMap() { @@ -120,6 +125,20 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe } } +std::tuple JurisdictionMap::getRootOctalCodeAndEndNodes() const { + std::lock_guard lock(_octalCodeMutex); + return std::tuple>(_rootOctalCode, _endNodes); +} + +OctalCodePtr JurisdictionMap::getRootOctalCode() const { + std::lock_guard lock(_octalCodeMutex); + return _rootOctalCode; +} + +OctalCodePtrList JurisdictionMap::getEndNodeOctalCodes() const { + std::lock_guard lock(_octalCodeMutex); + return _endNodes; +} void JurisdictionMap::init(OctalCodePtr rootOctalCode, const std::vector& endNodes) { std::lock_guard lock(_octalCodeMutex); @@ -160,7 +179,7 @@ bool JurisdictionMap::readFromFile(const char* filename) { qCDebug(octree) << "rootCode=" << rootCode; std::lock_guard lock(_octalCodeMutex); - _rootOctalCode = std::shared_ptr(hexStringToOctalCode(rootCode)); + _rootOctalCode = hexStringToOctalCode(rootCode); printOctalCode(_rootOctalCode.get()); settings.beginGroup("endNodes"); @@ -195,11 +214,10 @@ void JurisdictionMap::displayDebugDetails() const { bool JurisdictionMap::writeToFile(const char* filename) { - std::lock_guard lock(_octalCodeMutex); - QString settingsFile(filename); QSettings settings(settingsFile, QSettings::IniFormat); + std::lock_guard lock(_octalCodeMutex); QString rootNodeValue = octalCodeToHexString(_rootOctalCode.get()); diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 0deedb41e1..3b08fb221c 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -51,10 +51,11 @@ public: bool writeToFile(const char* filename); bool readFromFile(const char* filename); - OctalCodePtr getRootOctalCode() const { return _rootOctalCode; } - std::vector getEndNodeOctalCodes() const { return _endNodes; } + std::tuple JurisdictionMap::getRootOctalCodeAndEndNodes() const; + OctalCodePtr getRootOctalCode() const; + OctalCodePtrList getEndNodeOctalCodes() const; - void copyContents(const OctalCodePtr& rootCodeIn, const std::vector& endNodesIn); + void copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn); int unpackFromPacket(ReceivedMessage& message); std::unique_ptr packIntoPacket(); @@ -73,7 +74,7 @@ private: mutable std::mutex _octalCodeMutex; OctalCodePtr _rootOctalCode { nullptr }; - std::vector _endNodes; + OctalCodePtrList _endNodes; NodeType_t _nodeType; }; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index fd770ccd67..ca2bff32a4 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -154,15 +154,12 @@ void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, OctreeEleme _isFullScene = isFullScene; _isMoving = isMoving; - _jurisdictionRoot = nullptr; - - // clear existing endNodes before copying new ones... - _jurisdictionEndNodes.clear(); - // setup jurisdictions if (jurisdictionMap) { - _jurisdictionRoot = jurisdictionMap->getRootOctalCode(); - _jurisdictionEndNodes = jurisdictionMap->getEndNodeOctalCodes(); + std::tie(_jurisdictionRoot, _jurisdictionEndNodes) = jurisdictionMap->getRootOctalCodeAndEndNodes(); + } else { + _jurisdictionRoot = nullptr; + _jurisdictionEndNodes.clear(); } } diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 802ddbbb64..85b6c2b632 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -304,7 +304,7 @@ bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* po return true; } -std::shared_ptr hexStringToOctalCode(const QString& input) { +OctalCodePtr hexStringToOctalCode(const QString& input) { const int HEX_NUMBER_BASE = 16; const int HEX_BYTE_SIZE = 2; int stringIndex = 0; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index e8892fb40b..7d1424d954 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -24,6 +24,7 @@ const int GREEN_INDEX = 1; const int BLUE_INDEX = 2; using OctalCodePtr = std::shared_ptr; +using OctalCodePtrList = std::vector; void printOctalCode(const unsigned char* octalCode); size_t bytesRequiredForCodeLength(unsigned char threeBitCodes); @@ -61,6 +62,6 @@ typedef enum { OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned char* code2); QString octalCodeToHexString(const unsigned char* octalCode); -std::shared_ptr hexStringToOctalCode(const QString& input); +OctalCodePtr hexStringToOctalCode(const QString& input); #endif // hifi_OctalCode_h From 35f147f55703987d91678530d0356e348c370680 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 10:37:54 -0700 Subject: [PATCH 089/264] Cleanup use of OctalCodePtrList and add allocateOctalCodePtr --- libraries/octree/src/JurisdictionMap.cpp | 14 +++++++------- libraries/octree/src/JurisdictionMap.h | 4 ++-- libraries/octree/src/OctreeSceneStats.cpp | 8 ++++---- libraries/octree/src/OctreeSceneStats.h | 2 +- libraries/shared/src/OctalCode.cpp | 6 +++++- libraries/shared/src/OctalCode.h | 1 + 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 30a8de1bda..7eb976690b 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -63,7 +63,7 @@ JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode( copyContents(other); } -void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const std::vector& endNodesIn) { +void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn) { init(rootCodeIn, endNodesIn); } @@ -83,10 +83,10 @@ JurisdictionMap::~JurisdictionMap() { JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(nullptr) { _nodeType = type; - auto rootCode = std::shared_ptr(new unsigned char[1], std::default_delete()); + OctalCodePtr rootCode = allocateOctalCodePtr(1); *rootCode = 0; - std::vector emptyEndNodes; + OctalCodePtrList emptyEndNodes; init(rootCode, emptyEndNodes); } @@ -127,7 +127,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe std::tuple JurisdictionMap::getRootOctalCodeAndEndNodes() const { std::lock_guard lock(_octalCodeMutex); - return std::tuple>(_rootOctalCode, _endNodes); + return std::tuple(_rootOctalCode, _endNodes); } OctalCodePtr JurisdictionMap::getRootOctalCode() const { @@ -140,7 +140,7 @@ OctalCodePtrList JurisdictionMap::getEndNodeOctalCodes() const { return _endNodes; } -void JurisdictionMap::init(OctalCodePtr rootOctalCode, const std::vector& endNodes) { +void JurisdictionMap::init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes) { std::lock_guard lock(_octalCodeMutex); _rootOctalCode = rootOctalCode; _endNodes = endNodes; @@ -291,7 +291,7 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { _endNodes.clear(); if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { - _rootOctalCode = std::shared_ptr(new unsigned char[bytes], std::default_delete()); + _rootOctalCode = allocateOctalCodePtr(bytes); message.read(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes @@ -303,7 +303,7 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { message.readPrimitive(&bytes); if (bytes <= message.getBytesLeftToRead()) { - auto endNodeCode = std::shared_ptr(new unsigned char[bytes], std::default_delete()); + auto endNodeCode = allocateOctalCodePtr(bytes); message.read(reinterpret_cast(endNodeCode.get()), bytes); // if the endNodeCode was 0 length then don't add it diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 3b08fb221c..995b88e3ff 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -42,8 +42,8 @@ public: // application constructors JurisdictionMap(const char* filename); -// JurisdictionMap(unsigned char* rootOctalCode, const std::vector& endNodes); JurisdictionMap(const char* rootHextString, const char* endNodesHextString); + ~JurisdictionMap(); Area isMyJurisdiction(const unsigned char* nodeOctalCode, int childIndex) const; @@ -70,7 +70,7 @@ public: private: void copyContents(const JurisdictionMap& other); // use assignment instead - void init(OctalCodePtr rootOctalCode, const std::vector& endNodes); + void init(OctalCodePtr rootOctalCode, const OctalCodePtrList& endNodes); mutable std::mutex _octalCodeMutex; OctalCodePtr _rootOctalCode { nullptr }; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index ca2bff32a4..f164cd2d48 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -116,14 +116,14 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { // Now copy the values from the other if (other._jurisdictionRoot) { auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot.get())); - _jurisdictionRoot = OctalCodePtr(new unsigned char[bytes]); + _jurisdictionRoot = allocateOctalCodePtr(bytes); memcpy(_jurisdictionRoot.get(), other._jurisdictionRoot.get(), bytes); } for (size_t i = 0; i < other._jurisdictionEndNodes.size(); i++) { auto& endNodeCode = other._jurisdictionEndNodes[i]; if (endNodeCode) { auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); - auto endNodeCodeCopy = OctalCodePtr(new unsigned char[bytes]); + auto endNodeCodeCopy = allocateOctalCodePtr(bytes); memcpy(endNodeCodeCopy.get(), endNodeCode.get(), bytes); _jurisdictionEndNodes.push_back(endNodeCodeCopy); } @@ -470,7 +470,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } else { - _jurisdictionRoot = OctalCodePtr(new unsigned char[bytes]); + _jurisdictionRoot = allocateOctalCodePtr(bytes); packet.read(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements @@ -484,7 +484,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); - auto endNodeCode = OctalCodePtr(new unsigned char[bytes]); + auto endNodeCode = allocateOctalCodePtr(bytes); packet.read(reinterpret_cast(endNodeCode.get()), bytes); _jurisdictionEndNodes.push_back(endNodeCode); diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index a888ad2485..e7d697f785 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -147,7 +147,7 @@ public: OctalCodePtr getJurisdictionRoot() const { return _jurisdictionRoot; } /// Returns list of OctCodes for end elements of the jurisdiction of this particular octree server - const std::vector& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } + const OctalCodePtrList& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; } bool isMoving() const { return _isMoving; } bool isFullScene() const { return _isFullScene; } diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index 85b6c2b632..d6b9759619 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -304,6 +304,10 @@ bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* po return true; } +OctalCodePtr allocateOctalCodePtr(size_t size) { + return OctalCodePtr(new unsigned char[size], std::default_delete()); +} + OctalCodePtr hexStringToOctalCode(const QString& input) { const int HEX_NUMBER_BASE = 16; const int HEX_BYTE_SIZE = 2; @@ -311,7 +315,7 @@ OctalCodePtr hexStringToOctalCode(const QString& input) { int byteArrayIndex = 0; // allocate byte array based on half of string length - auto bytes = std::shared_ptr(new unsigned char[(input.length()) / HEX_BYTE_SIZE]); + auto bytes = allocateOctalCodePtr(input.length() / HEX_BYTE_SIZE); // loop through the string - 2 bytes at a time converting // it to decimal equivalent and store in byte array diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 7d1424d954..50381cb0d7 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -61,6 +61,7 @@ typedef enum { OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned char* code2); +OctalCodePtr allocateOctalCodePtr(size_t size); QString octalCodeToHexString(const unsigned char* octalCode); OctalCodePtr hexStringToOctalCode(const QString& input); From 5fc18eafda6f4d5614bfbbbeecd04a30b8bc5445 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 10:43:22 -0700 Subject: [PATCH 090/264] Rename OctalCodePtr related functions --- libraries/octree/src/JurisdictionMap.cpp | 10 +++++----- libraries/octree/src/JurisdictionMap.h | 3 ++- libraries/octree/src/OctreeSceneStats.cpp | 10 +++++----- libraries/shared/src/OctalCode.cpp | 4 ++-- libraries/shared/src/OctalCode.h | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 7eb976690b..1349b48b8d 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -73,7 +73,7 @@ void JurisdictionMap::copyContents(const JurisdictionMap& other) { OctalCodePtr rootOctalCode; OctalCodePtrList endNodes; - std::tie(rootOctalCode, endNodes) = other.getRootOctalCodeAndEndNodes(); + std::tie(rootOctalCode, endNodes) = other.getRootAndEndNodeOctalCodes(); init(rootOctalCode, endNodes); } @@ -83,7 +83,7 @@ JurisdictionMap::~JurisdictionMap() { JurisdictionMap::JurisdictionMap(NodeType_t type) : _rootOctalCode(nullptr) { _nodeType = type; - OctalCodePtr rootCode = allocateOctalCodePtr(1); + OctalCodePtr rootCode = createOctalCodePtr(1); *rootCode = 0; OctalCodePtrList emptyEndNodes; @@ -125,7 +125,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe } } -std::tuple JurisdictionMap::getRootOctalCodeAndEndNodes() const { +std::tuple JurisdictionMap::getRootAndEndNodeOctalCodes() const { std::lock_guard lock(_octalCodeMutex); return std::tuple(_rootOctalCode, _endNodes); } @@ -291,7 +291,7 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { _endNodes.clear(); if (bytes > 0 && bytes <= message.getBytesLeftToRead()) { - _rootOctalCode = allocateOctalCodePtr(bytes); + _rootOctalCode = createOctalCodePtr(bytes); message.read(reinterpret_cast(_rootOctalCode.get()), bytes); // if and only if there's a root jurisdiction, also include the end nodes @@ -303,7 +303,7 @@ int JurisdictionMap::unpackFromPacket(ReceivedMessage& message) { message.readPrimitive(&bytes); if (bytes <= message.getBytesLeftToRead()) { - auto endNodeCode = allocateOctalCodePtr(bytes); + auto endNodeCode = createOctalCodePtr(bytes); message.read(reinterpret_cast(endNodeCode.get()), bytes); // if the endNodeCode was 0 length then don't add it diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 995b88e3ff..2e39447eb0 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -51,7 +51,8 @@ public: bool writeToFile(const char* filename); bool readFromFile(const char* filename); - std::tuple JurisdictionMap::getRootOctalCodeAndEndNodes() const; + // Provide an atomic way to get both the rootOctalCode and endNodeOctalCodes. + std::tuple JurisdictionMap::getRootAndEndNodeOctalCodes() const; OctalCodePtr getRootOctalCode() const; OctalCodePtrList getEndNodeOctalCodes() const; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index f164cd2d48..fdaa6b4928 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -116,14 +116,14 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) { // Now copy the values from the other if (other._jurisdictionRoot) { auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(other._jurisdictionRoot.get())); - _jurisdictionRoot = allocateOctalCodePtr(bytes); + _jurisdictionRoot = createOctalCodePtr(bytes); memcpy(_jurisdictionRoot.get(), other._jurisdictionRoot.get(), bytes); } for (size_t i = 0; i < other._jurisdictionEndNodes.size(); i++) { auto& endNodeCode = other._jurisdictionEndNodes[i]; if (endNodeCode) { auto bytes = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(endNodeCode.get())); - auto endNodeCodeCopy = allocateOctalCodePtr(bytes); + auto endNodeCodeCopy = createOctalCodePtr(bytes); memcpy(endNodeCodeCopy.get(), endNodeCode.get(), bytes); _jurisdictionEndNodes.push_back(endNodeCodeCopy); } @@ -156,7 +156,7 @@ void OctreeSceneStats::sceneStarted(bool isFullScene, bool isMoving, OctreeEleme // setup jurisdictions if (jurisdictionMap) { - std::tie(_jurisdictionRoot, _jurisdictionEndNodes) = jurisdictionMap->getRootOctalCodeAndEndNodes(); + std::tie(_jurisdictionRoot, _jurisdictionEndNodes) = jurisdictionMap->getRootAndEndNodeOctalCodes(); } else { _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); @@ -470,7 +470,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { _jurisdictionRoot = nullptr; _jurisdictionEndNodes.clear(); } else { - _jurisdictionRoot = allocateOctalCodePtr(bytes); + _jurisdictionRoot = createOctalCodePtr(bytes); packet.read(reinterpret_cast(_jurisdictionRoot.get()), bytes); // if and only if there's a root jurisdiction, also include the end elements @@ -484,7 +484,7 @@ int OctreeSceneStats::unpackFromPacket(ReceivedMessage& packet) { packet.readPrimitive(&bytes); - auto endNodeCode = allocateOctalCodePtr(bytes); + auto endNodeCode = createOctalCodePtr(bytes); packet.read(reinterpret_cast(endNodeCode.get()), bytes); _jurisdictionEndNodes.push_back(endNodeCode); diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index d6b9759619..1a0a19bf44 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -304,7 +304,7 @@ bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* po return true; } -OctalCodePtr allocateOctalCodePtr(size_t size) { +OctalCodePtr createOctalCodePtr(size_t size) { return OctalCodePtr(new unsigned char[size], std::default_delete()); } @@ -315,7 +315,7 @@ OctalCodePtr hexStringToOctalCode(const QString& input) { int byteArrayIndex = 0; // allocate byte array based on half of string length - auto bytes = allocateOctalCodePtr(input.length() / HEX_BYTE_SIZE); + auto bytes = createOctalCodePtr(input.length() / HEX_BYTE_SIZE); // loop through the string - 2 bytes at a time converting // it to decimal equivalent and store in byte array diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 50381cb0d7..3fd9a49390 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -61,7 +61,7 @@ typedef enum { OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned char* code2); -OctalCodePtr allocateOctalCodePtr(size_t size); +OctalCodePtr createOctalCodePtr(size_t size); QString octalCodeToHexString(const unsigned char* octalCode); OctalCodePtr hexStringToOctalCode(const QString& input); From 368eded8eec6bdd897b731bf13c03425483454d2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 10:45:38 -0700 Subject: [PATCH 091/264] Remove unnecessary type in Jurisdictionmap --- libraries/octree/src/JurisdictionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 1349b48b8d..128a4aa162 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -99,7 +99,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe qCDebug(octree, "JurisdictionMap::JurisdictionMap(const char* rootHexCode=[%p] %s, const char* endNodesHexCodes=[%p] %s)", rootHexCode, rootHexCode, endNodesHexCodes, endNodesHexCodes); - _rootOctalCode = std::shared_ptr(hexStringToOctalCode(QString(rootHexCode))); + _rootOctalCode = hexStringToOctalCode(QString(rootHexCode)); qCDebug(octree, "JurisdictionMap::JurisdictionMap() _rootOctalCode=%p octalCode=", _rootOctalCode.get()); myDebugPrintOctalCode(_rootOctalCode.get(), true); From 1bec38584b52cf7770d991012f2e94d06aa3e03a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 11:11:52 -0700 Subject: [PATCH 092/264] Remove class qualification from header file --- libraries/octree/src/JurisdictionMap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 2e39447eb0..b5a311c3d9 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -52,7 +52,7 @@ public: bool readFromFile(const char* filename); // Provide an atomic way to get both the rootOctalCode and endNodeOctalCodes. - std::tuple JurisdictionMap::getRootAndEndNodeOctalCodes() const; + std::tuple getRootAndEndNodeOctalCodes() const; OctalCodePtr getRootOctalCode() const; OctalCodePtrList getEndNodeOctalCodes() const; From 475d881b043a2842806c6f3c29b8186ff829994b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 11:31:51 -0700 Subject: [PATCH 093/264] Fix logging of shared_ptr --- libraries/octree/src/JurisdictionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 128a4aa162..5d5d50f4ad 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -119,7 +119,7 @@ JurisdictionMap::JurisdictionMap(const char* rootHexCode, const char* endNodesHe //printOctalCode(endNodeOctcode); _endNodes.push_back(endNodeOctcode); - qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode); + qCDebug(octree, "JurisdictionMap::JurisdictionMap() endNodeOctcode=%p octalCode=", endNodeOctcode.get()); myDebugPrintOctalCode(endNodeOctcode.get(), true); } From 013d902376b332df07a70b4aaee9642ee9d03498 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 11 May 2016 11:50:00 -0700 Subject: [PATCH 094/264] Add vector include to OctalCode.h --- libraries/shared/src/OctalCode.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 3fd9a49390..a0d86f32d2 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -12,6 +12,7 @@ #ifndef hifi_OctalCode_h #define hifi_OctalCode_h +#include #include #include From 8ec280535bb9d6d97ab2b80eb58cc4eb98322169 Mon Sep 17 00:00:00 2001 From: samcake Date: Tue, 17 May 2016 11:13:34 -0700 Subject: [PATCH 095/264] FIx bad roughness value read from the deferred buffer --- libraries/render-utils/src/DeferredBufferRead.slh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index fa166300ae..569063955d 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -101,7 +101,7 @@ DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) { // Unpack the normal from the map frag.normal = normalize(frag.normalVal.xyz * 2.0 - vec3(1.0)); - frag.roughness = 2.0 * frag.normalVal.a; + frag.roughness = frag.normalVal.a; // Diffuse color and unpack the mode and the metallicness frag.diffuse = frag.diffuseVal.xyz; From e7087c7393bfb347987766dcf07dfb9a4ce87362 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 26 Apr 2016 11:03:20 -0700 Subject: [PATCH 096/264] Fix address bar becoming unusable after going backwards or forwards The address bar dialog was being disabled when a lookup request was finished, but was not hidden - this was bug #1. Presumably, though, the intent was to have the window _not_ disappear when going backwards or forwards, but hiding the window when a lookup request finishes would cause the window to be hidden when going backwards or forwards - this was bug #2. I decided to disable the hide-on-request-finished functionality because the window is already manually hidden after entering a new address. --- interface/src/ui/AddressBarDialog.cpp | 5 ----- interface/src/ui/AddressBarDialog.h | 1 - 2 files changed, 6 deletions(-) diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index b483160552..ba0cf18d32 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -23,7 +23,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare auto addressManager = DependencyManager::get(); connect(addressManager.data(), &AddressManager::lookupResultIsOffline, this, &AddressBarDialog::displayAddressOfflineMessage); connect(addressManager.data(), &AddressManager::lookupResultIsNotFound, this, &AddressBarDialog::displayAddressNotFoundMessage); - connect(addressManager.data(), &AddressManager::lookupResultsFinished, this, &AddressBarDialog::hide); connect(addressManager.data(), &AddressManager::goBackPossible, this, [this] (bool isPossible) { if (isPossible != _backEnabled) { _backEnabled = isPossible; @@ -40,10 +39,6 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare _forwardEnabled = !(DependencyManager::get()->getForwardStack().isEmpty()); } -void AddressBarDialog::hide() { - ((QQuickItem*)parent())->setEnabled(false); -} - void AddressBarDialog::loadAddress(const QString& address) { qDebug() << "Called LoadAddress with address " << address; if (!address.isEmpty()) { diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index eab1ebae69..b2751860cc 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -33,7 +33,6 @@ signals: protected: void displayAddressOfflineMessage(); void displayAddressNotFoundMessage(); - void hide(); Q_INVOKABLE void loadAddress(const QString& address); Q_INVOKABLE void loadHome(); From fde1f0740cf35129c5bd7622800f20291d25ab91 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 29 Apr 2016 11:03:22 -0700 Subject: [PATCH 097/264] Fix goBackPossible not being triggered correctly in AddressManager --- libraries/networking/src/AddressManager.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 757e145c9a..3e359e5ddb 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -600,13 +600,6 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything if (trigger != LookupTrigger::StartupFromSettings) { - if (trigger == LookupTrigger::UserInput) { - // anyime the user has manually looked up an address we know we should clear the forward stack - _forwardStack.clear(); - - emit goForwardPossible(false); - } - if (trigger == LookupTrigger::Back) { // we're about to push to the forward stack // if it's currently empty emit our signal to say that going forward is now possible @@ -618,9 +611,16 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // and do not but it into the back stack _forwardStack.push(currentAddress()); } else { + if (trigger == LookupTrigger::UserInput) { + // anyime the user has manually looked up an address we know we should clear the forward stack + _forwardStack.clear(); + + emit goForwardPossible(false); + } + // we're about to push to the back stack - // if it's currently empty emit our signal to say that going forward is now possible - if (_forwardStack.size() == 0) { + // if it's currently empty emit our signal to say that going backward is now possible + if (_backStack.size() == 0) { emit goBackPossible(true); } From f213a059fbad6cf64be51591b44f096a7f99596c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 4 May 2016 15:46:26 -0700 Subject: [PATCH 098/264] Fix AddressManager not always storing history when it should --- libraries/networking/src/AddressManager.cpp | 13 +++++++------ libraries/networking/src/AddressManager.h | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 3e359e5ddb..8e8ce9c4b0 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -310,7 +310,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const if (!returnedPath.isEmpty()) { if (shouldFaceViewpoint) { // try to parse this returned path as a viewpoint, that's the only thing it could be for now - if (!handleViewpoint(returnedPath, shouldFaceViewpoint)) { + if (!handleViewpoint(returnedPath, shouldFaceViewpoint, trigger)) { qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -" << returnedPath; } @@ -446,7 +446,7 @@ bool AddressManager::handleDomainID(const QString& host) { } void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly) { - if (!handleViewpoint(path, false, wasPathOnly)) { + if (!handleViewpoint(path, false, trigger, wasPathOnly)) { qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path << "- wll attempt to ask domain-server to resolve."; @@ -463,7 +463,7 @@ void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool } } -bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace, +bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, bool definitelyPathOnly, const QString& pathString) { const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; @@ -491,8 +491,9 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should // before moving to a new host thanks to the information in the same lookup URL. - if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath)) { - addCurrentAddressToHistory(LookupTrigger::UserInput); + if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath) + || trigger == Back || trigger == Forward) { + addCurrentAddressToHistory(trigger); } if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) { @@ -599,7 +600,7 @@ void AddressManager::copyPath() { void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything - if (trigger != LookupTrigger::StartupFromSettings) { + if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) { if (trigger == LookupTrigger::Back) { // we're about to push to the forward stack // if it's currently empty emit our signal to say that going forward is now possible diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index c0ba69018c..9bc9bd679b 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -46,7 +46,8 @@ public: UserInput, Back, Forward, - StartupFromSettings + StartupFromSettings, + DomainPathResponse }; bool isConnected(); @@ -77,7 +78,7 @@ public slots: // we currently expect this to be called from NodeList once handleLookupString has been called with a path bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) - { return handleViewpoint(viewpointString, false, false, pathString); } + { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } void goBack(); void goForward(); @@ -125,7 +126,7 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, bool shouldFace = false, + bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, bool definitelyPathOnly = false, const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); From 0ec9d9e277001e55902f0476eae1339eb866a79e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 4 May 2016 15:46:48 -0700 Subject: [PATCH 099/264] Remove goToAddressFromObject from slots --- libraries/networking/src/AddressManager.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 9bc9bd679b..be07fc9305 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -115,8 +115,9 @@ private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); private: + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); + void setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); void setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger); From d47c74408e332b8784940a202c348d365ef1058d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 4 May 2016 15:47:39 -0700 Subject: [PATCH 100/264] Cleanup handleViewpoint --- libraries/networking/src/AddressManager.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 8e8ce9c4b0..6ef6726827 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -501,6 +501,8 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should QRegExp orientationRegex(QUAT_REGEX_STRING); + bool orientationChanged = false; + // we may also have an orientation if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { @@ -512,14 +514,13 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z) && !isNaN(newOrientation.w)) { - emit locationChangeRequired(newPosition, true, newOrientation, shouldFace); - return true; + orientationChanged = true; } else { qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; } } - emit locationChangeRequired(newPosition, false, newOrientation, shouldFace); + emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); } else { qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value."; From 2dbea3769059ef7b5e85a8a9cd53c33035530847 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 May 2016 10:51:41 -0700 Subject: [PATCH 101/264] Fix address manager sometimes double adding address history --- libraries/networking/src/AddressManager.cpp | 45 +++++++++++++++------ libraries/networking/src/AddressManager.h | 10 +++-- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 6ef6726827..c5de5d629e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -147,8 +147,14 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (!handleUsername(lookupUrl.authority())) { // we're assuming this is either a network address or global place name // check if it is a network address first + bool hostChanged; if (handleNetworkAddress(lookupUrl.host() - + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger)) { + + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + + // If the host changed then we have already saved to history + if (hostChanged) { + trigger = Internal; + } // if we were not passed a path, use the index path auto path = lookupUrl.path(); @@ -170,13 +176,13 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } return true; + } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); - return true; } @@ -286,13 +292,19 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString PLACE_ID_KEY = "id"; _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); + bool addressChanged = false; + // set our current root place name to the name that came back const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); if (!placeName.isEmpty()) { - setHost(placeName, trigger); + if (setHost(placeName, trigger)) { + trigger = LookupTrigger::Internal; + } } else { - setHost(domainIDString, trigger); + if (setHost(domainIDString, trigger)) { + trigger = LookupTrigger::Internal; + } } // check if we had a path to override the path returned @@ -394,7 +406,7 @@ void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QS QByteArray(), NULL, requestParams); } -bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTrigger trigger) { +bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged) { const QString IP_ADDRESS_REGEX_STRING = "^((?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?::(\\d{1,5}))?$"; @@ -412,7 +424,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri } emit lookupResultsFinished(); - setDomainInfo(domainIPString, domainPort, trigger); + hostChanged = setDomainInfo(domainIPString, domainPort, trigger); return true; } @@ -429,11 +441,13 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri } emit lookupResultsFinished(); - setDomainInfo(domainHostname, domainPort, trigger); + hostChanged = setDomainInfo(domainHostname, domainPort, trigger); return true; } + hostChanged = false; + return false; } @@ -547,24 +561,27 @@ bool AddressManager::handleUsername(const QString& lookupString) { return false; } -void AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 port) { +bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 port) { if (host != _host || port != _port) { - _port = port; - - // if the host is being changed we should store current address in the history addCurrentAddressToHistory(trigger); + _port = port; + if (host != _host) { _host = host; emit hostChanged(_host); } + + return true; } + + return false; } -void AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { - setHost(hostname, trigger, port); +bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { + bool hostChanged = setHost(hostname, trigger, port); _rootPlaceID = QUuid(); @@ -573,6 +590,8 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); emit possibleDomainChangeRequired(hostname, port); + + return hostChanged; } void AddressManager::goToUser(const QString& username) { diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index be07fc9305..dd0dbd9f38 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -47,7 +47,8 @@ public: Back, Forward, StartupFromSettings, - DomainPathResponse + DomainPathResponse, + Internal }; bool isConnected(); @@ -118,14 +119,15 @@ private slots: private: void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); - void setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); - void setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger); + // Set host and port, and return `true` if it was changed. + bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); + bool setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger); const JSONCallbackParameters& apiCallbackParameters(); bool handleUrl(const QUrl& lookupUrl, LookupTrigger trigger = UserInput); - bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger); + bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, bool definitelyPathOnly = false, const QString& pathString = QString()); From cf6eba96a4a217603e458611a22283ca1ab3fbb7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 16 May 2016 11:20:22 -0700 Subject: [PATCH 102/264] Remove unused variable --- libraries/networking/src/AddressManager.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index c5de5d629e..27647d2694 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -292,8 +292,6 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString PLACE_ID_KEY = "id"; _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); - bool addressChanged = false; - // set our current root place name to the name that came back const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); From 9b7bf9789193e795f11b7a5e7c43ca03f762f240 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 11 May 2016 17:57:34 -0700 Subject: [PATCH 103/264] enable vertical thrust while walking --- libraries/physics/src/CharacterController.cpp | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 6ebe05ba86..705a8cfc88 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -187,40 +187,34 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); } - // decompose into horizontal and vertical components. - btVector3 actualVertVelocity = actualVelocity.dot(_currentUp) * _currentUp; - btVector3 actualHorizVelocity = actualVelocity - actualVertVelocity; - btVector3 desiredVertVelocity = desiredVelocity.dot(_currentUp) * _currentUp; - btVector3 desiredHorizVelocity = desiredVelocity - desiredVertVelocity; btVector3 finalVelocity; + const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f; - switch (_state) { - case State::Ground: - case State::Takeoff: - { - // horizontal ground control - const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f; - btScalar tau = dt / WALK_ACCELERATION_TIMESCALE; - finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity + actualVertVelocity; + if (_state == State::Hover) { + // simple case + btScalar tau = dt / FLY_ACCELERATION_TIMESCALE; + finalVelocity = tau * desiredVelocity + (1.0f - tau) * actualVelocity; + } else { + // ground states require special handling of horizontal and vertical components + btVector3 actualVertVelocity = actualVelocity.dot(_currentUp) * _currentUp; + btVector3 actualHorizVelocity = actualVelocity - actualVertVelocity; + btVector3 desiredVertVelocity = desiredVelocity.dot(_currentUp) * _currentUp; + btVector3 desiredHorizVelocity = desiredVelocity - desiredVertVelocity; + + // horizontal part + const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f; + const btScalar IN_AIR_ACCELERATION_TIMESCALE = 2.0f; + btScalar timescale = (_state == State::InAir) ? IN_AIR_ACCELERATION_TIMESCALE : WALK_ACCELERATION_TIMESCALE; + btScalar tau = dt / timescale; + finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity; + + // only blend vertical part if the target velocity has non-zero vertical component + const btScalar MIN_VERT_TARGET_SPEED_SQUARED = 1.0e-6; // 1mm/sec + if (desiredVertVelocity.length2() > MIN_VERT_TARGET_SPEED_SQUARED) { + tau = dt / FLY_ACCELERATION_TIMESCALE; + finalVelocity += tau * desiredVertVelocity + (1.0f - tau) * actualVertVelocity; } - break; - case State::InAir: - { - // horizontal air control - const btScalar IN_AIR_ACCELERATION_TIMESCALE = 2.0f; - btScalar tau = dt / IN_AIR_ACCELERATION_TIMESCALE; - finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity + actualVertVelocity; - } - break; - case State::Hover: - { - // vertical and horizontal air control - const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f; - btScalar tau = dt / FLY_ACCELERATION_TIMESCALE; - finalVelocity = tau * desiredVelocity + (1.0f - tau) * actualVelocity; - } - break; } _rigidBody->setLinearVelocity(finalVelocity + _parentVelocity); From 1749bfbc5890ef438d04f679ad75472f29e4a709 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 May 2016 10:15:23 -0700 Subject: [PATCH 104/264] transition to InAir state when not thrusting up --- libraries/physics/src/CharacterController.cpp | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 705a8cfc88..39710b63ab 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -173,17 +173,18 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) { _hasSupport = checkForSupport(collisionWorld); } +const btScalar MIN_TARGET_SPEED = 0.001f; +const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED; + void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { - const btScalar MIN_SPEED = 0.001f; - btVector3 actualVelocity = _rigidBody->getLinearVelocity() - _parentVelocity; - if (actualVelocity.length() < MIN_SPEED) { + if (actualVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { actualVelocity = btVector3(0.0f, 0.0f, 0.0f); } btVector3 desiredVelocity = _targetVelocity; - if (desiredVelocity.length() < MIN_SPEED) { + if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); } @@ -210,10 +211,11 @@ void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity; // only blend vertical part if the target velocity has non-zero vertical component - const btScalar MIN_VERT_TARGET_SPEED_SQUARED = 1.0e-6; // 1mm/sec - if (desiredVertVelocity.length2() > MIN_VERT_TARGET_SPEED_SQUARED) { + if (desiredVertVelocity.length2() > MIN_TARGET_SPEED_SQUARED) { tau = dt / FLY_ACCELERATION_TIMESCALE; finalVelocity += tau * desiredVertVelocity + (1.0f - tau) * actualVertVelocity; + } else { + finalVelocity += actualVertVelocity; } } @@ -507,10 +509,17 @@ void CharacterController::preSimulation() { case State::InAir: { if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < JUMP_PROXIMITY_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); - } else if (jumpButtonHeld && (_takeoffJumpButtonID != _jumpButtonDownCount)) { - SET_STATE(State::Hover, "double jump button"); - } else if (jumpButtonHeld && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { - SET_STATE(State::Hover, "jump button held"); + } else { + btVector3 desiredVelocity = _targetVelocity; + if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { + desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); + } + bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; + if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { + SET_STATE(State::Hover, "double jump button"); + } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { + SET_STATE(State::Hover, "jump button held"); + } } break; } From e4cb6e2b87a639bcdae4662bf406002222aeab55 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 13:57:04 -0700 Subject: [PATCH 105/264] order-independent CharacterController motors --- interface/src/avatar/MyAvatar.cpp | 263 ++++++++---------- interface/src/avatar/MyAvatar.h | 6 +- libraries/physics/src/CharacterController.cpp | 188 +++++++++---- libraries/physics/src/CharacterController.h | 26 +- 4 files changed, 284 insertions(+), 199 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ddc0407f14..24c41c9ea5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -59,9 +59,10 @@ using namespace std; const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f; -const float MAX_WALKING_SPEED = 2.5f; // human walking speed +const float MAX_WALKING_SPEED = 2.6f; // human walking speed const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // keyboard motor gets additive boost below this speed -const float MIN_AVATAR_SPEED = 0.05f; // speed is set to zero below this +const float MIN_AVATAR_SPEED = 0.05f; +const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this const float YAW_SPEED_DEFAULT = 120.0f; // degrees/sec const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec @@ -70,7 +71,6 @@ const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec // to properly follow avatar size. float MAX_AVATAR_SPEED = 30.0f; float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED; -float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f; float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; @@ -86,13 +86,13 @@ MyAvatar::MyAvatar(RigPointer rig) : Avatar(rig), _wasPushing(false), _isPushing(false), + _isBeingPushed(false), _isBraking(false), _boomLength(ZOOM_DEFAULT), _yawSpeed(YAW_SPEED_DEFAULT), _pitchSpeed(PITCH_SPEED_DEFAULT), _thrust(0.0f), _keyboardMotorVelocity(0.0f), - _keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE), _scriptedMotorVelocity(0.0f), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), @@ -246,7 +246,6 @@ void MyAvatar::reset(bool andRecenter) { _follow.deactivate(); _skeletonModel->reset(); getHead()->reset(); - _targetVelocity = glm::vec3(0.0f); setThrust(glm::vec3(0.0f)); if (andRecenter) { @@ -1166,8 +1165,47 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const { return getRightHandControllerPoseInWorldFrame().transform(invAvatarMatrix); } +void MyAvatar::updateMotors() { + _characterController.clearMotors(); + glm::quat motorRotation; + if (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED) { + if (_characterController.getState() == CharacterController::State::Hover) { + motorRotation = getHead()->getCameraOrientation(); + } else { + motorRotation = getOrientation(); + } + //const float DEFAULT_MOTOR_TIMESCALE = 0.7f; + const float DEFAULT_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + if (_isPushing || _isBraking || !_isBeingPushed) { + _characterController.addMotor(_keyboardMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + } else { + // _isBeingPushed must be true --> disable keyboard motor by giving it a long timescale, + // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts + _characterController.addMotor(_keyboardMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE); + } + } + if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { + if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { + motorRotation = getHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { + motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + } else { + // world-frame + motorRotation = glm::quat(); + } + _characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale); + } + + // legacy support for 'MyAvatar::applyThrust()', which has always been implemented as a + // short-lived linearAcceleration + _characterController.setLinearAcceleration(_thrust); + _thrust = Vectors::ZERO; +} + void MyAvatar::prepareForPhysicsSimulation() { relayDriveKeysToCharacterController(); + updateMotors(); bool success; glm::vec3 parentVelocity = getParentVelocity(success); @@ -1177,7 +1215,6 @@ void MyAvatar::prepareForPhysicsSimulation() { } _characterController.setParentVelocity(parentVelocity); - _characterController.setTargetVelocity(getTargetVelocity()); _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; @@ -1190,11 +1227,17 @@ void MyAvatar::prepareForPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); - _characterController.getPositionAndOrientation(position, orientation); + if (_characterController.isEnabled()) { + _characterController.getPositionAndOrientation(position, orientation); + } nextAttitude(position, orientation); _bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix); - setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); + if (_characterController.isEnabled()) { + setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); + } else { + setVelocity(getVelocity() + _characterController.getFollowVelocity()); + } } QString MyAvatar::getScriptedMotorFrame() const { @@ -1234,7 +1277,7 @@ void MyAvatar::setScriptedMotorFrame(QString frame) { } void MyAvatar::clearScriptableSettings() { - _scriptedMotorVelocity = glm::vec3(0.0f); + _scriptedMotorVelocity = Vectors::ZERO; _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; } @@ -1499,163 +1542,97 @@ void MyAvatar::updateOrientation(float deltaTime) { } } -glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool isHovering) { - if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) { - return localVelocity; - } - // compute motor efficiency - // The timescale of the motor is the approximate time it takes for the motor to - // accomplish its intended localVelocity. A short timescale makes the motor strong, - // and a long timescale makes it weak. The value of timescale to use depends - // on what the motor is doing: - // - // (1) braking --> short timescale (aggressive motor assertion) - // (2) pushing --> medium timescale (mild motor assertion) - // (3) inactive --> long timescale (gentle friction for low speeds) - const float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f; - const float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f; - const float MIN_KEYBOARD_BRAKE_SPEED = 0.3f; - float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE; - bool isThrust = (glm::length2(_thrust) > EPSILON); - if (_isPushing || isThrust || - (_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE && - (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED))) { - // we don't want to brake if something is pushing the avatar around - timescale = _keyboardMotorTimescale; +void MyAvatar::updateKeyboardMotor(float deltaTime) { + bool thrustIsPushing = (glm::length2(_thrust) > EPSILON); + bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) + && _scriptedMotorTimescale < MAX_CHARACTER_MOTOR_TIMESCALE; + _isBeingPushed = thrustIsPushing || scriptedMotorIsPushing; + if (_isPushing || _isBeingPushed) { + // we don't want the keyboard to brake if a script is pushing the avatar around + // (we assume the avatar is driving itself via script) _isBraking = false; } else { - float speed = glm::length(localVelocity); + float speed = glm::length(_keyboardMotorVelocity); + const float MIN_KEYBOARD_BRAKE_SPEED = 0.1f; _isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED); - if (_isBraking) { - timescale = MIN_KEYBOARD_MOTOR_TIMESCALE; - } } - _wasPushing = _isPushing || isThrust; - _isPushing = false; - float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); - glm::vec3 newLocalVelocity = localVelocity; + // compute keyboard input + glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; + glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; - // FIXME how do I implement step translation as well? - - - float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]); - if (keyboardInput) { - // Compute keyboard input - glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; - glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; + glm::vec3 direction = front + right; + CharacterController::State state = _characterController.getState(); + if (state == CharacterController::State::Hover) { + // we're flying --> support vertical motion glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP; + direction += up; + } - glm::vec3 direction = front + right + up; - float directionLength = glm::length(direction); + _wasPushing = _isPushing; + float directionLength = glm::length(direction); + _isPushing = directionLength > EPSILON; - //qCDebug(interfaceapp, "direction = (%.5f, %.5f, %.5f)", direction.x, direction.y, direction.z); - - // Compute motor magnitude - if (directionLength > EPSILON) { - direction /= directionLength; - - if (isHovering) { - // we're flying --> complex acceleration curve with high max speed - float motorSpeed = glm::length(_keyboardMotorVelocity); - float finalMaxMotorSpeed = getUniformScale() * MAX_KEYBOARD_MOTOR_SPEED; - float speedGrowthTimescale = 2.0f; - float speedIncreaseFactor = 1.8f; - motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; - const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED; - - if (motorSpeed < maxBoostSpeed) { - // an active keyboard motor should never be slower than this - float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; - motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; - motorEfficiency += (1.0f - motorEfficiency) * boostCoefficient; - } else if (motorSpeed > finalMaxMotorSpeed) { - motorSpeed = finalMaxMotorSpeed; - } - _keyboardMotorVelocity = motorSpeed * direction; - - } else { - // we're using a floor --> simple exponential decay toward target walk speed - const float WALK_ACCELERATION_TIMESCALE = 0.7f; // seconds to decrease delta to 1/e - _keyboardMotorVelocity = MAX_WALKING_SPEED * direction; - motorEfficiency = glm::clamp(deltaTime / WALK_ACCELERATION_TIMESCALE, 0.0f, 1.0f); - } - _isPushing = true; - } - newLocalVelocity = localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity); + // normalize direction + if (_isPushing) { + direction /= directionLength; } else { - _keyboardMotorVelocity = glm::vec3(0.0f); - newLocalVelocity = (1.0f - motorEfficiency) * localVelocity; - if (!isHovering && !_wasPushing) { - float speed = glm::length(newLocalVelocity); - if (speed > MIN_AVATAR_SPEED) { - // add small constant friction to help avatar drift to a stop sooner at low speeds - const float CONSTANT_FRICTION_DECELERATION = MIN_AVATAR_SPEED / 0.20f; - newLocalVelocity *= (speed - timescale * CONSTANT_FRICTION_DECELERATION) / speed; + direction = Vectors::ZERO; + } + + if (state == CharacterController::State::Hover) { + // we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed + float motorSpeed = glm::length(_keyboardMotorVelocity); + float finalMaxMotorSpeed = getUniformScale() * MAX_KEYBOARD_MOTOR_SPEED; + float speedGrowthTimescale = 2.0f; + float speedIncreaseFactor = 1.8f; + motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; + const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED; + + if (_isPushing) { + if (motorSpeed < maxBoostSpeed) { + // an active keyboard motor should never be slower than this + float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; + motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; + } else if (motorSpeed > finalMaxMotorSpeed) { + motorSpeed = finalMaxMotorSpeed; } } + _keyboardMotorVelocity = motorSpeed * direction; + } else { + // we're interacting with a floor --> simple horizontal speed and exponential decay + _keyboardMotorVelocity = MAX_WALKING_SPEED * direction; } float boomChange = _driveKeys[ZOOM]; _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); - - return newLocalVelocity; -} - -glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) { - // NOTE: localVelocity is in camera-frame because that's the frame of the default avatar motor - if (! (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) { - return localVelocity; - } - glm::vec3 deltaVelocity(0.0f); - if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - // camera frame - deltaVelocity = _scriptedMotorVelocity - localVelocity; - } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { - // avatar frame - glm::quat rotation = glm::inverse(getHead()->getCameraOrientation()) * getOrientation(); - deltaVelocity = rotation * _scriptedMotorVelocity - localVelocity; - } else { - // world-frame - glm::quat rotation = glm::inverse(getHead()->getCameraOrientation()); - deltaVelocity = rotation * _scriptedMotorVelocity - localVelocity; - } - float motorEfficiency = glm::clamp(deltaTime / _scriptedMotorTimescale, 0.0f, 1.0f); - return localVelocity + motorEfficiency * deltaVelocity; } void MyAvatar::updatePosition(float deltaTime) { - // rotate velocity into camera frame - glm::quat rotation = getHead()->getCameraOrientation(); - glm::vec3 localVelocity = glm::inverse(rotation) * _targetVelocity; - bool isHovering = _characterController.getState() == CharacterController::State::Hover; - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isHovering); - newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - - // rotate back into world-frame - _targetVelocity = rotation * newLocalVelocity; - - _targetVelocity += _thrust * deltaTime; - _thrust = glm::vec3(0.0f); - - // cap avatar speed - float speed = glm::length(_targetVelocity); - if (speed > MAX_AVATAR_SPEED) { - _targetVelocity *= MAX_AVATAR_SPEED / speed; - speed = MAX_AVATAR_SPEED; + if (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED) { + updateKeyboardMotor(deltaTime); } - if (speed > MIN_AVATAR_SPEED && !_characterController.isEnabled()) { - // update position ourselves - applyPositionDelta(deltaTime * _targetVelocity); + vec3 velocity = getVelocity(); + const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s + if (!_characterController.isEnabled()) { + // _characterController is not in physics simulation but it can still compute its target velocity + updateMotors(); + _characterController.computeNewVelocity(deltaTime, velocity); + + float speed2 = glm::length2(velocity); + if (speed2 > MIN_AVATAR_SPEED_SQUARED) { + // update position ourselves + applyPositionDelta(deltaTime * velocity); + } measureMotionDerivatives(deltaTime); - } // else physics will move avatar later - - // update _moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = speed > MOVING_SPEED_THRESHOLD; - + _moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED; + } else { + // physics physics simulation updated elsewhere + float speed2 = glm::length2(velocity); + _moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED; + } // capture the head rotation, in sensor space, when the user first indicates they would like to move/fly. if (!_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) > 0.1f || fabs(_driveKeys[TRANSLATE_X]) > 0.1f)) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 66e6af340e..68f5becdf7 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -218,6 +218,7 @@ public: MyCharacterController* getCharacterController() { return &_characterController; } const MyCharacterController* getCharacterController() const { return &_characterController; } + void updateMotors(); void prepareForPhysicsSimulation(); void harvestResultsFromPhysicsSimulation(float deltaTime); @@ -350,6 +351,7 @@ private: float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; bool _isPushing; + bool _isBeingPushed; bool _isBraking; float _boomLength; @@ -359,7 +361,6 @@ private: glm::vec3 _thrust; // impulse accumulator for outside sources glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard) - float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script) float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity int _scriptedMotorFrame; @@ -384,8 +385,7 @@ private: // private methods void updateOrientation(float deltaTime); - glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool isHovering); - glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity); + void updateKeyboardMotor(float deltaTime); void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 39710b63ab..7ba266d279 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -46,6 +46,20 @@ protected: btRigidBody* _me; }; +CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const glm::quat& rot, float horizTimescale, float vertTimescale) { + velocity = glmToBullet(vel); + rotation = glmToBullet(rot); + hTimescale = horizTimescale; + if (hTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { + hTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; + } + vTimescale = vertTimescale; + if (vTimescale < 0.0f) { + vTimescale = hTimescale; + } else if (vTimescale < MIN_CHARACTER_MOTOR_TIMESCALE) { + vTimescale = MIN_CHARACTER_MOTOR_TIMESCALE; + } +} CharacterController::CharacterController() { _halfHeight = 1.0f; @@ -177,52 +191,12 @@ const btScalar MIN_TARGET_SPEED = 0.001f; const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED; void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) { - - btVector3 actualVelocity = _rigidBody->getLinearVelocity() - _parentVelocity; - if (actualVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { - actualVelocity = btVector3(0.0f, 0.0f, 0.0f); - } - - btVector3 desiredVelocity = _targetVelocity; - if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { - desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); - } - - - btVector3 finalVelocity; - const btScalar FLY_ACCELERATION_TIMESCALE = 0.2f; - - if (_state == State::Hover) { - // simple case - btScalar tau = dt / FLY_ACCELERATION_TIMESCALE; - finalVelocity = tau * desiredVelocity + (1.0f - tau) * actualVelocity; - } else { - // ground states require special handling of horizontal and vertical components - btVector3 actualVertVelocity = actualVelocity.dot(_currentUp) * _currentUp; - btVector3 actualHorizVelocity = actualVelocity - actualVertVelocity; - btVector3 desiredVertVelocity = desiredVelocity.dot(_currentUp) * _currentUp; - btVector3 desiredHorizVelocity = desiredVelocity - desiredVertVelocity; - - // horizontal part - const btScalar WALK_ACCELERATION_TIMESCALE = 0.1f; - const btScalar IN_AIR_ACCELERATION_TIMESCALE = 2.0f; - btScalar timescale = (_state == State::InAir) ? IN_AIR_ACCELERATION_TIMESCALE : WALK_ACCELERATION_TIMESCALE; - btScalar tau = dt / timescale; - finalVelocity = tau * desiredHorizVelocity + (1.0f - tau) * actualHorizVelocity; - - // only blend vertical part if the target velocity has non-zero vertical component - if (desiredVertVelocity.length2() > MIN_TARGET_SPEED_SQUARED) { - tau = dt / FLY_ACCELERATION_TIMESCALE; - finalVelocity += tau * desiredVertVelocity + (1.0f - tau) * actualVertVelocity; - } else { - finalVelocity += actualVertVelocity; - } - } - - _rigidBody->setLinearVelocity(finalVelocity + _parentVelocity); + btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; + computeNewVelocity(dt, velocity); + _rigidBody->setLinearVelocity(velocity + _parentVelocity); // Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform. - // Rather then add this velocity to velocity the RigidBody, we explicitly teleport the RigidBody towards its goal. + // Rather than add this velocity to velocity the RigidBody, we explicitly teleport the RigidBody towards its goal. // This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate(). const float MINIMUM_TIME_REMAINING = 0.005f; @@ -389,10 +363,6 @@ void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::qu } } -void CharacterController::setTargetVelocity(const glm::vec3& velocity) { - _targetVelocity = glmToBullet(velocity); -} - void CharacterController::setParentVelocity(const glm::vec3& velocity) { _parentVelocity = glmToBullet(velocity); } @@ -434,6 +404,123 @@ glm::vec3 CharacterController::getVelocityChange() const { return velocity; } +void CharacterController::clearMotors() { + _motors.clear(); +} + +void CharacterController::addMotor(const glm::vec3& velocity, const glm::quat& rotation, float horizTimescale, float vertTimescale) { + _motors.push_back(CharacterController::CharacterMotor(velocity, rotation, horizTimescale, vertTimescale)); +} + +void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVelocity, std::vector& velocities, std::vector& weights) { + assert(index < (int)(_motors.size())); + CharacterController::CharacterMotor& motor = _motors[index]; + if (motor.hTimescale >= MAX_CHARACTER_MOTOR_TIMESCALE && motor.vTimescale >= MAX_CHARACTER_MOTOR_TIMESCALE) { + // nothing to do + return; + } + + // rotate into motor-frame + btVector3 axis = motor.rotation.getAxis(); + btScalar angle = motor.rotation.getAngle(); + btVector3 velocity = worldVelocity.rotate(axis, -angle); + btVector3 oldVelocity = velocity; + + if (_state == State::Hover || motor.hTimescale == motor.vTimescale) { + // modify velocity + btScalar tau = dt / motor.hTimescale; + if (tau > 1.0f) { + tau = 1.0f; + } + velocity += (motor.velocity - velocity) * tau; + + // rotate back into world-frame + velocity = velocity.rotate(axis, angle); + + // store the velocity and weight + velocities.push_back(velocity); + weights.push_back(tau); + } else { + // compute local UP + btVector3 up = _currentUp.rotate(axis, -angle); + + // split velocity into horizontal and vertical components + btVector3 vVelocity = velocity.dot(up) * up; + btVector3 hVelocity = velocity - vVelocity; + btVector3 vTargetVelocity = motor.velocity.dot(up) * up; + btVector3 hTargetVelocity = motor.velocity - vTargetVelocity; + + // modify each component separately + btScalar maxTau = 0.0f; + if (motor.hTimescale < MAX_CHARACTER_MOTOR_TIMESCALE) { + btScalar tau = dt / motor.hTimescale; + if (tau > 1.0f) { + tau = 1.0f; + } + maxTau = tau; + hVelocity += (hTargetVelocity - hVelocity) * tau; + } + if (motor.vTimescale < MAX_CHARACTER_MOTOR_TIMESCALE) { + btScalar tau = dt / motor.vTimescale; + if (tau > 1.0f) { + tau = 1.0f; + } + if (tau > maxTau) { + maxTau = tau; + } + vVelocity += (vTargetVelocity - vVelocity) * tau; + } + + // add components back together and rotate into world-frame + velocity = (hVelocity + vVelocity).rotate(axis, angle); + + // store velocity and weights + velocities.push_back(velocity); + weights.push_back(maxTau); + } +} + +void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { + if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) { + velocity = btVector3(0.0f, 0.0f, 0.0f); + } + + // measure velocity changes and their weights + std::vector velocities; + velocities.reserve(_motors.size()); + std::vector weights; + weights.reserve(_motors.size()); + for (size_t i = 0; i < _motors.size(); ++i) { + applyMotor(i, dt, velocity, velocities, weights); + } + assert(velocities.size() == weights.size()); + + // blend velocity changes according to relative weights + btScalar totalWeight = 0.0f; + for (size_t i = 0; i < weights.size(); ++i) { + totalWeight += weights[i]; + } + if (totalWeight > 0.0f) { + velocity = btVector3(0.0f, 0.0f, 0.0f); + for (size_t i = 0; i < velocities.size(); ++i) { + velocity += (weights[i] / totalWeight) * velocities[i]; + } + } + if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) { + velocity = btVector3(0.0f, 0.0f, 0.0f); + } + + // 'thrust' is applied at the very end + velocity += dt * _linearAcceleration; + _targetVelocity = velocity; +} + +void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) { + btVector3 btVelocity = glmToBullet(velocity); + computeNewVelocity(dt, btVelocity); + velocity = bulletToGLM(btVelocity); +} + void CharacterController::preSimulation() { if (_enabled && _dynamicsWorld) { quint64 now = usecTimestampNow(); @@ -443,9 +530,6 @@ void CharacterController::preSimulation() { btVector3 velocity = _rigidBody->getLinearVelocity(); _preSimulationVelocity = velocity; - btVector3 actualVertVelocity = velocity.dot(_currentUp) * _currentUp; - btVector3 actualHorizVelocity = velocity - actualVertVelocity; - // scan for distant floor // rayStart is at center of bottom sphere btVector3 rayStart = _characterBodyTransform.getOrigin() - _halfHeight * _currentUp; @@ -483,6 +567,8 @@ void CharacterController::preSimulation() { } bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP; + + btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp; bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f); switch (_state) { diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index a54ab97a14..ba82f07c97 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -31,7 +31,10 @@ class btRigidBody; class btCollisionWorld; class btDynamicsWorld; -//#define DEBUG_STATE_CHANGE +#define DEBUG_STATE_CHANGE + +const btScalar MAX_CHARACTER_MOTOR_TIMESCALE = 60.0f; // one minute +const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f; class CharacterController : public btCharacterControllerInterface { public: @@ -62,13 +65,21 @@ public: virtual void jump() override; virtual bool onGround() const override; + void clearMotors(); + void addMotor(const glm::vec3& velocity, const glm::quat& rotation, float horizTimescale, float vertTimescale = -1.0f); + void applyMotor(int index, btScalar dt, btVector3& worldVelocity, std::vector& velocities, std::vector& weights); + void computeNewVelocity(btScalar dt, btVector3& velocity); + void computeNewVelocity(btScalar dt, glm::vec3& velocity); + + // HACK for legacy 'thrust' feature + void setLinearAcceleration(const glm::vec3& acceleration) { _linearAcceleration = glmToBullet(acceleration); } + void preSimulation(); void postSimulation(); void setPositionAndOrientation( const glm::vec3& position, const glm::quat& orientation); void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; - void setTargetVelocity(const glm::vec3& velocity); void setParentVelocity(const glm::vec3& parentVelocity); void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining); float getFollowTime() const { return _followTime; } @@ -110,6 +121,16 @@ protected: bool checkForSupport(btCollisionWorld* collisionWorld) const; protected: + struct CharacterMotor { + CharacterMotor(const glm::vec3& vel, const glm::quat& rot, float horizTimescale, float vertTimescale = -1.0f); + + btVector3 velocity { btVector3(0.0f, 0.0f, 0.0f) }; // local-frame + btQuaternion rotation; // local-to-world + btScalar hTimescale { MAX_CHARACTER_MOTOR_TIMESCALE }; // horizontal + btScalar vTimescale { MAX_CHARACTER_MOTOR_TIMESCALE }; // vertical + }; + + std::vector _motors; btVector3 _currentUp; btVector3 _targetVelocity; btVector3 _parentVelocity; @@ -141,6 +162,7 @@ protected: btScalar _followTime; btVector3 _followLinearDisplacement; btQuaternion _followAngularDisplacement; + btVector3 _linearAcceleration; bool _enabled; State _state; From 2f39968a031d408d73ee22d91fc853fd57f8d33e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 14:32:20 -0700 Subject: [PATCH 106/264] remove debug cruft --- libraries/physics/src/CharacterController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 7ba266d279..a732bd3e5f 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -424,7 +424,6 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel btVector3 axis = motor.rotation.getAxis(); btScalar angle = motor.rotation.getAngle(); btVector3 velocity = worldVelocity.rotate(axis, -angle); - btVector3 oldVelocity = velocity; if (_state == State::Hover || motor.hTimescale == motor.vTimescale) { // modify velocity From c64612c42b6e7ffb0affcf933024217663960562 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 14:46:50 -0700 Subject: [PATCH 107/264] namechange keyboardMotor --> actionMotor --- interface/src/Menu.cpp | 2 +- interface/src/Menu.h | 2 +- interface/src/avatar/MyAvatar.cpp | 46 +++++++++++++++--------------- interface/src/avatar/MyAvatar.h | 6 ++-- libraries/avatars/src/AvatarData.h | 4 +-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 25e94f4e92..05bb03bf86 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -500,7 +500,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false, avatar, SLOT(setEnableDebugDrawSensorToWorldMatrix(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fdb136b900..59a6819bed 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -116,7 +116,7 @@ namespace MenuOption { const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; const QString InputMenu = "Avatar>Input Devices"; - const QString KeyboardMotorControl = "Enable Keyboard Motor Control"; + const QString ActionMotorControl = "Enable Default Motor Control"; const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; const QString LoadScriptURL = "Open and Run Script from URL..."; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 24c41c9ea5..49c3ea5876 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -60,7 +60,7 @@ const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f; const float MAX_WALKING_SPEED = 2.6f; // human walking speed -const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // keyboard motor gets additive boost below this speed +const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // action motor gets additive boost below this speed const float MIN_AVATAR_SPEED = 0.05f; const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this @@ -70,7 +70,7 @@ const float PITCH_SPEED_DEFAULT = 90.0f; // degrees/sec // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. float MAX_AVATAR_SPEED = 30.0f; -float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED; +float MAX_ACTION_MOTOR_SPEED = MAX_AVATAR_SPEED; float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; @@ -92,7 +92,7 @@ MyAvatar::MyAvatar(RigPointer rig) : _yawSpeed(YAW_SPEED_DEFAULT), _pitchSpeed(PITCH_SPEED_DEFAULT), _thrust(0.0f), - _keyboardMotorVelocity(0.0f), + _actionMotorVelocity(0.0f), _scriptedMotorVelocity(0.0f), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), @@ -1168,7 +1168,7 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const { void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; - if (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED) { + if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover) { motorRotation = getHead()->getCameraOrientation(); } else { @@ -1178,11 +1178,11 @@ void MyAvatar::updateMotors() { const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_keyboardMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); } else { - // _isBeingPushed must be true --> disable keyboard motor by giving it a long timescale, + // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts - _characterController.addMotor(_keyboardMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE); } } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { @@ -1542,22 +1542,22 @@ void MyAvatar::updateOrientation(float deltaTime) { } } -void MyAvatar::updateKeyboardMotor(float deltaTime) { +void MyAvatar::updateActionMotor(float deltaTime) { bool thrustIsPushing = (glm::length2(_thrust) > EPSILON); bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) && _scriptedMotorTimescale < MAX_CHARACTER_MOTOR_TIMESCALE; _isBeingPushed = thrustIsPushing || scriptedMotorIsPushing; if (_isPushing || _isBeingPushed) { - // we don't want the keyboard to brake if a script is pushing the avatar around + // we don't want the motor to brake if a script is pushing the avatar around // (we assume the avatar is driving itself via script) _isBraking = false; } else { - float speed = glm::length(_keyboardMotorVelocity); - const float MIN_KEYBOARD_BRAKE_SPEED = 0.1f; - _isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED); + float speed = glm::length(_actionMotorVelocity); + const float MIN_ACTION_BRAKE_SPEED = 0.1f; + _isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED); } - // compute keyboard input + // compute action input glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; @@ -1582,8 +1582,8 @@ void MyAvatar::updateKeyboardMotor(float deltaTime) { if (state == CharacterController::State::Hover) { // we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed - float motorSpeed = glm::length(_keyboardMotorVelocity); - float finalMaxMotorSpeed = getUniformScale() * MAX_KEYBOARD_MOTOR_SPEED; + float motorSpeed = glm::length(_actionMotorVelocity); + float finalMaxMotorSpeed = getUniformScale() * MAX_ACTION_MOTOR_SPEED; float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor; @@ -1591,17 +1591,17 @@ void MyAvatar::updateKeyboardMotor(float deltaTime) { if (_isPushing) { if (motorSpeed < maxBoostSpeed) { - // an active keyboard motor should never be slower than this + // an active action motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; motorSpeed += MIN_AVATAR_SPEED * boostCoefficient; } else if (motorSpeed > finalMaxMotorSpeed) { motorSpeed = finalMaxMotorSpeed; } } - _keyboardMotorVelocity = motorSpeed * direction; + _actionMotorVelocity = motorSpeed * direction; } else { // we're interacting with a floor --> simple horizontal speed and exponential decay - _keyboardMotorVelocity = MAX_WALKING_SPEED * direction; + _actionMotorVelocity = MAX_WALKING_SPEED * direction; } float boomChange = _driveKeys[ZOOM]; @@ -1610,8 +1610,8 @@ void MyAvatar::updateKeyboardMotor(float deltaTime) { } void MyAvatar::updatePosition(float deltaTime) { - if (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED) { - updateKeyboardMotor(deltaTime); + if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + updateActionMotor(deltaTime); } vec3 velocity = getVelocity(); @@ -1779,10 +1779,10 @@ void MyAvatar::updateMotionBehaviorFromMenu() { } Menu* menu = Menu::getInstance(); - if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { - _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; + if (menu->isOptionChecked(MenuOption::ActionMotorControl)) { + _motionBehaviors |= AVATAR_MOTION_ACTION_MOTOR_ENABLED; } else { - _motionBehaviors &= ~AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; + _motionBehaviors &= ~AVATAR_MOTION_ACTION_MOTOR_ENABLED; } if (menu->isOptionChecked(MenuOption::ScriptedMotorControl)) { _motionBehaviors |= AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 68f5becdf7..1b335fe294 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -360,8 +360,8 @@ private: glm::vec3 _thrust; // impulse accumulator for outside sources - glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard) - glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script) + glm::vec3 _actionMotorVelocity; // target local-frame velocity of avatar (default controller actions) + glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (analog script) float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity int _scriptedMotorFrame; quint32 _motionBehaviors; @@ -385,7 +385,7 @@ private: // private methods void updateOrientation(float deltaTime); - void updateKeyboardMotor(float deltaTime); + void updateActionMotor(float deltaTime); void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a7b97ef4c0..e9dc100a7c 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -65,11 +65,11 @@ using AvatarHash = QHash; using AvatarDataSequenceNumber = uint16_t; // avatar motion behaviors -const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0; +const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0; const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; const quint32 AVATAR_MOTION_DEFAULTS = - AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED | + AVATAR_MOTION_ACTION_MOTOR_ENABLED | AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; // these bits will be expanded as features are exposed From aa171578b7fb1d60463bc9532e3fcbc5b38f64f2 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 14:58:14 -0700 Subject: [PATCH 108/264] remove cruft --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 49c3ea5876..7d08367c8d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1174,7 +1174,6 @@ void MyAvatar::updateMotors() { } else { motorRotation = getOrientation(); } - //const float DEFAULT_MOTOR_TIMESCALE = 0.7f; const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; if (_isPushing || _isBraking || !_isBeingPushed) { From b82356a249f00b6678bd161d6f88eb366bf2f39f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 17 May 2016 15:02:04 -0700 Subject: [PATCH 109/264] AvatarMixer: Clients will show incompatible version dialog For this to work, the server needs to send an empty AvatarIdentity packet back to the sender when it receives a packet mismatch error. This AvatarIdentity packet will be different then what the client expects and will trigger the incompatible version dialog. Previously, the avatar-mixer was just silently dropping incoming mismatched version packets. Causing the client to never get a response, and thus never showing the incompatible version dialog. --- assignment-client/src/avatars/AvatarMixer.cpp | 16 ++++++++++++++++ assignment-client/src/avatars/AvatarMixer.h | 3 ++- libraries/networking/src/LimitedNodeList.cpp | 7 ++++--- libraries/networking/src/LimitedNodeList.h | 4 +++- libraries/networking/src/udt/PacketHeaders.cpp | 4 +++- libraries/networking/src/udt/PacketHeaders.h | 2 +- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index a109934d10..610c9bcc40 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -45,6 +45,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); + + auto nodeList = DependencyManager::get(); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); } AvatarMixer::~AvatarMixer() { @@ -509,6 +512,19 @@ void AvatarMixer::domainSettingsRequestComplete() { _broadcastThread.start(); } +void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) { + // if this client is using packet versions we don't expect. + if ((type == PacketTypeEnum::Value::AvatarIdentity || type == PacketTypeEnum::Value::AvatarData) && !senderUUID.isNull()) { + // Echo an empty AvatarIdentity packet back to that client. + // This should trigger a version mismatch dialog on their side. + auto nodeList = DependencyManager::get(); + auto node = nodeList->nodeWithUUID(senderUUID); + if (node) { + auto poisonPacket = NLPacket::create(PacketType::AvatarIdentity, 0); + nodeList->sendPacket(std::move(poisonPacket), *node); + } + } +} void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c7761a2cba..d1a9249c83 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -38,7 +38,8 @@ private slots: void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); - + void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + private: void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c10d0627e..9efe51183e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -176,9 +176,10 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { bool hasBeenOutput = false; QString senderString; + const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); + QUuid sourceID; if (NON_SOURCED_PACKETS.contains(headerType)) { - const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType); if (!hasBeenOutput) { @@ -186,7 +187,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); } } else { - QUuid sourceID = NLPacket::sourceIDInHeader(packet); + sourceID = NLPacket::sourceIDInHeader(packet); hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType); @@ -201,7 +202,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { << senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but" << qPrintable(QString::number(versionForPacketType(headerType))) << "expected."; - emit packetVersionMismatch(headerType); + emit packetVersionMismatch(headerType, senderSockAddr, sourceID); } return false; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 0cbe9668b3..2ab8aaab39 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -236,7 +236,9 @@ public slots: signals: void dataSent(quint8 channelType, int bytes); - void packetVersionMismatch(PacketType type); + + // QUuid might be zero for non-sourced packet types. + void packetVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e4aab94090..81984521f8 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -48,9 +48,11 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: return VERSION_LIGHT_HAS_FALLOFF_RADIUS; + case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: - return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); + case PacketType::KillAvatar: + return static_cast(AvatarMixerPacketVersion::AbsoluteSixByteRotations); case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing case PacketType::AssetGetInfo: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e0d854ab71..4c2141dff4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -175,7 +175,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, - AbsoluteFortyEightBitRotations + AbsoluteSixByteRotations }; #endif // hifi_PacketHeaders_h From 47d897f66967415d399e3ef5054dc311b522b634 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 May 2016 15:12:14 -0700 Subject: [PATCH 110/264] Fix processOctreeStats recreating JurisdictionMap for every stat packet --- interface/src/Application.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4d772d4b19..a60adeceb5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4372,8 +4372,11 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer serverType = "Entity"; } + bool found = false; + jurisdiction->withReadLock([&] { if (jurisdiction->find(nodeUUID) != jurisdiction->end()) { + found = true; return; } @@ -4384,15 +4387,18 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer qPrintable(serverType), (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); - // store jurisdiction details for later use - // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it - // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the - // details from the OctreeSceneStats to construct the JurisdictionMap - JurisdictionMap jurisdictionMap; - jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); - jurisdiction->withWriteLock([&] { - (*jurisdiction)[nodeUUID] = jurisdictionMap; - }); + + if (!found) { + // store jurisdiction details for later use + // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it + // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the + // details from the OctreeSceneStats to construct the JurisdictionMap + JurisdictionMap jurisdictionMap; + jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); + jurisdiction->withWriteLock([&] { + (*jurisdiction)[nodeUUID] = jurisdictionMap; + }); + } }); return statsMessageLength; From 68665e6b2579fa63e9cf0af6ce293af78afdf1dd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 May 2016 15:12:45 -0700 Subject: [PATCH 111/264] Fix JurisdictionMap not initializing correctly --- libraries/octree/src/JurisdictionMap.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 5d5d50f4ad..fcbc085339 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -64,7 +64,14 @@ JurisdictionMap::JurisdictionMap(const JurisdictionMap& other) : _rootOctalCode( } void JurisdictionMap::copyContents(const OctalCodePtr& rootCodeIn, const OctalCodePtrList& endNodesIn) { - init(rootCodeIn, endNodesIn); + OctalCodePtr rootCode = rootCodeIn; + if (!rootCode) { + rootCode = createOctalCodePtr(1); + *rootCode = 0; + } + + OctalCodePtrList emptyEndNodes; + init(rootCode, endNodesIn); } void JurisdictionMap::copyContents(const JurisdictionMap& other) { From aab26c5089935508c0256f180af52dec01c5abcd Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 15:14:36 -0700 Subject: [PATCH 112/264] disable DEBUG_STATE_CHANGE --- libraries/physics/src/CharacterController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index ba82f07c97..0d73289cb0 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -31,7 +31,7 @@ class btRigidBody; class btCollisionWorld; class btDynamicsWorld; -#define DEBUG_STATE_CHANGE +//#define DEBUG_STATE_CHANGE const btScalar MAX_CHARACTER_MOTOR_TIMESCALE = 60.0f; // one minute const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f; From 9786c0bfd390c3e4f43a71611fedddeee21735f3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 May 2016 15:34:09 -0700 Subject: [PATCH 113/264] remove warning about signed/unsigned conversion --- libraries/physics/src/CharacterController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index a732bd3e5f..9ef059bb05 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -489,7 +489,7 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) { velocities.reserve(_motors.size()); std::vector weights; weights.reserve(_motors.size()); - for (size_t i = 0; i < _motors.size(); ++i) { + for (int i = 0; i < (int)_motors.size(); ++i) { applyMotor(i, dt, velocity, velocities, weights); } assert(velocities.size() == weights.size()); From dc6e1afae668ddb683a9ce6b4413cee30a9b00e1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 17 May 2016 16:34:30 -0700 Subject: [PATCH 114/264] Changed empty AvatarIdentity packet to AvatarData packet Just in-case it actually gets through, it will fail to be parsed by AvatarData::parseDataFromBuffer() due to it's size. AvatarData::hasIdentityChangedAfterParsing() has no such checks. --- assignment-client/src/avatars/AvatarMixer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 610c9bcc40..cc94e4f1b7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -515,13 +515,13 @@ void AvatarMixer::domainSettingsRequestComplete() { void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) { // if this client is using packet versions we don't expect. if ((type == PacketTypeEnum::Value::AvatarIdentity || type == PacketTypeEnum::Value::AvatarData) && !senderUUID.isNull()) { - // Echo an empty AvatarIdentity packet back to that client. + // Echo an empty AvatarData packet back to that client. // This should trigger a version mismatch dialog on their side. auto nodeList = DependencyManager::get(); auto node = nodeList->nodeWithUUID(senderUUID); if (node) { - auto poisonPacket = NLPacket::create(PacketType::AvatarIdentity, 0); - nodeList->sendPacket(std::move(poisonPacket), *node); + auto emptyPacket = NLPacket::create(PacketType::AvatarData, 0); + nodeList->sendPacket(std::move(emptyPacket), *node); } } } From 2c6b0e5c95f4aa86c3096db047809def5bae66b7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 17 May 2016 17:32:09 -0700 Subject: [PATCH 115/264] Fix for linux warning. --- libraries/shared/src/GLMHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index e81958f407..e73e936fb9 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -151,7 +151,7 @@ int packOrientationQuatToSixBytes(unsigned char* buffer, const glm::quat& quatIn // ensure that the sign of the dropped component is always negative. glm::quat q = quatInput[largestComponent] > 0 ? -quatInput : quatInput; - const float MAGNITUDE = 1.0f / sqrt(2.0f); + const float MAGNITUDE = 1.0f / sqrtf(2.0f); const uint32_t NUM_BITS_PER_COMPONENT = 15; const uint32_t RANGE = (1 << NUM_BITS_PER_COMPONENT) - 1; From 17d41dd7b977c833e97961c64df3752ed574a73b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 18 May 2016 13:25:11 +1200 Subject: [PATCH 116/264] HiFi glyphs font file update with fixed up/down carats Update QML and HTML spinboxes accordingly. --- interface/resources/fonts/hifi-glyphs.ttf | Bin 23944 -> 24204 bytes .../resources/qml/controls-uit/SpinBox.qml | 6 +++--- .../qml/styles-uit/HifiConstants.qml | 2 +- scripts/system/html/edit-style.css | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 1b0d4f3fe61a60a3cf499f3557dd29bb924bc924..89d47670122b68a27ce80d77da0227390d817ac1 100644 GIT binary patch delta 960 zcmYL|TTGi}7{`Ba`yriL4&&T@UxC6X9V?WwavHFXL17!GaVYJ8!pE_&GDtz0GsrG> zu`Ih-{H88*F_Q~ixG-j&-iTg!VUR4*L?b3M1e3)qlPfc$$zm|=miQ!Z-v9g0`#gCM zzvt?g;`&!&1pyejjGiv9x3_(Px3?fT56CS|&0cQt3Iw}=yw097SLa4s2M9ml8=F<< z`~F?;0NoAHS*O&6`Tc$L5NrhuQ{lHJo$}rqBwUB?kkN-@vaxWhgYUqDoG& z3nCpT49zZH%zm2o9f;3>o$%b4S~l2R1kwZGms$1VJewj2qB?e<3+n8|AC;Rv=-&eo zou6A+Z2mCmJqX+dX)S&^0jW=3T^dfV`-|6f#NtOay!>L;g_qwI_2RtNAcn*N?T#4I zo{JG}SXWx}1O05X9Z%3he8};N2pN4`$w;yH!+?PorQg{-pl#@smOAQb;5a8}qsG*i>`sv~^VHQh96!-`@^rJe@6?&Lc2~EfqjP$u zwaHUcTixGv)-Nl$Q+fH;g2F>ZwqpC?k|UL+WzO=dilgqHq23nXG0iBMq(E?BP%D?N zXdcO+eJ<6lIA8zPc*NLb3>#OCJ4vRb_2iD^pHfCsK22>+T~Av~dy?);|04ZqhLjP> zxSx4Ab24)^>tI$m>+9^U?DunAIk!zIrbg3M)6Zs$dD6UL{#~}nL3vC5N0F2irB)eL zZYVnzmu1v))ABg?UY;-SL4Gj*8|!XCcfnra{q!Ph(L-CC?UHS?_;T@Gd$zsM?u=eZ z_#i7fpj@*?Gd#PQT8`q;?B#ztws|BR;$ZBT4%sS%uEh7K3Iy9c77k&K`68i_TZ5K delta 954 zcmY+CTTGjE7{-79wm`=y$I{ZnR|iUW=)K7P7OD@g{aX8H`CySUJ!PN#%MB3nK!=4oA=3+H&31??;Cw0 zZao&O2*ALbXz6Wh8|;pJxV;X+dBEP@(%Qy-5+T?HIC};>-k#69xxOTunxNSkHQJ~?aPn0CerHu;JAi({a9|F9h{X3#w%&9&R6ngcc2#W93zl5UQ zVMh&UJRah4j@o{thOnYNLUwe#jK@UJE7=>ndi5)fv|5jk2F}q$Gc8=8jdlWb(nU8H zDd!TW>7kcC`Z-T4jns0MGmJ36Afwok$i+z>`4k{iND(fIIl)OvIE9;PJd{#~ml`Ul zqzXTm8KQ$>>eMl9rDY6-aV7|Ig-NEEW`d)s>>o^>rkkcsv)3Fo|B@5V`NYz1`OI2sy<%Or z{%P~sZrYyP6YW0x4f~#yBgs;!R3~*v5$S>Sjl<-ab3AZt<=S(1o$omJ^X}#SU65le zxGl@_UeSn4=UQ_;DyH~Th(#0bo7H#yzF3|V&G!g)cq-h2_zeqPMEcZKR6zANU`&Ea*u7!ZTv68UOmL{{G$ z`A45LvlMwYO diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 599d94c28d..5d78cfc5a8 100755 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -56,7 +56,7 @@ SpinBox { incrementControl: HiFiGlyphs { id: incrementButton text: hifi.glyphs.caratUp - x: 6 + x: 10 y: 1 size: hifi.dimensions.spinnerSize color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray @@ -64,8 +64,8 @@ SpinBox { decrementControl: HiFiGlyphs { text: hifi.glyphs.caratDn - x: 6 - y: -3 + x: 10 + y: -1 size: hifi.dimensions.spinnerSize color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 640fe8625b..16f150b2d9 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -141,7 +141,7 @@ Item { readonly property real textPadding: 8 readonly property real sliderHandleSize: 18 readonly property real sliderGrooveHeight: 8 - readonly property real spinnerSize: 42 + readonly property real spinnerSize: 50 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 readonly property real tableHeaderHeight: 29 diff --git a/scripts/system/html/edit-style.css b/scripts/system/html/edit-style.css index 5eaa3c6497..4083e580b5 100644 --- a/scripts/system/html/edit-style.css +++ b/scripts/system/html/edit-style.css @@ -268,7 +268,7 @@ input[type=number]::-webkit-inner-spin-button { height: 100%; overflow: hidden; font-family: hifi-glyphs; - font-size: 50px; + font-size: 46px; color: #afafaf; cursor: pointer; /*background-color: #000000;*/ @@ -276,17 +276,17 @@ input[type=number]::-webkit-inner-spin-button { input[type=number]::-webkit-inner-spin-button:before, input[type=number]::-webkit-inner-spin-button:after { position:absolute; - left: -21px; + left: -19px; line-height: 8px; text-align: center; } input[type=number]::-webkit-inner-spin-button:before { content: "6"; - top: 5px; + top: 4px; } input[type=number]::-webkit-inner-spin-button:after { content: "5"; - bottom: 6px; + bottom: 4px; } input[type=number].hover-up::-webkit-inner-spin-button:before, From e1d885004f55b98681f94ebc4e92170cad584472 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 18 May 2016 13:59:13 +1200 Subject: [PATCH 117/264] Fix carat in drop-down boxes --- interface/resources/qml/controls-uit/ComboBox.qml | 2 +- scripts/system/html/edit-style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index f99e18b12b..cd6dc8ede0 100755 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -91,7 +91,7 @@ FocusScope { HiFiGlyphs { anchors { top: parent.top - topMargin: -8 + topMargin: -11 horizontalCenter: parent.horizontalCenter } size: hifi.dimensions.spinnerSize diff --git a/scripts/system/html/edit-style.css b/scripts/system/html/edit-style.css index 4083e580b5..19d1cd95a9 100644 --- a/scripts/system/html/edit-style.css +++ b/scripts/system/html/edit-style.css @@ -613,7 +613,7 @@ hr { margin-right: -48px; position: relative; left: -12px; - top: -11px; + top: -9px; } .dropdown dd { From cf829f1babf58f4d442bdebdb8696f2c7f164fdf Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 17 May 2016 19:14:44 -0700 Subject: [PATCH 118/264] magic number --- interface/src/avatar/Avatar.cpp | 2 +- libraries/entities/src/SimulationOwner.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 95a3ef6132..efaed5b83a 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -220,7 +220,7 @@ void Avatar::updateAvatarEntities() { // there's no entity-server to tell us we're the simulation owner, so always set the // simulationOwner to the owningAvatarID and a high priority. - properties.setSimulationOwner(getID(), 129); + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); if (properties.getParentID() == AVATAR_SELF_ID) { properties.setParentID(getID()); diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 1afec426d7..5f940bbe25 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -27,6 +27,7 @@ const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower From 59e17c2d5f66e801c889349f114cb3c154e4e54e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 17 May 2016 22:13:56 -0700 Subject: [PATCH 119/264] Fix abs of unsigned warning --- libraries/shared/src/GLMHelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index bd8bffefd2..784e9ce5af 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -435,8 +435,8 @@ void generateBasisVectors(const glm::vec3& primaryAxis, const glm::vec3& seconda #ifndef NDEBUG const float MIN_LENGTH_SQUARED = 1.0e-6f; #endif - assert(fabsf(glm::length2(primaryAxis) > MIN_LENGTH_SQUARED)); - assert(fabsf(glm::length2(secondaryAxis) > MIN_LENGTH_SQUARED)); + assert(glm::length2(primaryAxis) > MIN_LENGTH_SQUARED); + assert(glm::length2(secondaryAxis) > MIN_LENGTH_SQUARED); uAxisOut = glm::normalize(primaryAxis); glm::vec3 normSecondary = glm::normalize(secondaryAxis); From 0c5adf83ce3b8d45ad50944611c973e41d66512e Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 May 2016 08:43:53 -0700 Subject: [PATCH 120/264] fix hands --- unpublishedScripts/DomainContent/Home/reset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index a488562a66..fa57ca4cfe 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -621,7 +621,7 @@ var rightHandPosition = MyAvatar.getRightPalmPosition(); var rightDistance = Vec3.distance(leftHandPosition, BEAM_POSITION) - var leftDistance = Vec3.distance(leftHandPosition, BEAM_POSITION) + var leftDistance = Vec3.distance(rightHandPosition, BEAM_POSITION) if (rightDistance < BEAM_TRIGGER_THRESHOLD || leftDistance < BEAM_TRIGGER_THRESHOLD) { _this.toggleButton(); From 946558d0f6fb77942f3a14685d66e2c960f085ff Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 May 2016 08:53:13 -0700 Subject: [PATCH 121/264] cleanup --- .../DomainContent/Home/reset.js | 52 +++++-------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index fa57ca4cfe..eeaeb7db9e 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -74,8 +74,6 @@ z: -74.2998 }; - - Reset.prototype = { tidying: false, eyesOn: false, @@ -97,7 +95,7 @@ "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png" - } + }; Entities.editEntity(_this.entityID, { @@ -106,34 +104,31 @@ }, tidyGlowActive: function() { - print('tidy glow active') var res = Entities.findEntities(MyAvatar.position, 30); var glow = null; + res.forEach(function(item) { var props = Entities.getEntityProperties(item); if (props.name === "Tidyguy-Glow") { glow = item; } + }); - - }) if (glow !== null) { - print('editing glow' + glow) Entities.editEntity(glow, { color: { red: 255, green: 140, blue: 20 } - }) + }); } }, tidyGlowDefault: function() { - print('tidy glow default') - var res = Entities.findEntities(MyAvatar.position, 30); var glow = null; + res.forEach(function(item) { var props = Entities.getEntityProperties(item); if (props.name === "Tidyguy-Glow") { @@ -143,16 +138,13 @@ }); if (glow !== null) { - print('editing glow' + glow) - Entities.editEntity(glow, { - color: { red: 92, green: 255, blue: 209 } - }) + }); } }, @@ -167,8 +159,7 @@ "tex.face.screen-active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/face-active.jpg", "tex.glow.active": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-active.png", "tex.glow.default": "atp:/Tidyguy-4d.fbx/Tidyguy-4d.fbm/glow-default.png" - } - + }; Entities.editEntity(_this.entityID, { textures: JSON.stringify(data) @@ -186,9 +177,6 @@ volume: 1, position: location }); - - print('INJECTOR IS: ' + injector) - }, toggleButton: function() { @@ -212,12 +200,10 @@ _this.createScriptedEntities(); _this.setupDressingRoom(); }, 750); - } }, flickerEyes: function() { - this.flickerInterval = Script.setInterval(function() { if (_this.eyesOn === true) { _this.turnEyesOff(); @@ -230,7 +216,7 @@ Script.setTimeout(function() { Script.clearInterval(_this.flickerInterval); - }, 2500) + }, 2500); }, turnEyesOn: function() { @@ -274,13 +260,8 @@ return; } _this.toggleButton(); - }, - // startNearTrigger: function() { - // _this.toggleButton(); - // }, - findAndDeleteHomeEntities: function() { print('HOME trying to find home entities to delete') var resetProperties = Entities.getEntityProperties(_this.entityID); @@ -288,12 +269,10 @@ var found = []; results.forEach(function(result) { var properties = Entities.getEntityProperties(result); - if (properties.userData === "" || properties.userData === undefined) { print('no userdata -- its blank or undefined') return; } - var userData = null; try { userData = JSON.parse(properties.userData); @@ -307,10 +286,9 @@ Entities.deleteEntity(result); } } - - }) - print('HOME after deleting home entities') + + print('HOME after deleting home entities'); }, createScriptedEntities: function() { @@ -444,11 +422,11 @@ y: 461, z: -73.3 }); - print('HOME after creating kinetic entities') + print('HOME after creating kinetic entities'); }, setupDressingRoom: function() { - print('HOME setup dressing room') + print('HOME setup dressing room'); this.createRotatorBlock(); this.createTransformingDais(); this.createTransformers(); @@ -495,7 +473,7 @@ } var rotatorBlock = Entities.addEntity(rotatorBlockProps); - print('HOME created rotator block') + print('HOME created rotator block'); }, createTransformingDais: function() { @@ -625,16 +603,12 @@ if (rightDistance < BEAM_TRIGGER_THRESHOLD || leftDistance < BEAM_TRIGGER_THRESHOLD) { _this.toggleButton(); - } else { - //too far to toggle swipe } }, unload: function() { Script.update.disconnect(_this.update); - Script.clearInterval(_this.flickerInterval); - // this.findAndDeleteHomeEntities(); } } From 5d1b2996d3ab7a84100d592af8d61fbd43864bb8 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 May 2016 08:53:57 -0700 Subject: [PATCH 122/264] semicolon --- unpublishedScripts/DomainContent/Home/reset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index eeaeb7db9e..ba10604e4d 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -592,7 +592,7 @@ update: function() { if (_this.tidying === true) { - return + return; } var leftHandPosition = MyAvatar.getLeftPalmPosition(); From d24ec08f9f69e394282a3956ff3383fa210d52a7 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Wed, 18 May 2016 08:55:02 -0700 Subject: [PATCH 123/264] cleanup --- unpublishedScripts/DomainContent/Home/reset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unpublishedScripts/DomainContent/Home/reset.js b/unpublishedScripts/DomainContent/Home/reset.js index ba10604e4d..f6e5a01c7c 100644 --- a/unpublishedScripts/DomainContent/Home/reset.js +++ b/unpublishedScripts/DomainContent/Home/reset.js @@ -171,7 +171,7 @@ x: 1102.7660, y: 460.9814, z: -78.6451 - } + }; var injector = Audio.playSound(_this.tidySound, { volume: 1, From 8026a5e7f04ddd829e1068c5b0b65b9d97a26a0f Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Thu, 12 May 2016 10:55:43 -0700 Subject: [PATCH 124/264] fix utils paths --- .../DomainContent/Toybox/basketball/createRack.js | 2 +- unpublishedScripts/DomainContent/Toybox/bow/bow.js | 2 +- unpublishedScripts/DomainContent/Toybox/bow/createBow.js | 2 +- .../DomainContent/Toybox/bubblewand/createWand.js | 2 +- unpublishedScripts/DomainContent/Toybox/bubblewand/wand.js | 2 +- unpublishedScripts/DomainContent/Toybox/doll/doll.js | 2 +- .../DomainContent/Toybox/flashlight/createFlashlight.js | 2 +- .../DomainContent/Toybox/flashlight/flashlight.js | 2 +- .../DomainContent/Toybox/lights/lightSwitch.js | 2 +- .../DomainContent/Toybox/ping_pong_gun/createPingPongGun.js | 2 +- .../DomainContent/Toybox/ping_pong_gun/createTargets.js | 2 +- .../DomainContent/Toybox/ping_pong_gun/pingPongGun.js | 2 +- unpublishedScripts/DomainContent/Toybox/pistol/pistol.js | 2 +- .../DomainContent/Toybox/spray_paint/sprayPaintCan.js | 5 +---- 14 files changed, 14 insertions(+), 17 deletions(-) diff --git a/unpublishedScripts/DomainContent/Toybox/basketball/createRack.js b/unpublishedScripts/DomainContent/Toybox/basketball/createRack.js index 3ab288dfc4..8209a88f33 100644 --- a/unpublishedScripts/DomainContent/Toybox/basketball/createRack.js +++ b/unpublishedScripts/DomainContent/Toybox/basketball/createRack.js @@ -9,7 +9,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -Script.include("libraries/utils.js"); +Script.include("../libraries/utils.js"); var basketballURL ="http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/basketball/basketball2.fbx"; var collisionSoundURL = "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/basketball/basketball.wav"; diff --git a/unpublishedScripts/DomainContent/Toybox/bow/bow.js b/unpublishedScripts/DomainContent/Toybox/bow/bow.js index 1e3c0ef0eb..b287966d2c 100644 --- a/unpublishedScripts/DomainContent/Toybox/bow/bow.js +++ b/unpublishedScripts/DomainContent/Toybox/bow/bow.js @@ -11,7 +11,7 @@ (function() { - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); var NOTCH_ARROW_SOUND_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bow/notch.wav'; var SHOOT_ARROW_SOUND_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bow/String_release2.L.wav'; diff --git a/unpublishedScripts/DomainContent/Toybox/bow/createBow.js b/unpublishedScripts/DomainContent/Toybox/bow/createBow.js index ab64a55f38..5deec3f6bc 100644 --- a/unpublishedScripts/DomainContent/Toybox/bow/createBow.js +++ b/unpublishedScripts/DomainContent/Toybox/bow/createBow.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var utilsPath = Script.resolvePath('libraries/utils.js'); +var utilsPath = Script.resolvePath('../libraries/utils.js'); Script.include(utilsPath); var SCRIPT_URL = Script.resolvePath('bow.js'); diff --git a/unpublishedScripts/DomainContent/Toybox/bubblewand/createWand.js b/unpublishedScripts/DomainContent/Toybox/bubblewand/createWand.js index 848aa0dfae..0a7d8b7055 100644 --- a/unpublishedScripts/DomainContent/Toybox/bubblewand/createWand.js +++ b/unpublishedScripts/DomainContent/Toybox/bubblewand/createWand.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -Script.include("libraries/utils.js"); +Script.include("../libraries/utils.js"); var WAND_MODEL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bubblewand/wand.fbx'; var WAND_COLLISION_SHAPE = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bubblewand/wand_collision_hull.obj'; diff --git a/unpublishedScripts/DomainContent/Toybox/bubblewand/wand.js b/unpublishedScripts/DomainContent/Toybox/bubblewand/wand.js index fbef018131..6f63fe3ec9 100644 --- a/unpublishedScripts/DomainContent/Toybox/bubblewand/wand.js +++ b/unpublishedScripts/DomainContent/Toybox/bubblewand/wand.js @@ -14,7 +14,7 @@ (function() { - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); var BUBBLE_MODEL = "http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/bubblewand/bubble.fbx"; diff --git a/unpublishedScripts/DomainContent/Toybox/doll/doll.js b/unpublishedScripts/DomainContent/Toybox/doll/doll.js index 9abf7c33c0..c2e2ef2cda 100644 --- a/unpublishedScripts/DomainContent/Toybox/doll/doll.js +++ b/unpublishedScripts/DomainContent/Toybox/doll/doll.js @@ -13,7 +13,7 @@ /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ (function() { - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); var _this; // this is the "constructor" for the entity as a JS object we don't do much here var Doll = function() { diff --git a/unpublishedScripts/DomainContent/Toybox/flashlight/createFlashlight.js b/unpublishedScripts/DomainContent/Toybox/flashlight/createFlashlight.js index d6414fca6e..9bcc6dae58 100644 --- a/unpublishedScripts/DomainContent/Toybox/flashlight/createFlashlight.js +++ b/unpublishedScripts/DomainContent/Toybox/flashlight/createFlashlight.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -Script.include("libraries/utils.js"); +Script.include("../libraries/utils.js"); var scriptURL = Script.resolvePath('flashlight.js'); diff --git a/unpublishedScripts/DomainContent/Toybox/flashlight/flashlight.js b/unpublishedScripts/DomainContent/Toybox/flashlight/flashlight.js index e58d68c425..5cbf93538a 100644 --- a/unpublishedScripts/DomainContent/Toybox/flashlight/flashlight.js +++ b/unpublishedScripts/DomainContent/Toybox/flashlight/flashlight.js @@ -17,7 +17,7 @@ /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ (function() { - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); var ON_SOUND_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/flashlight/flashlight_on.wav'; var OFF_SOUND_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/flashlight/flashlight_off.wav'; diff --git a/unpublishedScripts/DomainContent/Toybox/lights/lightSwitch.js b/unpublishedScripts/DomainContent/Toybox/lights/lightSwitch.js index 77e89b3111..2a1ef04ed3 100644 --- a/unpublishedScripts/DomainContent/Toybox/lights/lightSwitch.js +++ b/unpublishedScripts/DomainContent/Toybox/lights/lightSwitch.js @@ -17,7 +17,7 @@ (function() { var _this; - var utilitiesScript = Script.resolvePath("libraries/utils.js"); + var utilitiesScript = Script.resolvePath("../libraries/utils.js"); Script.include(utilitiesScript); LightSwitch = function() { _this = this; diff --git a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createPingPongGun.js b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createPingPongGun.js index 5f0c00a7bc..22812c1206 100644 --- a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createPingPongGun.js +++ b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createPingPongGun.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -Script.include("../../libraries/utils.js"); +Script.include("../libraries/utils.js"); var scriptURL = Script.resolvePath('pingPongGun.js'); diff --git a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createTargets.js b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createTargets.js index 1dfe02e31c..c52d2bff32 100644 --- a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createTargets.js +++ b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/createTargets.js @@ -11,7 +11,7 @@ // /*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ -Script.include("utils.js"); +Script.include("../libraries/utils.js"); var scriptURL = Script.resolvePath('wallTarget.js'); var MODEL_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/ping_pong_gun/target.fbx'; diff --git a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/pingPongGun.js b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/pingPongGun.js index 37c909cb6e..81a293a830 100644 --- a/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/pingPongGun.js +++ b/unpublishedScripts/DomainContent/Toybox/ping_pong_gun/pingPongGun.js @@ -11,7 +11,7 @@ /*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ (function() { - Script.include("utils.js"); + Script.include("../libraries/utils.js"); var SHOOTING_SOUND_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/ping_pong_gun/pong_sound.wav'; var PING_PONG_BALL_URL = 'http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/ping_pong_gun/ping_pong_ball.fbx'; diff --git a/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js b/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js index 3371248474..5f57c6fc17 100644 --- a/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js +++ b/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js @@ -11,7 +11,7 @@ (function() { - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); var _this; var DISABLE_LASER_THRESHOLD = 0.2; diff --git a/unpublishedScripts/DomainContent/Toybox/spray_paint/sprayPaintCan.js b/unpublishedScripts/DomainContent/Toybox/spray_paint/sprayPaintCan.js index 9d906eaf42..54534ef656 100644 --- a/unpublishedScripts/DomainContent/Toybox/spray_paint/sprayPaintCan.js +++ b/unpublishedScripts/DomainContent/Toybox/spray_paint/sprayPaintCan.js @@ -10,10 +10,7 @@ (function () { - // Script.include("../libraries/utils.js"); - //Need absolute path for now, for testing before PR merge and s3 cloning. Will change post-merge - - Script.include("libraries/utils.js"); + Script.include("../libraries/utils.js"); this.spraySound = SoundCache.getSound("http://hifi-production.s3.amazonaws.com/DomainContent/Toybox/spray_paint/spray_paint.wav"); From 10c412d8c7c2ff06573ec4f3d631f45bdf79a045 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 16 May 2016 18:43:59 -0700 Subject: [PATCH 125/264] Fix settings getting reset fixes bugzid:531 When a setting handle was created but never initialized/used, it would override the current value with the default value on destruction. --- libraries/shared/src/SettingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 6c5431a13e..a931875771 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -97,7 +97,7 @@ namespace Setting { } void Interface::deinit() { - if (privateInstance) { + if (_isInitialized && privateInstance) { // Save value to disk save(); From de36cd150e8d00dd82df9e6b002c4730fd6eaa4c Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 16 May 2016 19:48:20 -0700 Subject: [PATCH 126/264] Move runnings scripts setting storage So that it doesn't conflict with the "Settings" menu setting storage. Running script would clear those settings while storing its data This adds some backward compatible code to move the scripts settings to the new location. --- libraries/script-engine/src/ScriptEngines.cpp | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 70eb055d22..4fd680025a 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -256,7 +256,7 @@ QVariantList ScriptEngines::getRunning() { } -static const QString SETTINGS_KEY = "Settings"; +static const QString SETTINGS_KEY = "RunningScripts"; void ScriptEngines::loadDefaultScripts() { QUrl defaultScriptsLoc = defaultScriptsLocation(); @@ -281,6 +281,43 @@ void ScriptEngines::loadScripts() { // loads all saved scripts Settings settings; + + + // START of backward compatibility code + // This following if statement is only meant to update the settings file still using the old setting key. + // If you read that comment and it has been more than a couple months since it was merged, + // then by all means, feel free to remove it. + if (!settings.childGroups().contains(SETTINGS_KEY)) { + qWarning() << "Detected old script settings config, loading from previous location"; + const QString oldKey = "Settings"; + + // Load old scripts array from settings + int size = settings.beginReadArray(oldKey); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QString string = settings.value("script").toString(); + if (!string.isEmpty()) { + loadScript(string); + } + } + settings.endArray(); + + // Cleanup old scripts array from settings + settings.beginWriteArray(oldKey); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + settings.remove(""); + } + settings.endArray(); + settings.beginGroup(oldKey); + settings.remove("size"); + settings.endGroup(); + + return; + } + // END of backward compatibility code + + int size = settings.beginReadArray(SETTINGS_KEY); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); From b48134e30cefb6d9fa00ae7bddfbd32bcbc68919 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 11:40:45 -0700 Subject: [PATCH 127/264] WIP commit testing for joint mapping transmission --- interface/src/avatar/Avatar.cpp | 4 -- interface/src/avatar/Avatar.h | 2 - libraries/avatars/src/AvatarData.cpp | 67 ++++++++++++++++++++++------ libraries/avatars/src/AvatarData.h | 1 + 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 9ae636af36..820a0491d1 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -543,10 +543,6 @@ void Avatar::simulateAttachments(float deltaTime) { } } -void Avatar::updateJointMappings() { - // no-op; joint mappings come from skeleton model -} - float Avatar::getBoundingRadius() const { return getBounds().getLargestDimension() / 2.0f; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 2580ac1d37..288fc9d781 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -235,8 +235,6 @@ protected: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); - virtual void updateJointMappings() override; - virtual void updatePalms(); render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 251d83c19b..4b7e5cfb3c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -602,8 +602,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); } // 1 byte + // joint rotations int numJoints = *sourceBuffer++; + + + // do not process any jointData until we've received a valid jointIndices hash from + // an earlier AvatarIdentity packet. Because if we do, we risk applying the joint data + // the wrong bones, resulting in a twisted avatar, An un-animated avatar is preferable to this. + bool skipJoints = false; + if (_networkJointIndexMap.empty()) { + skipJoints = true; + } + int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; if (minPossibleSize > maxAvailableSize) { @@ -654,9 +665,13 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; if (validRotations[i]) { - _hasNewJointRotations = true; - data.rotationSet = true; - sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + if (skipJoints) { + sourceBuffer += COMPRESSED_QUATERNION_SIZE; + } else { + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + _hasNewJointRotations = true; + data.rotationSet = true; + } } } } // numJoints * 6 bytes @@ -684,7 +699,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } // 1 + bytesOfValidity bytes // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix - minPossibleSize += numValidJointTranslations * 6 + 1; + const size_t COMPRESSED_TRANSLATION_SIZE = 6; + minPossibleSize += numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE + 1; if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" @@ -701,10 +717,13 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; if (validTranslations[i]) { - sourceBuffer += - unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); - _hasNewJointTranslations = true; - data.translationSet = true; + if (skipJoints) { + sourceBuffer += COMPRESSED_TRANSLATION_SIZE; + } else { + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); + _hasNewJointTranslations = true; + data.translationSet = true; + } } } } // numJoints * 12 bytes @@ -966,14 +985,25 @@ bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { QDataStream packetStream(data); QUuid avatarUUID; - QUrl unusedModelURL; // legacy faceModel support QUrl skeletonModelURL; QVector attachmentData; QString displayName; - packetStream >> avatarUUID >> unusedModelURL >> skeletonModelURL >> attachmentData >> displayName; - + QHash networkJointIndices; + packetStream >> avatarUUID >> skeletonModelURL >> attachmentData >> displayName >> networkJointIndices; bool hasIdentityChanged = false; + if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !networkJointIndices.empty()) { + // build networkJointIndexMap from _jointIndices and networkJointIndices. + _networkJointIndexMap.fill(networkJointIndices.size(), -1); + for (auto iter = networkJointIndices.cbegin(); iter != networkJointIndices.end(); ++iter) { + int jointIndex = getJointIndex(iter.key()); + _networkJointIndexMap[iter.value()] = jointIndex; + } + } + + // AJT: just got a new networkJointIndicesMap. + qCDebug(avatars) << "AJT: receiving networkJointIndices.size = " << networkJointIndices.size(); + if (_firstSkeletonCheck || (skeletonModelURL != _skeletonModelURL)) { setSkeletonModelURL(skeletonModelURL); hasIdentityChanged = true; @@ -999,9 +1029,10 @@ QByteArray AvatarData::identityByteArray() { QUrl emptyURL(""); const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; - QUrl unusedModelURL; // legacy faceModel support + // AJT: just got a sending networkJointIndices + qCDebug(avatars) << "AJT: sending _jointIndices.size = " << _jointIndices.size(); - identityStream << QUuid() << unusedModelURL << urlToSend << _attachmentData << _displayName; + identityStream << QUuid() << urlToSend << _attachmentData << _displayName << _jointIndices; return identityData; } @@ -1106,6 +1137,8 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { void AvatarData::setJointMappingsFromNetworkReply() { QNetworkReply* networkReply = static_cast(sender()); + qCDebug(avatars) << "AJT: GOT HERE! finished fst network request"; + QByteArray line; while (!(line = networkReply->readLine()).isEmpty()) { line = line.trimmed(); @@ -1140,6 +1173,11 @@ void AvatarData::setJointMappingsFromNetworkReply() { _jointIndices.insert(_jointNames.at(i), i + 1); } + // now that we have the jointIndices send them to the AvatarMixer. + sendIdentityPacket(); + + qCDebug(avatars) << "AJT: _jointIndices.size = " << _jointIndices.size(); + networkReply->deleteLater(); } @@ -1180,6 +1218,9 @@ void AvatarData::sendIdentityPacket() { void AvatarData::updateJointMappings() { _jointIndices.clear(); _jointNames.clear(); + _networkJointIndexMap.clear(); + + qCDebug(avatars) << "AJT: GOT HERE! kicking off fst network request"; if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a7b97ef4c0..43bc682bda 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -366,6 +366,7 @@ protected: float _displayNameAlpha; QHash _jointIndices; ///< 1-based, since zero is returned for missing keys + QVector _networkJointIndexMap; // maps network joint indices to local model joint indices. QStringList _jointNames; ///< in order of depth-first traversal quint64 _errorLogExpiry; ///< time in future when to log an error From fa90d823d5b403072c1ccd6271c8edef1b3123cf Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 18 May 2016 11:49:12 -0700 Subject: [PATCH 128/264] Turn off hand-controller pointer when Interface doesn't have focus. --- scripts/system/controllers/handControllerPointer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 6e9fe17077..921c55b7b2 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -405,6 +405,9 @@ function update() { if (!Menu.isOptionChecked("First Person")) { return turnOffVisualization(); } // What to do? menus can be behind hand! + if (!Window.hasFocus()) { // Don't mess with other apps + return turnOffVisualization(); + } var controllerPose = Controller.getPoseValue(activeHand); // Vive is effectively invalid when not in HMD if (!controllerPose.valid || ((hardware === 'Vive') && !HMD.active)) { From 89f18aee3b54661075204d238e2da5136a4e4c30 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 18 May 2016 13:14:42 -0700 Subject: [PATCH 129/264] Simplifying the deferredbuffer clear and enableing z test for lighting pass --- .../src/DeferredLightingEffect.cpp | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 1d9ce65581..fd7826070d 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -149,17 +149,12 @@ void DeferredLightingEffect::prepare(RenderArgs* args) { batch.setFramebuffer(deferredFbo); - // Clear Color, Depth and Stencil + // Clear Color, Depth and Stencil for deferred buffer batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | + gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_DEPTH | gpu::Framebuffer::BUFFER_STENCIL, - vec4(vec3(0), 1), 1.0, 0.0, true); - - // clear the normal and specular buffers - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR1, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f), true); - const float MAX_SPECULAR_EXPONENT = 128.0f; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR2, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f / MAX_SPECULAR_EXPONENT), true); + vec4(vec3(0), 0), 1.0, 0.0, true); }); } @@ -469,9 +464,15 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo batch.setInputBuffer(0, mesh->getVertexBuffer()); batch.setInputFormat(mesh->getVertexFormat()); - auto& part = mesh->getPartBuffer().get(); - - batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); + { + auto& part = mesh->getPartBuffer().get(0); + batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); + } + // keep for debug + /* { + auto& part = mesh->getPartBuffer().get(1); + batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); + }*/ } } } @@ -548,14 +549,13 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo // Stencil test all the light passes for objects pixels only, not the background state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); - + if (lightVolume) { state->setCullMode(gpu::State::CULL_BACK); - - // No need for z test since the depth buffer is not bound state->setDepthTest(true, false, gpu::LESS_EQUAL); - // TODO: We should bind the true depth buffer both as RT and texture for the depth test + state->setDepthTest(true, false, gpu::LESS_EQUAL); + // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases - state->setDepthClampEnable(true); + //state->setDepthClampEnable(true); // additive blending state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -662,10 +662,14 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { _spotLightMesh->setIndexBuffer(gpu::BufferView(new gpu::Buffer(sizeof(unsigned short) * indices, (gpu::Byte*) indexData), gpu::Element::INDEX_UINT16)); delete[] indexData; - model::Mesh::Part part(0, indices, 0, model::Mesh::TRIANGLES); - //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); - _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); + std::vector parts; + parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::TRIANGLES)); + //DEBUG: + parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::LINE_STRIP)); + + + _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); } return _spotLightMesh; } From 551c6698835683a8dab27040904c9c1f711327fc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 19 May 2016 09:24:15 +1200 Subject: [PATCH 130/264] Move FolderListModel to be stand-alone --- .../resources/qml/dialogs/FileDialog.qml | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index e8b6b9b2b7..4f6e7ef050 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -43,7 +43,7 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property alias caption: root.title; // Set from OffscreenUi::getOpenFile() - property alias dir: model.folder; + property alias dir: folderListModel.folder; // Set from OffscreenUi::getOpenFile() property alias filter: selectionType.filtersString; // Set from OffscreenUi::getOpenFile() @@ -110,7 +110,7 @@ ModalWindow { glyph: hifi.glyphs.levelUp width: height size: 30 - enabled: model.parentFolder && model.parentFolder !== "" + enabled: folderListModel.parentFolder && folderListModel.parentFolder !== "" onClicked: d.navigateUp(); } @@ -135,7 +135,7 @@ ModalWindow { TextField { id: currentDirectory - property var lastValidFolder: helper.urlToPath(model.folder) + property var lastValidFolder: helper.urlToPath(folderListModel.folder) height: homeButton.height anchors { top: parent.top @@ -161,7 +161,7 @@ ModalWindow { text = lastValidFolder; return } - model.folder = helper.pathToUrl(text); + folderListModel.folder = helper.pathToUrl(text); } } @@ -172,7 +172,7 @@ ModalWindow { property bool currentSelectionIsFolder; property var backStack: [] property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } - property var modelConnection: Connections { target: model; onFolderChanged: d.update(); } + property var modelConnection: Connections { target: model; onFolderChanged: d.update(); } // DJRTODO property var homeDestination: helper.home(); Component.onCompleted: update(); @@ -194,18 +194,38 @@ ModalWindow { } function navigateUp() { - if (model.parentFolder && model.parentFolder !== "") { - model.folder = model.parentFolder + if (folderListModel.parentFolder && folderListModel.parentFolder !== "") { + folderListModel.folder = folderListModel.parentFolder return true; } } function navigateHome() { - model.folder = homeDestination; + folderListModel.folder = homeDestination; return true; } } + FolderListModel { + id: folderListModel + nameFilters: selectionType.currentFilter + showDirsFirst: true + showDotAndDotDot: false + showFiles: !root.selectDirectory + // For some reason, declaring these bindings directly in the targets doesn't + // work for setting the initial state + Component.onCompleted: { + currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(folder); }); + upButton.enabled = Qt.binding(function() { return (parentFolder && parentFolder != "") ? true : false; }); + showFiles = !root.selectDirectory + } + onFolderChanged: { + fileTableView.selection.clear(); + fileTableView.selection.select(0); + fileTableView.currentRow = 0; + } + } + Table { id: fileTableView colorScheme: hifi.colorSchemes.light @@ -227,25 +247,7 @@ ModalWindow { sortIndicatorOrder: Qt.AscendingOrder sortIndicatorVisible: true - model: FolderListModel { - id: model - nameFilters: selectionType.currentFilter - showDirsFirst: true - showDotAndDotDot: false - showFiles: !root.selectDirectory - // For some reason, declaring these bindings directly in the targets doesn't - // work for setting the initial state - Component.onCompleted: { - currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(model.folder); }); - upButton.enabled = Qt.binding(function() { return (model.parentFolder && model.parentFolder != "") ? true : false; }); - showFiles = !root.selectDirectory - } - onFolderChanged: { - fileTableView.selection.clear(); - fileTableView.selection.select(0); - fileTableView.currentRow = 0; - } - } + model: folderListModel function updateSort() { model.sortReversed = sortIndicatorColumn == 0 @@ -343,7 +345,7 @@ ModalWindow { var isFolder = model.isFolder(row); var file = model.get(row, "fileURL"); if (isFolder) { - fileTableView.model.folder = file + fileTableView.model.folder = file; } else { okAction.trigger(); } From 3cc08cdcfce195bb0becd33ad9555060cd290846 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 16 May 2016 11:44:18 -0700 Subject: [PATCH 131/264] Breaking up GL agnostic code from 4.1 specific code --- cmake/macros/TargetNsight.cmake | 15 +- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 411 +----- libraries/gpu-gl/src/gpu/gl/GLBackend.h | 611 +++------ .../gpu-gl/src/gpu/gl/GLBackendBuffer.cpp | 124 -- .../gpu-gl/src/gpu/gl/GLBackendInput.cpp | 175 +-- .../gpu-gl/src/gpu/gl/GLBackendOutput.cpp | 208 +-- .../gpu-gl/src/gpu/gl/GLBackendPipeline.cpp | 67 +- .../gpu-gl/src/gpu/gl/GLBackendQuery.cpp | 50 +- libraries/gpu-gl/src/gpu/gl/GLBackendShared.h | 68 - .../gpu-gl/src/gpu/gl/GLBackendState.cpp | 563 +-------- .../gpu-gl/src/gpu/gl/GLBackendTexture.cpp | 604 +-------- .../gpu-gl/src/gpu/gl/GLBackendTransform.cpp | 74 -- libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp | 28 + libraries/gpu-gl/src/gpu/gl/GLBuffer.h | 57 + libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp | 39 + libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h | 76 ++ libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp | 48 + libraries/gpu-gl/src/gpu/gl/GLPipeline.h | 26 + libraries/gpu-gl/src/gpu/gl/GLQuery.cpp | 12 + libraries/gpu-gl/src/gpu/gl/GLQuery.h | 57 + libraries/gpu-gl/src/gpu/gl/GLShader.cpp | 182 +++ libraries/gpu-gl/src/gpu/gl/GLShader.h | 52 + .../gl/{GLBackendShader.cpp => GLShared.cpp} | 1124 ++++++++++------- libraries/gpu-gl/src/gpu/gl/GLShared.h | 158 +++ libraries/gpu-gl/src/gpu/gl/GLState.cpp | 233 ++++ libraries/gpu-gl/src/gpu/gl/GLState.h | 73 ++ ...{GLBackendShared.cpp => GLTexelFormat.cpp} | 70 +- libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h | 32 + libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 292 +++++ libraries/gpu-gl/src/gpu/gl/GLTexture.h | 192 +++ ...tureTransfer.cpp => GLTextureTransfer.cpp} | 19 +- ...dTextureTransfer.h => GLTextureTransfer.h} | 15 +- libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp | 175 +++ libraries/gpu-gl/src/gpu/gl41/GLBackend.h | 96 ++ .../gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp | 62 + .../gpu-gl/src/gpu/gl41/GLBackendInput.cpp | 185 +++ .../gpu-gl/src/gpu/gl41/GLBackendOutput.cpp | 169 +++ .../gpu-gl/src/gpu/gl41/GLBackendQuery.cpp | 37 + .../gpu-gl/src/gpu/gl41/GLBackendTexture.cpp | 237 ++++ .../src/gpu/gl41/GLBackendTransform.cpp | 83 ++ libraries/gpu/CMakeLists.txt | 2 + libraries/gpu/src/gpu/Batch.cpp | 106 +- libraries/gpu/src/gpu/Forward.h | 7 + libraries/gpu/src/gpu/Resource.h | 31 +- 44 files changed, 3685 insertions(+), 3260 deletions(-) delete mode 100644 libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp delete mode 100644 libraries/gpu-gl/src/gpu/gl/GLBackendShared.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLBuffer.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLPipeline.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLQuery.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLQuery.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLShader.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLShader.h rename libraries/gpu-gl/src/gpu/gl/{GLBackendShader.cpp => GLShared.cpp} (57%) create mode 100644 libraries/gpu-gl/src/gpu/gl/GLShared.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLState.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLState.h rename libraries/gpu-gl/src/gpu/gl/{GLBackendShared.cpp => GLTexelFormat.cpp} (82%) create mode 100644 libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h create mode 100644 libraries/gpu-gl/src/gpu/gl/GLTexture.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl/GLTexture.h rename libraries/gpu-gl/src/gpu/gl/{GLBackendTextureTransfer.cpp => GLTextureTransfer.cpp} (84%) rename libraries/gpu-gl/src/gpu/gl/{GLBackendTextureTransfer.h => GLTextureTransfer.h} (78%) create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackend.h create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp create mode 100644 libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp diff --git a/cmake/macros/TargetNsight.cmake b/cmake/macros/TargetNsight.cmake index 09b056d07a..44ca4eecbf 100644 --- a/cmake/macros/TargetNsight.cmake +++ b/cmake/macros/TargetNsight.cmake @@ -7,18 +7,21 @@ # macro(TARGET_NSIGHT) if (WIN32 AND USE_NSIGHT) - + # grab the global CHECKED_FOR_NSIGHT_ONCE property - get_property(NSIGHT_CHECKED GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) + get_property(NSIGHT_UNAVAILABLE GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) - if (NOT NSIGHT_CHECKED) + if (NOT NSIGHT_UNAVAILABLE) # try to find the Nsight package and add it to the build if we find it find_package(NSIGHT) - # set the global CHECKED_FOR_NSIGHT_ONCE property so that we only debug that we couldn't find it once - set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + # Cache the failure to find nsight, so that we don't check over and over + if (NOT NSIGHT_FOUND) + set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + endif() endif () - + + # try to find the Nsight package and add it to the build if we find it if (NSIGHT_FOUND) include_directories(${NSIGHT_INCLUDE_DIRS}) add_definitions(-DNSIGHT_FOUND) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 69699e673b..e5460c6641 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -16,17 +16,51 @@ #include #include +#include "../gl41/GLBackend.h" + #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" #endif #include -#include -#include "GLBackendShared.h" +#include +#include +#include "GLTexture.h" +#include "GLShader.h" using namespace gpu; using namespace gpu::gl; + +static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); +static bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + +Backend* GLBackend::createBackend() { + auto version = QOpenGLContextWrapper::currentContextVersion(); + + // FIXME provide a mechanism to override the backend for testing + // Where the gpuContext is initialized and where the TRUE Backend is created and assigned +#if 0 + GLBackend* result; + if (enableOpenGL45 && version >= 0x0405) { + result = new gpu::gl45::GLBackend; + } else { + result = new gpu::gl41::GLBackend; + } +#else + GLBackend* result = new gpu::gl41::GLBackend; +#endif + result->initInput(); + result->initTransform(); + gl::GLTexture::initTextureTransferHelper(); + return result; +} + + +bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { + return GLShader::makeProgram(shader, slotBindings); +} + GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = { (&::gpu::gl::GLBackend::do_draw), @@ -95,16 +129,13 @@ void GLBackend::init() { std::call_once(once, [] { TEXTURE_ID_RESOLVER = [](const Texture& texture)->uint32 { - auto object = Backend::getGPUObject(texture); + auto object = Backend::getGPUObject(texture); if (!object) { return 0; } - if (object->getSyncState() != GLTexture::Idle) { - if (object->_downsampleSource) { - return object->_downsampleSource->_texture; - } - return 0; + if (object->getSyncState() != GLSyncState::Idle) { + return object->_downsampleSource._texture; } return object->_texture; }; @@ -148,61 +179,11 @@ void GLBackend::init() { }); } -Context::Size GLBackend::getDedicatedMemory() { - static Context::Size dedicatedMemory { 0 }; - static std::once_flag once; - std::call_once(once, [&] { -#ifdef Q_OS_WIN - if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) { - UINT maxCount = wglGetGPUIDsAMD(0, 0); - std::vector ids; - ids.resize(maxCount); - wglGetGPUIDsAMD(maxCount, &ids[0]); - GLuint memTotal; - wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal); - dedicatedMemory = MB_TO_BYTES(memTotal); - } -#endif - - if (!dedicatedMemory) { - GLint atiGpuMemory[4]; - // not really total memory, but close enough if called early enough in the application lifecycle - glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); - } - } - - if (!dedicatedMemory) { - GLint nvGpuMemory { 0 }; - glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); - if (GL_NO_ERROR == glGetError()) { - dedicatedMemory = KB_TO_BYTES(nvGpuMemory); - } - } - - if (!dedicatedMemory) { - auto gpuIdent = GPUIdent::getInstance(); - if (gpuIdent && gpuIdent->isValid()) { - dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory()); - } - } - }); - - return dedicatedMemory; -} - -Backend* GLBackend::createBackend() { - return new GLBackend(); -} - GLBackend::GLBackend() { glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment); - initInput(); - initTransform(); - initTextureTransferHelper(); } + GLBackend::~GLBackend() { resetStages(); @@ -260,7 +241,7 @@ void GLBackend::renderPassTransfer(Batch& batch) { { // Sync the transform buffers PROFILE_RANGE("syncGPUTransform"); - _transform.transfer(batch); + transferTransformState(batch); } _inRenderTransferPass = false; @@ -355,164 +336,6 @@ void GLBackend::setupStereoSide(int side) { _transform.bindCurrentCamera(side); } - -void GLBackend::do_draw(Batch& batch, size_t paramOffset) { - Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numVertices = batch._params[paramOffset + 1]._uint; - uint32 startVertex = batch._params[paramOffset + 0]._uint; - - if (isStereo()) { - setupStereoSide(0); - glDrawArrays(mode, startVertex, numVertices); - setupStereoSide(1); - glDrawArrays(mode, startVertex, numVertices); - - _stats._DSNumTriangles += 2 * numVertices / 3; - _stats._DSNumDrawcalls += 2; - - } else { - glDrawArrays(mode, startVertex, numVertices); - _stats._DSNumTriangles += numVertices / 3; - _stats._DSNumDrawcalls++; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { - Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numIndices = batch._params[paramOffset + 1]._uint; - uint32 startIndex = batch._params[paramOffset + 0]._uint; - - GLenum glType = _elementTypeToGLType[_input._indexBufferType]; - - auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; - GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); - - if (isStereo()) { - setupStereoSide(0); - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - setupStereoSide(1); - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - - _stats._DSNumTriangles += 2 * numIndices / 3; - _stats._DSNumDrawcalls += 2; - } else { - glDrawElements(mode, numIndices, glType, indexBufferByteOffset); - _stats._DSNumTriangles += numIndices / 3; - _stats._DSNumDrawcalls++; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { - GLint numInstances = batch._params[paramOffset + 4]._uint; - Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; - GLenum mode = _primitiveToGLmode[primitiveType]; - uint32 numVertices = batch._params[paramOffset + 2]._uint; - uint32 startVertex = batch._params[paramOffset + 1]._uint; - - - if (isStereo()) { - GLint trueNumInstances = 2 * numInstances; - - setupStereoSide(0); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - setupStereoSide(1); - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - - _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; - _stats._DSNumDrawcalls += trueNumInstances; - } else { - glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); - _stats._DSNumTriangles += (numInstances * numVertices) / 3; - _stats._DSNumDrawcalls += numInstances; - } - _stats._DSNumAPIDrawcalls++; - - (void) CHECK_GL_ERROR(); -} - -void glbackend_glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - glDrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, primcount, basevertex, baseinstance); -#else - glDrawElementsInstanced(mode, count, type, indices, primcount); -#endif -} - -void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { - GLint numInstances = batch._params[paramOffset + 4]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 3]._uint]; - uint32 numIndices = batch._params[paramOffset + 2]._uint; - uint32 startIndex = batch._params[paramOffset + 1]._uint; - // FIXME glDrawElementsInstancedBaseVertexBaseInstance is only available in GL 4.3 - // and higher, so currently we ignore this field - uint32 startInstance = batch._params[paramOffset + 0]._uint; - GLenum glType = _elementTypeToGLType[_input._indexBufferType]; - - auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; - GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); - - if (isStereo()) { - GLint trueNumInstances = 2 * numInstances; - - setupStereoSide(0); - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - setupStereoSide(1); - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - - _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; - _stats._DSNumDrawcalls += trueNumInstances; - } else { - glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); - _stats._DSNumTriangles += (numInstances * numIndices) / 3; - _stats._DSNumDrawcalls += numInstances; - } - - _stats._DSNumAPIDrawcalls++; - - (void)CHECK_GL_ERROR(); -} - - -void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; - - glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); - _stats._DSNumDrawcalls += commandCount; - _stats._DSNumAPIDrawcalls++; - -#else - // FIXME implement the slow path -#endif - (void)CHECK_GL_ERROR(); - -} - -void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { -#if (GPU_INPUT_PROFILE == GPU_CORE_43) - uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; - GLenum indexType = _elementTypeToGLType[_input._indexBufferType]; - - glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); - _stats._DSNumDrawcalls += commandCount; - _stats._DSNumAPIDrawcalls++; -#else - // FIXME implement the slow path -#endif - (void)CHECK_GL_ERROR(); -} - - void GLBackend::do_resetStages(Batch& batch, size_t paramOffset) { resetStages(); } @@ -543,41 +366,34 @@ void GLBackend::resetStages() { (void) CHECK_GL_ERROR(); } + +void GLBackend::do_pushProfileRange(Batch& batch, size_t paramOffset) { +#if defined(NSIGHT_FOUND) + auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); + nvtxRangePush(name.c_str()); +#endif +} + +void GLBackend::do_popProfileRange(Batch& batch, size_t paramOffset) { +#if defined(NSIGHT_FOUND) + nvtxRangePop(); +#endif +} + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API -#define ADD_COMMAND_GL(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); - // 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()); #define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc - -void Batch::_glActiveBindTexture(GLenum unit, GLenum target, GLuint texture) { - // clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine - setResourceTexture(unit - GL_TEXTURE0, nullptr); - - ADD_COMMAND_GL(glActiveBindTexture); - _params.push_back(texture); - _params.push_back(target); - _params.push_back(unit); -} void GLBackend::do_glActiveBindTexture(Batch& batch, size_t paramOffset) { glActiveTexture(batch._params[paramOffset + 2]._uint); glBindTexture( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._uint), batch._params[paramOffset + 0]._uint); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform1i(GLint location, GLint v0) { - if (location < 0) { - return; - } - ADD_COMMAND_GL(glUniform1i); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) { @@ -591,17 +407,9 @@ void GLBackend::do_glUniform1i(Batch& batch, size_t paramOffset) { glUniform1f( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._int); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniform1f(GLint location, GLfloat v0) { - if (location < 0) { - return; - } - ADD_COMMAND_GL(glUniform1f); - _params.push_back(v0); - _params.push_back(location); -} void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -613,15 +421,7 @@ void GLBackend::do_glUniform1f(Batch& batch, size_t paramOffset) { glUniform1f( GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int), batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform2f(GLint location, GLfloat v0, GLfloat v1) { - ADD_COMMAND_GL(glUniform2f); - - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) { @@ -635,16 +435,7 @@ void GLBackend::do_glUniform2f(Batch& batch, size_t paramOffset) { GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int), batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); -} - -void Batch::_glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) { - ADD_COMMAND_GL(glUniform3f); - - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); + (void)CHECK_GL_ERROR(); } void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) { @@ -659,21 +450,9 @@ void GLBackend::do_glUniform3f(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, batch._params[paramOffset + 0]._float); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } - -void Batch::_glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - ADD_COMMAND_GL(glUniform4f); - - _params.push_back(v3); - _params.push_back(v2); - _params.push_back(v1); - _params.push_back(v0); - _params.push_back(location); -} - - void GLBackend::do_glUniform4f(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -690,14 +469,6 @@ void GLBackend::do_glUniform4f(Batch& batch, size_t paramOffset) { (void)CHECK_GL_ERROR(); } -void Batch::_glUniform3fv(GLint location, GLsizei count, const GLfloat* value) { - ADD_COMMAND_GL(glUniform3fv); - - const int VEC3_SIZE = 3 * sizeof(float); - _params.push_back(cacheData(count * VEC3_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -710,18 +481,9 @@ void GLBackend::do_glUniform3fv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } - -void Batch::_glUniform4fv(GLint location, GLsizei count, const GLfloat* value) { - ADD_COMMAND_GL(glUniform4fv); - - const int VEC4_SIZE = 4 * sizeof(float); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -729,23 +491,15 @@ void GLBackend::do_glUniform4fv(Batch& batch, size_t paramOffset) { return; } updatePipeline(); - + GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int); GLsizei count = batch._params[paramOffset + 1]._uint; const GLfloat* value = (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint); glUniform4fv(location, count, value); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniform4iv(GLint location, GLsizei count, const GLint* value) { - ADD_COMMAND_GL(glUniform4iv); - - const int VEC4_SIZE = 4 * sizeof(int); - _params.push_back(cacheData(count * VEC4_SIZE, value)); - _params.push_back(count); - _params.push_back(location); -} void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -758,18 +512,9 @@ void GLBackend::do_glUniform4iv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 1]._uint, (const GLint*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value) { - ADD_COMMAND_GL(glUniformMatrix4fv); - - const int MATRIX4_SIZE = 16 * sizeof(float); - _params.push_back(cacheData(count * MATRIX4_SIZE, value)); - _params.push_back(transpose); - _params.push_back(count); - _params.push_back(location); -} void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) { if (_pipeline._program == 0) { // We should call updatePipeline() to bind the program but we are not doing that @@ -783,42 +528,20 @@ void GLBackend::do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) { batch._params[paramOffset + 2]._uint, batch._params[paramOffset + 1]._uint, (const GLfloat*)batch.editData(batch._params[paramOffset + 0]._uint)); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } -void Batch::_glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { - ADD_COMMAND_GL(glColor4f); - - _params.push_back(alpha); - _params.push_back(blue); - _params.push_back(green); - _params.push_back(red); -} void GLBackend::do_glColor4f(Batch& batch, size_t paramOffset) { glm::vec4 newColor( batch._params[paramOffset + 3]._float, batch._params[paramOffset + 2]._float, batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 0]._float); + batch._params[paramOffset + 0]._float); if (_input._colorAttribute != newColor) { _input._colorAttribute = newColor; glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r); } - (void) CHECK_GL_ERROR(); -} - - -void GLBackend::do_pushProfileRange(Batch& batch, size_t paramOffset) { -#if defined(NSIGHT_FOUND) - auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); - nvtxRangePush(name.c_str()); -#endif -} - -void GLBackend::do_popProfileRange(Batch& batch, size_t paramOffset) { -#if defined(NSIGHT_FOUND) - nvtxRangePop(); -#endif + (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.h b/libraries/gpu-gl/src/gpu/gl/GLBackend.h index 16bb1cc394..610672f44f 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.h @@ -8,8 +8,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_gpu_GLBackend_h -#define hifi_gpu_GLBackend_h +#ifndef hifi_gpu_gl_GLBackend_h +#define hifi_gpu_gl_GLBackend_h #include #include @@ -26,325 +26,34 @@ #include #include - -#define GPU_CORE 1 -#define GPU_LEGACY 0 -#define GPU_CORE_41 410 -#define GPU_CORE_43 430 -#define GPU_CORE_MINIMUM GPU_CORE_41 -#define GPU_FEATURE_PROFILE GPU_CORE - -#if defined(__APPLE__) - -#define GPU_INPUT_PROFILE GPU_CORE_41 - -#else - -#define GPU_INPUT_PROFILE GPU_CORE_43 - -#endif - - -#if (GPU_INPUT_PROFILE == GPU_CORE_43) -// Deactivate SSBO for now, we've run into some issues -// on GL 4.3 capable GPUs not behaving as expected -//#define GPU_SSBO_DRAW_CALL_INFO -#endif +#include "GLShared.h" namespace gpu { namespace gl { -class GLTextureTransferHelper; - class GLBackend : public Backend { - // Context Backend static interface required friend class gpu::Context; static void init(); static Backend* createBackend(); - static bool makeProgram(Shader& shader, const Shader::BindingSet& bindings); + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings); +protected: explicit GLBackend(bool syncCache); GLBackend(); public: - static Context::Size getDedicatedMemory(); + ~GLBackend(); - virtual ~GLBackend(); - - virtual void render(Batch& batch); + void render(Batch& batch) final; // This call synchronize the Full Backend cache with the current GLState // THis is only intended to be used when mixing raw gl calls with the gpu api usage in order to sync // the gpu::Backend state with the true gl state which has probably been messed up by these ugly naked gl calls // Let's try to avoid to do that as much as possible! - virtual void syncCache(); + void syncCache() final; // This is the ugly "download the pixels to sysmem for taking a snapshot" // Just avoid using it, it's ugly and will break performances - virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage); - - static void checkGLStackStable(std::function f); - - - - class GLBuffer : public GPUObject { - public: - const GLuint _buffer; - const GLuint _size; - const Stamp _stamp; - - GLBuffer(const Buffer& buffer, GLBuffer* original = nullptr); - ~GLBuffer(); - - virtual void transfer(); - - private: - bool getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const; - - // The owning texture - const Buffer& _gpuBuffer; - }; - - static GLBuffer* syncGPUObject(const Buffer& buffer); - static GLuint getBufferID(const Buffer& buffer); - - class GLTexture : public GPUObject { - public: - // The public gl texture object - GLuint _texture{ 0 }; - - const Stamp _storageStamp; - Stamp _contentStamp { 0 }; - const GLenum _target; - const uint16 _maxMip; - const uint16 _minMip; - const bool _transferrable; - - struct DownsampleSource { - using Pointer = std::shared_ptr; - DownsampleSource(GLTexture& oldTexture); - ~DownsampleSource(); - const GLuint _texture; - const uint16 _minMip; - const uint16 _maxMip; - }; - - DownsampleSource::Pointer _downsampleSource; - - GLTexture(bool transferrable, const gpu::Texture& gpuTexture); - GLTexture(GLTexture& originalTexture, const gpu::Texture& gpuTexture); - ~GLTexture(); - - // Return a floating point value indicating how much of the allowed - // texture memory we are currently consuming. A value of 0 indicates - // no texture memory usage, while a value of 1 indicates all available / allowed memory - // is consumed. A value above 1 indicates that there is a problem. - static float getMemoryPressure(); - - void withPreservedTexture(std::function f); - - void createTexture(); - void allocateStorage(); - - GLuint size() const { return _size; } - GLuint virtualSize() const { return _virtualSize; } - - void updateSize(); - - enum SyncState { - // The texture is currently undergoing no processing, although it's content - // may be out of date, or it's storage may be invalid relative to the - // owning GPU texture - Idle, - // The texture has been queued for transfer to the GPU - Pending, - // The texture has been transferred to the GPU, but is awaiting - // any post transfer operations that may need to occur on the - // primary rendering thread - Transferred, - }; - - void setSyncState(SyncState syncState) { _syncState = syncState; } - SyncState getSyncState() const { return _syncState; } - - // Is the storage out of date relative to the gpu texture? - bool isInvalid() const; - - // Is the content out of date relative to the gpu texture? - bool isOutdated() const; - - // Is the texture in a state where it can be rendered with no work? - bool isReady() const; - - // Is this texture pushing us over the memory limit? - bool isOverMaxMemory() const; - - // Move the image bits from the CPU to the GPU - void transfer() const; - - // Execute any post-move operations that must occur only on the main thread - void postTransfer(); - - uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } - - static const size_t CUBE_NUM_FACES = 6; - static const GLenum CUBE_FACE_LAYOUT[6]; - - private: - friend class GLTextureTransferHelper; - - GLTexture(bool transferrable, const gpu::Texture& gpuTexture, bool init); - // at creation the true texture is created in GL - // it becomes public only when ready. - GLuint _privateTexture{ 0 }; - - const std::vector& getFaceTargets() const; - - void setSize(GLuint size); - - const GLuint _virtualSize; // theorical size as expected - GLuint _size { 0 }; // true size as reported by the gl api - - void transferMip(uint16_t mipLevel, uint8_t face = 0) const; - - // The owning texture - const Texture& _gpuTexture; - std::atomic _syncState { SyncState::Idle }; - }; - static GLTexture* syncGPUObject(const TexturePointer& texture, bool needTransfer = true); - static GLuint getTextureID(const TexturePointer& texture, bool sync = true); - - // very specific for now - static void syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object); - - class GLShader : public GPUObject { - public: - enum Version { - Mono = 0, - - NumVersions - }; - - struct ShaderObject { - GLuint glshader{ 0 }; - GLuint glprogram{ 0 }; - GLint transformCameraSlot{ -1 }; - GLint transformObjectSlot{ -1 }; - }; - - using ShaderObjects = std::array< ShaderObject, NumVersions >; - - using UniformMapping = std::map; - using UniformMappingVersions = std::vector; - - GLShader(); - ~GLShader(); - - ShaderObjects _shaderObjects; - UniformMappingVersions _uniformMappings; - - GLuint getProgram(Version version = Mono) const { - return _shaderObjects[version].glprogram; - } - - 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; - } - - }; - static GLShader* syncGPUObject(const Shader& shader); - - class GLState : public GPUObject { - public: - class Command { - public: - virtual void run(GLBackend* backend) = 0; - Command() {} - virtual ~Command() {}; - }; - - template class Command1 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T); - void run(GLBackend* backend) { (backend->*(_func))(_param); } - Command1(GLFunction func, T param) : _func(func), _param(param) {}; - GLFunction _func; - T _param; - }; - template class Command2 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1); } - Command2(GLFunction func, T param0, U param1) : _func(func), _param0(param0), _param1(param1) {}; - GLFunction _func; - T _param0; - U _param1; - }; - - template class Command3 : public Command { - public: - typedef void (GLBackend::*GLFunction)(T, U, V); - void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1, _param2); } - Command3(GLFunction func, T param0, U param1, V param2) : _func(func), _param0(param0), _param1(param1), _param2(param2) {}; - GLFunction _func; - T _param0; - U _param1; - V _param2; - }; - - typedef std::shared_ptr< Command > CommandPointer; - typedef std::vector< CommandPointer > Commands; - - Commands _commands; - Stamp _stamp; - State::Signature _signature; - - GLState(); - ~GLState(); - - // The state commands to reset to default, - static const Commands _resetStateCommands; - - friend class GLBackend; - }; - static GLState* syncGPUObject(const State& state); - - class GLPipeline : public GPUObject { - public: - GLShader* _program = 0; - GLState* _state = 0; - - GLPipeline(); - ~GLPipeline(); - }; - static GLPipeline* syncGPUObject(const Pipeline& pipeline); - - - class GLFramebuffer : public GPUObject { - public: - GLuint _fbo = 0; - std::vector _colorBuffers; - Stamp _depthStamp { 0 }; - std::vector _colorStamps; - - - GLFramebuffer(); - ~GLFramebuffer(); - }; - static GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer); - static GLuint getFramebufferID(const FramebufferPointer& framebuffer); - - class GLQuery : public GPUObject { - public: - GLuint _qo = 0; - GLuint64 _result = 0; - - GLQuery(); - ~GLQuery(); - }; - static GLQuery* syncGPUObject(const Query& query); - static GLuint getQueryID(const QueryPointer& query); + virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) final; static const int MAX_NUM_ATTRIBUTES = Stream::NUM_INPUT_SLOTS; @@ -362,70 +71,131 @@ public: static const int MAX_NUM_RESOURCE_TEXTURES = 16; size_t getMaxNumResourceTextures() const { return MAX_NUM_RESOURCE_TEXTURES; } + // Draw Stage + virtual void do_draw(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexed(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawInstanced(Batch& batch, size_t paramOffset) = 0; + virtual void do_drawIndexedInstanced(Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndirect(Batch& batch, size_t paramOffset) = 0; + virtual void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) = 0; + + // Input Stage + virtual void do_setInputFormat(Batch& batch, size_t paramOffset) final; + virtual void do_setInputBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_setIndexBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_setIndirectBuffer(Batch& batch, size_t paramOffset) final; + virtual void do_generateTextureMips(Batch& batch, size_t paramOffset) final; + + // Transform Stage + virtual void do_setModelTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setViewTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setViewportTransform(Batch& batch, size_t paramOffset) final; + virtual void do_setDepthRangeTransform(Batch& batch, size_t paramOffset) final; + + // Uniform Stage + virtual void do_setUniformBuffer(Batch& batch, size_t paramOffset) final; + + // Resource Stage + virtual void do_setResourceTexture(Batch& batch, size_t paramOffset) final; + + // Pipeline Stage + virtual void do_setPipeline(Batch& batch, size_t paramOffset) final; + + // Output stage + virtual void do_setFramebuffer(Batch& batch, size_t paramOffset) final; + virtual void do_clearFramebuffer(Batch& batch, size_t paramOffset) final; + virtual void do_blit(Batch& batch, size_t paramOffset) = 0; + + // Query section + virtual void do_beginQuery(Batch& batch, size_t paramOffset) final; + virtual void do_endQuery(Batch& batch, size_t paramOffset) final; + virtual void do_getQuery(Batch& batch, size_t paramOffset) final; + + // Reset stages + virtual void do_resetStages(Batch& batch, size_t paramOffset) final; + + virtual void do_runLambda(Batch& batch, size_t paramOffset) final; + + virtual void do_startNamedCall(Batch& batch, size_t paramOffset) final; + virtual void do_stopNamedCall(Batch& batch, size_t paramOffset) final; + + virtual void do_pushProfileRange(Batch& batch, size_t paramOffset) final; + virtual void do_popProfileRange(Batch& batch, size_t paramOffset) final; + + // TODO: As long as we have gl calls explicitely issued from interface + // code, we need to be able to record and batch these calls. THe long + // term strategy is to get rid of any GL calls in favor of the HIFI GPU API + virtual void do_glActiveBindTexture(Batch& batch, size_t paramOffset) final; + + virtual void do_glUniform1i(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform1f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform2f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4f(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform3fv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4fv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniform4iv(Batch& batch, size_t paramOffset) final; + virtual void do_glUniformMatrix4fv(Batch& batch, size_t paramOffset) final; + + virtual void do_glColor4f(Batch& batch, size_t paramOffset) final; + // The State setters called by the GLState::Commands when a new state is assigned - void do_setStateFillMode(int32 mode); - void do_setStateCullMode(int32 mode); - void do_setStateFrontFaceClockwise(bool isClockwise); - void do_setStateDepthClampEnable(bool enable); - void do_setStateScissorEnable(bool enable); - void do_setStateMultisampleEnable(bool enable); - void do_setStateAntialiasedLineEnable(bool enable); + virtual void do_setStateFillMode(int32 mode) final; + virtual void do_setStateCullMode(int32 mode) final; + virtual void do_setStateFrontFaceClockwise(bool isClockwise) final; + virtual void do_setStateDepthClampEnable(bool enable) final; + virtual void do_setStateScissorEnable(bool enable) final; + virtual void do_setStateMultisampleEnable(bool enable) final; + virtual void do_setStateAntialiasedLineEnable(bool enable) final; + virtual void do_setStateDepthBias(Vec2 bias) final; + virtual void do_setStateDepthTest(State::DepthTest test) final; + virtual void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) final; + virtual void do_setStateAlphaToCoverageEnable(bool enable) final; + virtual void do_setStateSampleMask(uint32 mask) final; + virtual void do_setStateBlend(State::BlendFunction blendFunction) final; + virtual void do_setStateColorWriteMask(uint32 mask) final; + virtual void do_setStateBlendFactor(Batch& batch, size_t paramOffset) final; + virtual void do_setStateScissorRect(Batch& batch, size_t paramOffset) final; - void do_setStateDepthBias(Vec2 bias); - void do_setStateDepthTest(State::DepthTest test); - - void do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest); - - void do_setStateAlphaToCoverageEnable(bool enable); - void do_setStateSampleMask(uint32 mask); - - void do_setStateBlend(State::BlendFunction blendFunction); - - void do_setStateColorWriteMask(uint32 mask); - protected: - static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass; + virtual GLuint getFramebufferID(const FramebufferPointer& framebuffer) = 0; + virtual GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) = 0; + + virtual GLuint getBufferID(const Buffer& buffer) = 0; + virtual GLBuffer* syncGPUObject(const Buffer& buffer) = 0; + + virtual GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) = 0; + virtual GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) = 0; + + virtual GLuint getQueryID(const QueryPointer& query) = 0; + virtual GLQuery* syncGPUObject(const Query& query) = 0; + + static const size_t INVALID_OFFSET = (size_t)-1; + bool _inRenderTransferPass { false }; + int32_t _uboAlignment { 0 }; + int _currentDraw { -1 }; void renderPassTransfer(Batch& batch); void renderPassDraw(Batch& batch); - void setupStereoSide(int side); - void initTextureTransferHelper(); - static void transferGPUObject(const TexturePointer& texture); + virtual void initInput() final; + virtual void killInput() final; + virtual void syncInputStateCache() final; + virtual void resetInputStage() final; + virtual void updateInput() = 0; - static std::shared_ptr _textureTransferHelper; - - // Draw Stage - void do_draw(Batch& batch, size_t paramOffset); - void do_drawIndexed(Batch& batch, size_t paramOffset); - void do_drawInstanced(Batch& batch, size_t paramOffset); - void do_drawIndexedInstanced(Batch& batch, size_t paramOffset); - void do_multiDrawIndirect(Batch& batch, size_t paramOffset); - void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset); - - // Input Stage - void do_setInputFormat(Batch& batch, size_t paramOffset); - void do_setInputBuffer(Batch& batch, size_t paramOffset); - void do_setIndexBuffer(Batch& batch, size_t paramOffset); - void do_setIndirectBuffer(Batch& batch, size_t paramOffset); - - void initInput(); - void killInput(); - void syncInputStateCache(); - void updateInput(); - void resetInputStage(); struct InputStageState { - bool _invalidFormat = true; + bool _invalidFormat { true }; Stream::FormatPointer _format; typedef std::bitset ActivationCache; - ActivationCache _attributeActivation; + ActivationCache _attributeActivation { 0 }; typedef std::bitset BuffersState; - BuffersState _invalidBuffers; + BuffersState _invalidBuffers { 0 }; Buffers _buffers; Offsets _bufferOffsets; @@ -435,39 +205,23 @@ protected: glm::vec4 _colorAttribute{ 0.0f }; BufferPointer _indexBuffer; - Offset _indexBufferOffset; - Type _indexBufferType; + Offset _indexBufferOffset { 0 }; + Type _indexBufferType { UINT32 }; BufferPointer _indirectBuffer; Offset _indirectBufferOffset{ 0 }; Offset _indirectBufferStride{ 0 }; - GLuint _defaultVAO; + GLuint _defaultVAO { 0 }; InputStageState() : - _invalidFormat(true), - _format(0), - _attributeActivation(0), - _invalidBuffers(0), - _buffers(_invalidBuffers.size(), BufferPointer(0)), + _buffers(_invalidBuffers.size()), _bufferOffsets(_invalidBuffers.size(), 0), _bufferStrides(_invalidBuffers.size(), 0), - _bufferVBOs(_invalidBuffers.size(), 0), - _indexBuffer(0), - _indexBufferOffset(0), - _indexBufferType(UINT32), - _defaultVAO(0) - {} + _bufferVBOs(_invalidBuffers.size(), 0) {} } _input; - // Transform Stage - void do_setModelTransform(Batch& batch, size_t paramOffset); - void do_setViewTransform(Batch& batch, size_t paramOffset); - void do_setProjectionTransform(Batch& batch, size_t paramOffset); - void do_setViewportTransform(Batch& batch, size_t paramOffset); - void do_setDepthRangeTransform(Batch& batch, size_t paramOffset); - - void initTransform(); + virtual void initTransform() = 0; void killTransform(); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncTransformStateCache(); @@ -505,153 +259,74 @@ protected: void preUpdate(size_t commandIndex, const StereoState& stereo); void update(size_t commandIndex, const StereoState& stereo) const; void bindCurrentCamera(int stereoSide) const; - void transfer(const Batch& batch) const; } _transform; - int32_t _uboAlignment{ 0 }; + virtual void transferTransformState(const Batch& batch) const = 0; - - // Uniform Stage - void do_setUniformBuffer(Batch& batch, size_t paramOffset); + struct UniformStageState { + std::array _buffers; + //Buffers _buffers { }; + } _uniform; void releaseUniformBuffer(uint32_t slot); void resetUniformStage(); - struct UniformStageState { - Buffers _buffers; - - UniformStageState(): - _buffers(MAX_NUM_UNIFORM_BUFFERS, nullptr) - {} - } _uniform; - // Resource Stage - void do_setResourceTexture(Batch& batch, size_t paramOffset); - // update resource cache and do the gl unbind call with the current gpu::Texture cached at slot s void releaseResourceTexture(uint32_t slot); void resetResourceStage(); + struct ResourceStageState { - Textures _textures; - + std::array _textures; + //Textures _textures { { MAX_NUM_RESOURCE_TEXTURES } }; int findEmptyTextureSlot() const; - - ResourceStageState(): - _textures(MAX_NUM_RESOURCE_TEXTURES, nullptr) - {} - } _resource; + size_t _commandIndex{ 0 }; - // Pipeline Stage - void do_setPipeline(Batch& batch, size_t paramOffset); - void do_setStateBlendFactor(Batch& batch, size_t paramOffset); - void do_setStateScissorRect(Batch& batch, size_t paramOffset); - // Standard update pipeline check that the current Program and current State or good to go for a void updatePipeline(); // Force to reset all the state fields indicated by the 'toBeReset" signature void resetPipelineState(State::Signature toBeReset); // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncPipelineStateCache(); - // Grab the actual gl state into it's gpu::State equivalent. THis is used by the above call syncPipleineStateCache() - void getCurrentGLState(State::Data& state); void resetPipelineStage(); struct PipelineStageState { - PipelinePointer _pipeline; - GLuint _program; - GLShader* _programShader; - bool _invalidProgram; + GLuint _program { 0 }; + GLShader* _programShader { nullptr }; + bool _invalidProgram { false }; - State::Data _stateCache; - State::Signature _stateSignatureCache; + State::Data _stateCache { State::DEFAULT }; + State::Signature _stateSignatureCache { 0 }; - GLState* _state; - bool _invalidState = false; - - PipelineStageState() : - _pipeline(), - _program(0), - _programShader(nullptr), - _invalidProgram(false), - _stateCache(State::DEFAULT), - _stateSignatureCache(0), - _state(nullptr), - _invalidState(false) - {} + GLState* _state { nullptr }; + bool _invalidState { false }; } _pipeline; - // Output stage - void do_setFramebuffer(Batch& batch, size_t paramOffset); - void do_clearFramebuffer(Batch& batch, size_t paramOffset); - void do_blit(Batch& batch, size_t paramOffset); - void do_generateTextureMips(Batch& batch, size_t paramOffset); - // Synchronize the state cache of this Backend with the actual real state of the GL Context void syncOutputStateCache(); void resetOutputStage(); struct OutputStageState { - - FramebufferPointer _framebuffer = nullptr; - GLuint _drawFBO = 0; - - OutputStageState() {} + FramebufferPointer _framebuffer { nullptr }; + GLuint _drawFBO { 0 }; } _output; - // Query section - void do_beginQuery(Batch& batch, size_t paramOffset); - void do_endQuery(Batch& batch, size_t paramOffset); - void do_getQuery(Batch& batch, size_t paramOffset); - void resetQueryStage(); struct QueryStageState { }; - // Reset stages - void do_resetStages(Batch& batch, size_t paramOffset); - - void do_runLambda(Batch& batch, size_t paramOffset); - - void do_startNamedCall(Batch& batch, size_t paramOffset); - void do_stopNamedCall(Batch& batch, size_t paramOffset); - void resetStages(); - // TODO: As long as we have gl calls explicitely issued from interface - // code, we need to be able to record and batch these calls. THe long - // term strategy is to get rid of any GL calls in favor of the HIFI GPU API - void do_glActiveBindTexture(Batch& batch, size_t paramOffset); - - void do_glUniform1i(Batch& batch, size_t paramOffset); - void do_glUniform1f(Batch& batch, size_t paramOffset); - void do_glUniform2f(Batch& batch, size_t paramOffset); - void do_glUniform3f(Batch& batch, size_t paramOffset); - void do_glUniform4f(Batch& batch, size_t paramOffset); - void do_glUniform3fv(Batch& batch, size_t paramOffset); - void do_glUniform4fv(Batch& batch, size_t paramOffset); - void do_glUniform4iv(Batch& batch, size_t paramOffset); - void do_glUniformMatrix4fv(Batch& batch, size_t paramOffset); - - void do_glColor4f(Batch& batch, size_t paramOffset); - - void do_pushProfileRange(Batch& batch, size_t paramOffset); - void do_popProfileRange(Batch& batch, size_t paramOffset); - - int _currentDraw { -1 }; - typedef void (GLBackend::*CommandCall)(Batch&, size_t); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; - + friend class GLState; }; } } -Q_DECLARE_LOGGING_CATEGORY(gpugllogging) - - #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp deleted file mode 100644 index 0057b3d702..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendBuffer.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// -// GLBackendBuffer.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 3/8/2015. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "GLBackend.h" -#include "GLBackendShared.h" - -using namespace gpu; -using namespace gpu::gl; - -GLuint allocateSingleBuffer() { - GLuint result; - glGenBuffers(1, &result); - (void)CHECK_GL_ERROR(); - return result; -} - -GLBackend::GLBuffer::GLBuffer(const Buffer& buffer, GLBuffer* original) : - _buffer(allocateSingleBuffer()), - _size((GLuint)buffer._sysmem.getSize()), - _stamp(buffer._sysmem.getStamp()), - _gpuBuffer(buffer) { - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - if (GLEW_VERSION_4_4 || GLEW_ARB_buffer_storage) { - glBufferStorage(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_STORAGE_BIT); - (void)CHECK_GL_ERROR(); - } else { - glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); - (void)CHECK_GL_ERROR(); - } - glBindBuffer(GL_ARRAY_BUFFER, 0); - - if (original) { - glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer); - glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - (void)CHECK_GL_ERROR(); - } - - Backend::setGPUObject(buffer, this); - Backend::incrementBufferGPUCount(); - Backend::updateBufferGPUMemoryUsage(0, _size); -} - -GLBackend::GLBuffer::~GLBuffer() { - if (_buffer != 0) { - glDeleteBuffers(1, &_buffer); - } - Backend::updateBufferGPUMemoryUsage(_size, 0); - Backend::decrementBufferGPUCount(); -} - -void GLBackend::GLBuffer::transfer() { - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - (void)CHECK_GL_ERROR(); - GLintptr offset; - GLsizeiptr size; - size_t currentPage { 0 }; - auto data = _gpuBuffer.getSysmem().readData(); - while (getNextTransferBlock(offset, size, currentPage)) { - glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset); - (void)CHECK_GL_ERROR(); - } - glBindBuffer(GL_ARRAY_BUFFER, 0); - (void)CHECK_GL_ERROR(); - _gpuBuffer._flags &= ~Buffer::DIRTY; -} - -bool GLBackend::GLBuffer::getNextTransferBlock(GLintptr& outOffset, GLsizeiptr& outSize, size_t& currentPage) const { - size_t pageCount = _gpuBuffer._pages.size(); - // Advance to the first dirty page - while (currentPage < pageCount && (0 == (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) { - ++currentPage; - } - - // If we got to the end, we're done - if (currentPage >= pageCount) { - return false; - } - - // Advance to the next clean page - outOffset = static_cast(currentPage * _gpuBuffer._pageSize); - while (currentPage < pageCount && (0 != (Buffer::DIRTY & _gpuBuffer._pages[currentPage]))) { - _gpuBuffer._pages[currentPage] &= ~Buffer::DIRTY; - ++currentPage; - } - outSize = static_cast((currentPage * _gpuBuffer._pageSize) - outOffset); - return true; -} - -GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { - GLBuffer* object = Backend::getGPUObject(buffer); - - // Has the storage size changed? - if (!object || object->_stamp != buffer.getSysmem().getStamp()) { - object = new GLBuffer(buffer, object); - } - - if (0 != (buffer._flags & Buffer::DIRTY)) { - object->transfer(); - } - - return object; -} - - - -GLuint GLBackend::getBufferID(const Buffer& buffer) { - GLBuffer* bo = GLBackend::syncGPUObject(buffer); - if (bo) { - return bo->_buffer; - } else { - return 0; - } -} - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp index 7849da42a0..448cc508eb 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendInput.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" using namespace gpu; using namespace gpu::gl; @@ -59,9 +59,6 @@ void GLBackend::do_setInputBuffer(Batch& batch, size_t paramOffset) { } } - - - void GLBackend::initInput() { if(!_input._defaultVAO) { glGenVertexArrays(1, &_input._defaultVAO); @@ -88,176 +85,6 @@ void GLBackend::syncInputStateCache() { glBindVertexArray(_input._defaultVAO); } -// Core 41 doesn't expose the features to really separate the vertex format from the vertex buffers binding -// Core 43 does :) -// FIXME crashing problem with glVertexBindingDivisor / glVertexAttribFormat -#if 1 || (GPU_INPUT_PROFILE == GPU_CORE_41) -#define NO_SUPPORT_VERTEX_ATTRIB_FORMAT -#else -#define SUPPORT_VERTEX_ATTRIB_FORMAT -#endif - -void GLBackend::updateInput() { -#if defined(SUPPORT_VERTEX_ATTRIB_FORMAT) - if (_input._invalidFormat) { - - InputStageState::ActivationCache newActivation; - - // Assign the vertex format required - if (_input._format) { - for (auto& it : _input._format->getAttributes()) { - const Stream::Attribute& attrib = (it).second; - - GLuint slot = attrib._slot; - GLuint count = attrib._element.getLocationScalarCount(); - uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = _elementTypeToGLType[attrib._element.getType()]; - GLuint offset = attrib._offset;; - GLboolean isNormalized = attrib._element.isNormalized(); - - GLenum perLocationSize = attrib._element.getLocationSize(); - - for (size_t locNum = 0; locNum < locationCount; ++locNum) { - newActivation.set(slot + locNum); - glVertexAttribFormat(slot + locNum, count, type, isNormalized, offset + locNum * perLocationSize); - glVertexAttribBinding(slot + locNum, attrib._channel); - } - glVertexBindingDivisor(attrib._channel, attrib._frequency); - } - (void) CHECK_GL_ERROR(); - } - - // Manage Activation what was and what is expected now - for (size_t i = 0; i < newActivation.size(); i++) { - bool newState = newActivation[i]; - if (newState != _input._attributeActivation[i]) { - if (newState) { - glEnableVertexAttribArray(i); - } else { - glDisableVertexAttribArray(i); - } - _input._attributeActivation.flip(i); - } - } - (void) CHECK_GL_ERROR(); - - _input._invalidFormat = false; - _stats._ISNumFormatChanges++; - } - - if (_input._invalidBuffers.any()) { - int numBuffers = _input._buffers.size(); - auto buffer = _input._buffers.data(); - auto vbo = _input._bufferVBOs.data(); - auto offset = _input._bufferOffsets.data(); - auto stride = _input._bufferStrides.data(); - - for (int bufferNum = 0; bufferNum < numBuffers; bufferNum++) { - if (_input._invalidBuffers.test(bufferNum)) { - glBindVertexBuffer(bufferNum, (*vbo), (*offset), (*stride)); - } - buffer++; - vbo++; - offset++; - stride++; - } - _input._invalidBuffers.reset(); - (void) CHECK_GL_ERROR(); - } -#else - if (_input._invalidFormat || _input._invalidBuffers.any()) { - - if (_input._invalidFormat) { - InputStageState::ActivationCache newActivation; - - _stats._ISNumFormatChanges++; - - // Check expected activation - if (_input._format) { - for (auto& it : _input._format->getAttributes()) { - const Stream::Attribute& attrib = (it).second; - uint8_t locationCount = attrib._element.getLocationCount(); - for (int i = 0; i < locationCount; ++i) { - newActivation.set(attrib._slot + i); - } - } - } - - // Manage Activation what was and what is expected now - for (unsigned int i = 0; i < newActivation.size(); i++) { - bool newState = newActivation[i]; - if (newState != _input._attributeActivation[i]) { - - if (newState) { - glEnableVertexAttribArray(i); - } else { - glDisableVertexAttribArray(i); - } - (void) CHECK_GL_ERROR(); - - _input._attributeActivation.flip(i); - } - } - } - - // now we need to bind the buffers and assign the attrib pointers - if (_input._format) { - const Buffers& buffers = _input._buffers; - const Offsets& offsets = _input._bufferOffsets; - const Offsets& strides = _input._bufferStrides; - - const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); - auto& inputChannels = _input._format->getChannels(); - _stats._ISNumInputBufferChanges++; - - GLuint boundVBO = 0; - for (auto& channelIt : inputChannels) { - const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; - if ((channelIt).first < buffers.size()) { - int bufferNum = (channelIt).first; - - if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { - // GLuint vbo = gpu::GLBackend::getBufferID((*buffers[bufferNum])); - GLuint vbo = _input._bufferVBOs[bufferNum]; - if (boundVBO != vbo) { - glBindBuffer(GL_ARRAY_BUFFER, vbo); - (void) CHECK_GL_ERROR(); - boundVBO = vbo; - } - _input._invalidBuffers[bufferNum] = false; - - for (unsigned int i = 0; i < channel._slots.size(); i++) { - const Stream::Attribute& attrib = attributes.at(channel._slots[i]); - GLuint slot = attrib._slot; - GLuint count = attrib._element.getLocationScalarCount(); - uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = _elementTypeToGLType[attrib._element.getType()]; - // GLenum perLocationStride = strides[bufferNum]; - GLenum perLocationStride = attrib._element.getLocationSize(); - GLuint stride = (GLuint)strides[bufferNum]; - GLuint pointer = (GLuint)(attrib._offset + offsets[bufferNum]); - GLboolean isNormalized = attrib._element.isNormalized(); - - for (size_t locNum = 0; locNum < locationCount; ++locNum) { - glVertexAttribPointer(slot + (GLuint)locNum, count, type, isNormalized, stride, - reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); - glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); - } - - // TODO: Support properly the IAttrib version - - (void) CHECK_GL_ERROR(); - } - } - } - } - } - // everything format related should be in sync now - _input._invalidFormat = false; - } -#endif -} - void GLBackend::resetInputStage() { // Reset index buffer _input._indexBufferType = UINT32; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index f0a29a19ba..1d46078b5b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -9,180 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" +#include "GLShared.h" +#include "GLFramebuffer.h" #include -#include "GLBackendShared.h" - using namespace gpu; using namespace gpu::gl; -GLBackend::GLFramebuffer::GLFramebuffer() {} - -GLBackend::GLFramebuffer::~GLFramebuffer() { - if (_fbo != 0) { - glDeleteFramebuffers(1, &_fbo); - } -} - -GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { - GLFramebuffer* object = Backend::getGPUObject(framebuffer); - - bool needsUpate { false }; - if (!object || - framebuffer.getDepthStamp() != object->_depthStamp || - framebuffer.getColorStamps() != object->_colorStamps) { - needsUpate = true; - } - - // If GPU object already created and in sync - if (!needsUpate) { - return object; - } else if (framebuffer.isEmpty()) { - // NO framebuffer definition yet so let's avoid thinking - return nullptr; - } - - // need to have a gpu object? - if (!object) { - // All is green, assign the gpuobject to the Framebuffer - object = new GLFramebuffer(); - Backend::setGPUObject(framebuffer, object); - glGenFramebuffers(1, &object->_fbo); - (void)CHECK_GL_ERROR(); - } - - if (needsUpate) { - GLint currentFBO = -1; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); - glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo); - - GLTexture* gltexture = nullptr; - TexturePointer surface; - if (framebuffer.getColorStamps() != object->_colorStamps) { - if (framebuffer.hasColor()) { - object->_colorBuffers.clear(); - static const GLenum colorAttachments[] = { - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, - GL_COLOR_ATTACHMENT8, - GL_COLOR_ATTACHMENT9, - GL_COLOR_ATTACHMENT10, - GL_COLOR_ATTACHMENT11, - GL_COLOR_ATTACHMENT12, - GL_COLOR_ATTACHMENT13, - GL_COLOR_ATTACHMENT14, - GL_COLOR_ATTACHMENT15 }; - - int unit = 0; - for (auto& b : framebuffer.getRenderBuffers()) { - surface = b._texture; - if (surface) { - gltexture = GLBackend::syncGPUObject(surface, false); // Grab the gltexture and don't transfer - } else { - gltexture = nullptr; - } - - if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); - object->_colorBuffers.push_back(colorAttachments[unit]); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); - } - unit++; - } - } - object->_colorStamps = framebuffer.getColorStamps(); - } - - GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; - if (!framebuffer.hasStencil()) { - attachement = GL_DEPTH_ATTACHMENT; - } else if (!framebuffer.hasDepth()) { - attachement = GL_STENCIL_ATTACHMENT; - } - - if (framebuffer.getDepthStamp() != object->_depthStamp) { - auto surface = framebuffer.getDepthStencilBuffer(); - if (framebuffer.hasDepthStencil() && surface) { - gltexture = GLBackend::syncGPUObject(surface, false); // Grab the gltexture and don't transfer - } - - if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); - } - object->_depthStamp = framebuffer.getDepthStamp(); - } - - - // Last but not least, define where we draw - if (!object->_colorBuffers.empty()) { - glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data()); - } else { - glDrawBuffer( GL_NONE ); - } - - // Now check for completness - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - // restore the current framebuffer - if (currentFBO != -1) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); - } - - bool result = false; - switch (status) { - case GL_FRAMEBUFFER_COMPLETE : - // Success ! - result = true; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; - break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; - break; - case GL_FRAMEBUFFER_UNSUPPORTED : - qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; - break; - } - if (!result && object->_fbo) { - glDeleteFramebuffers(1, &object->_fbo); - return nullptr; - } - } - - return object; -} - - - -GLuint GLBackend::getFramebufferID(const FramebufferPointer& framebuffer) { - if (!framebuffer) { - return 0; - } - GLFramebuffer* object = GLBackend::syncGPUObject(*framebuffer); - if (object) { - return object->_fbo; - } else { - return 0; - } -} - void GLBackend::syncOutputStateCache() { GLint currentFBO; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); @@ -303,44 +137,6 @@ void GLBackend::do_clearFramebuffer(Batch& batch, size_t paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_blit(Batch& batch, size_t paramOffset) { - auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); - Vec4i srcvp; - for (auto i = 0; i < 4; ++i) { - srcvp[i] = batch._params[paramOffset + 1 + i]._int; - } - - auto dstframebuffer = batch._framebuffers.get(batch._params[paramOffset + 5]._uint); - Vec4i dstvp; - for (auto i = 0; i < 4; ++i) { - dstvp[i] = batch._params[paramOffset + 6 + i]._int; - } - - // Assign dest framebuffer if not bound already - auto newDrawFBO = getFramebufferID(dstframebuffer); - if (_output._drawFBO != newDrawFBO) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newDrawFBO); - } - - // always bind the read fbo - glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcframebuffer)); - - // Blit! - glBlitFramebuffer(srcvp.x, srcvp.y, srcvp.z, srcvp.w, - dstvp.x, dstvp.y, dstvp.z, dstvp.w, - GL_COLOR_BUFFER_BIT, GL_LINEAR); - - // Always clean the read fbo to 0 - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - // Restore draw fbo if changed - if (_output._drawFBO != newDrawFBO) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _output._drawFBO); - } - - (void) CHECK_GL_ERROR(); -} - void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) { auto readFBO = getFramebufferID(srcFramebuffer); if (srcFramebuffer && readFBO) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp index b66b672850..04f56ba0f5 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendPipeline.cpp @@ -9,54 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" +#include "GLPipeline.h" +#include "GLShader.h" +#include "GLState.h" +#include "GLBuffer.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLPipeline::GLPipeline() : - _program(nullptr), - _state(nullptr) -{} - -GLBackend::GLPipeline::~GLPipeline() { - _program = nullptr; - _state = nullptr; -} - -GLBackend::GLPipeline* GLBackend::syncGPUObject(const Pipeline& pipeline) { - GLPipeline* object = Backend::getGPUObject(pipeline); - - // If GPU object already created then good - if (object) { - return object; - } - - // No object allocated yet, let's see if it's worth it... - ShaderPointer shader = pipeline.getProgram(); - GLShader* programObject = GLBackend::syncGPUObject((*shader)); - if (programObject == nullptr) { - return nullptr; - } - - StatePointer state = pipeline.getState(); - GLState* stateObject = GLBackend::syncGPUObject((*state)); - if (stateObject == nullptr) { - return nullptr; - } - - // Program and state are valid, we can create the pipeline object - if (!object) { - object = new GLPipeline(); - Backend::setGPUObject(pipeline, object); - } - - object->_program = programObject; - object->_state = stateObject; - - return object; -} - void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) { PipelinePointer pipeline = batch._pipelines.get(batch._params[paramOffset + 0]._uint); @@ -78,7 +40,7 @@ void GLBackend::do_setPipeline(Batch& batch, size_t paramOffset) { _pipeline._state = nullptr; _pipeline._invalidState = true; } else { - auto pipelineObject = syncGPUObject((*pipeline)); + auto pipelineObject = GLPipeline::sync(*pipeline); if (!pipelineObject) { return; } @@ -155,14 +117,12 @@ void GLBackend::resetPipelineStage() { glUseProgram(0); } - void GLBackend::releaseUniformBuffer(uint32_t slot) { auto& buf = _uniform._buffers[slot]; if (buf) { - auto* object = Backend::getGPUObject(*buf); + auto* object = Backend::getGPUObject(*buf); if (object) { glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void) CHECK_GL_ERROR(); } buf.reset(); @@ -181,9 +141,6 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { GLintptr rangeStart = batch._params[paramOffset + 1]._uint; GLsizeiptr rangeSize = batch._params[paramOffset + 0]._uint; - - - if (!uniformBuffer) { releaseUniformBuffer(slot); return; @@ -195,7 +152,7 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { } // Sync BufferObject - auto* object = GLBackend::syncGPUObject(*uniformBuffer); + auto* object = syncGPUObject(*uniformBuffer); if (object) { glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, rangeStart, rangeSize); @@ -210,19 +167,17 @@ void GLBackend::do_setUniformBuffer(Batch& batch, size_t paramOffset) { void GLBackend::releaseResourceTexture(uint32_t slot) { auto& tex = _resource._textures[slot]; if (tex) { - auto* object = Backend::getGPUObject(*tex); + auto* object = Backend::getGPUObject(*tex); if (object) { GLuint target = object->_target; glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(target, 0); // RELEASE - (void) CHECK_GL_ERROR(); } tex.reset(); } } - void GLBackend::resetResourceStage() { for (uint32_t i = 0; i < _resource._textures.size(); i++) { releaseResourceTexture(i); @@ -246,7 +201,7 @@ void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { _stats._RSNumTextureBounded++; // Always make sure the GLObject is in sync - GLTexture* object = GLBackend::syncGPUObject(resourceTexture); + GLTexture* object = syncGPUObject(resourceTexture); if (object) { GLuint to = object->_texture; GLuint target = object->_target; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp index 4f5b2ff1bf..344f380339 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendQuery.cpp @@ -9,58 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLQuery.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLQuery::GLQuery() {} - -GLBackend::GLQuery::~GLQuery() { - if (_qo != 0) { - glDeleteQueries(1, &_qo); - } -} - -GLBackend::GLQuery* GLBackend::syncGPUObject(const Query& query) { - GLQuery* object = Backend::getGPUObject(query); - - // If GPU object already created and in sync - if (object) { - return object; - } - - // need to have a gpu object? - if (!object) { - GLuint qo; - glGenQueries(1, &qo); - (void) CHECK_GL_ERROR(); - GLuint64 result = -1; - - // All is green, assign the gpuobject to the Query - object = new GLQuery(); - object->_qo = qo; - object->_result = result; - Backend::setGPUObject(query, object); - } - - return object; -} - - - -GLuint GLBackend::getQueryID(const QueryPointer& query) { - if (!query) { - return 0; - } - GLQuery* object = GLBackend::syncGPUObject(*query); - if (object) { - return object->_qo; - } else { - return 0; - } -} - void GLBackend::do_beginQuery(Batch& batch, size_t paramOffset) { auto query = batch._queries.get(batch._params[paramOffset]._uint); GLQuery* glquery = syncGPUObject(*query); @@ -94,4 +47,3 @@ void GLBackend::do_getQuery(Batch& batch, size_t paramOffset) { void GLBackend::resetQueryStage() { } - diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h b/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h deleted file mode 100644 index 0f7215f364..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// GLBackendShared.h -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 1/22/2014. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#ifndef hifi_gpu_GLBackend_Shared_h -#define hifi_gpu_GLBackend_Shared_h - -#include -#include -#include - -namespace gpu { namespace gl { - -static const GLenum _primitiveToGLmode[gpu::NUM_PRIMITIVES] = { - GL_POINTS, - GL_LINES, - GL_LINE_STRIP, - GL_TRIANGLES, - GL_TRIANGLE_STRIP, - GL_TRIANGLE_FAN, -}; - -static const GLenum _elementTypeToGLType[gpu::NUM_TYPES] = { - GL_FLOAT, - GL_INT, - GL_UNSIGNED_INT, - GL_HALF_FLOAT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE, - // Normalized values - GL_INT, - GL_UNSIGNED_INT, - GL_SHORT, - GL_UNSIGNED_SHORT, - GL_BYTE, - GL_UNSIGNED_BYTE -}; - -class GLTexelFormat { -public: - GLenum internalFormat; - GLenum format; - GLenum type; - - static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat) { - return evalGLTexelFormat(dstFormat, dstFormat); - } - static GLTexelFormat evalGLTexelFormatInternal(const gpu::Element& dstFormat); - - static GLTexelFormat evalGLTexelFormat(const gpu::Element& dstFormat, const gpu::Element& srcFormat); -}; - -bool checkGLError(const char* name = nullptr); -bool checkGLErrorDebug(const char* name = nullptr); - -} } - -#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__) - -#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp index 1c1e0b38a4..a42b0dca6f 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendState.cpp @@ -9,238 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLState.h" using namespace gpu; using namespace gpu::gl; -GLBackend::GLState::GLState() -{} - -GLBackend::GLState::~GLState() { -} - - -typedef GLBackend::GLState::Command Command; -typedef GLBackend::GLState::CommandPointer CommandPointer; -typedef GLBackend::GLState::Command1 Command1U; -typedef GLBackend::GLState::Command1 Command1I; -typedef GLBackend::GLState::Command1 Command1B; -typedef GLBackend::GLState::Command1 CommandDepthBias; -typedef GLBackend::GLState::Command1 CommandDepthTest; -typedef GLBackend::GLState::Command3 CommandStencil; -typedef GLBackend::GLState::Command1 CommandBlend; - -const GLBackend::GLState::Commands makeResetStateCommands(); -const GLBackend::GLState::Commands GLBackend::GLState::_resetStateCommands = makeResetStateCommands(); - - -// NOTE: This must stay in sync with the ordering of the State::Field enum -const GLBackend::GLState::Commands makeResetStateCommands() { - // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random - // and we have a 50/50 chance that State::DEFAULT is not yet initialized. - // Since State::DEFAULT = State::Data() it is much easier to not use the actual State::DEFAULT - // but another State::Data object with a default initialization. - const State::Data DEFAULT = State::Data(); - - auto depthBiasCommand = std::make_shared(&GLBackend::do_setStateDepthBias, - Vec2(DEFAULT.depthBias, DEFAULT.depthBiasSlopeScale)); - auto stencilCommand = std::make_shared(&GLBackend::do_setStateStencil, DEFAULT.stencilActivation, - DEFAULT.stencilTestFront, DEFAULT.stencilTestBack); - - // The state commands to reset to default, - // WARNING depending on the order of the State::Field enum - return { - std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), - std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), - std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), - std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), - std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), - std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), - std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), - - // Depth bias has 2 fields in State but really one call in GLBackend - CommandPointer(depthBiasCommand), - CommandPointer(depthBiasCommand), - - std::make_shared(&GLBackend::do_setStateDepthTest, DEFAULT.depthTest), - - // Depth bias has 3 fields in State but really one call in GLBackend - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - CommandPointer(stencilCommand), - - std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), - - std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), - - std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), - - std::make_shared(&GLBackend::do_setStateColorWriteMask, DEFAULT.colorWriteMask) - }; -} - -void generateFillMode(GLBackend::GLState::Commands& commands, State::FillMode fillMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFillMode, int32(fillMode))); -} - -void generateCullMode(GLBackend::GLState::Commands& commands, State::CullMode cullMode) { - commands.push_back(std::make_shared(&GLBackend::do_setStateCullMode, int32(cullMode))); -} - -void generateFrontFaceClockwise(GLBackend::GLState::Commands& commands, bool isClockwise) { - commands.push_back(std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, isClockwise)); -} - -void generateDepthClampEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthClampEnable, enable)); -} - -void generateScissorEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateScissorEnable, enable)); -} - -void generateMultisampleEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateMultisampleEnable, enable)); -} - -void generateAntialiasedLineEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, enable)); -} - -void generateDepthBias(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthBias, Vec2(state.getDepthBias(), state.getDepthBiasSlopeScale()))); -} - -void generateDepthTest(GLBackend::GLState::Commands& commands, const State::DepthTest& test) { - commands.push_back(std::make_shared(&GLBackend::do_setStateDepthTest, int32(test.getRaw()))); -} - -void generateStencil(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateStencil, state.getStencilActivation(), state.getStencilTestFront(), state.getStencilTestBack())); -} - -void generateAlphaToCoverageEnable(GLBackend::GLState::Commands& commands, bool enable) { - commands.push_back(std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, enable)); -} - -void generateSampleMask(GLBackend::GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateSampleMask, mask)); -} - -void generateBlend(GLBackend::GLState::Commands& commands, const State& state) { - commands.push_back(std::make_shared(&GLBackend::do_setStateBlend, state.getBlendFunction())); -} - -void generateColorWriteMask(GLBackend::GLState::Commands& commands, uint32 mask) { - commands.push_back(std::make_shared(&GLBackend::do_setStateColorWriteMask, mask)); -} - -GLBackend::GLState* GLBackend::syncGPUObject(const State& state) { - GLState* object = Backend::getGPUObject(state); - - // If GPU object already created then good - if (object) { - return object; - } - - // Else allocate and create the GLState - if (!object) { - object = new GLState(); - Backend::setGPUObject(state, object); - } - - // here, we need to regenerate something so let's do it all - object->_commands.clear(); - object->_stamp = state.getStamp(); - object->_signature = state.getSignature(); - - bool depthBias = false; - bool stencilState = false; - - // go thorugh the list of state fields in the State and record the corresponding gl command - for (int i = 0; i < State::NUM_FIELDS; i++) { - if (state.getSignature()[i]) { - switch(i) { - case State::FILL_MODE: { - generateFillMode(object->_commands, state.getFillMode()); - break; - } - case State::CULL_MODE: { - generateCullMode(object->_commands, state.getCullMode()); - break; - } - case State::DEPTH_BIAS: - case State::DEPTH_BIAS_SLOPE_SCALE: { - depthBias = true; - break; - } - case State::FRONT_FACE_CLOCKWISE: { - generateFrontFaceClockwise(object->_commands, state.isFrontFaceClockwise()); - break; - } - case State::DEPTH_CLAMP_ENABLE: { - generateDepthClampEnable(object->_commands, state.isDepthClampEnable()); - break; - } - case State::SCISSOR_ENABLE: { - generateScissorEnable(object->_commands, state.isScissorEnable()); - break; - } - case State::MULTISAMPLE_ENABLE: { - generateMultisampleEnable(object->_commands, state.isMultisampleEnable()); - break; - } - case State::ANTIALISED_LINE_ENABLE: { - generateAntialiasedLineEnable(object->_commands, state.isAntialiasedLineEnable()); - break; - } - case State::DEPTH_TEST: { - generateDepthTest(object->_commands, state.getDepthTest()); - break; - } - - case State::STENCIL_ACTIVATION: - case State::STENCIL_TEST_FRONT: - case State::STENCIL_TEST_BACK: { - stencilState = true; - break; - } - - case State::SAMPLE_MASK: { - generateSampleMask(object->_commands, state.getSampleMask()); - break; - } - case State::ALPHA_TO_COVERAGE_ENABLE: { - generateAlphaToCoverageEnable(object->_commands, state.isAlphaToCoverageEnabled()); - break; - } - - case State::BLEND_FUNCTION: { - generateBlend(object->_commands, state); - break; - } - - case State::COLOR_WRITE_MASK: { - generateColorWriteMask(object->_commands, state.getColorWriteMask()); - break; - } - } - } - } - - if (depthBias) { - generateDepthBias(object->_commands, state); - } - - if (stencilState) { - generateStencil(object->_commands, state); - } - - return object; -} - - void GLBackend::resetPipelineState(State::Signature nextSignature) { auto currentNotSignature = ~_pipeline._stateSignatureCache; auto nextNotSignature = ~nextSignature; @@ -255,230 +28,6 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) { } } -ComparisonFunction comparisonFuncFromGL(GLenum func) { - if (func == GL_NEVER) { - return NEVER; - } else if (func == GL_LESS) { - return LESS; - } else if (func == GL_EQUAL) { - return EQUAL; - } else if (func == GL_LEQUAL) { - return LESS_EQUAL; - } else if (func == GL_GREATER) { - return GREATER; - } else if (func == GL_NOTEQUAL) { - return NOT_EQUAL; - } else if (func == GL_GEQUAL) { - return GREATER_EQUAL; - } else if (func == GL_ALWAYS) { - return ALWAYS; - } - - return ALWAYS; -} - -State::StencilOp stencilOpFromGL(GLenum stencilOp) { - if (stencilOp == GL_KEEP) { - return State::STENCIL_OP_KEEP; - } else if (stencilOp == GL_ZERO) { - return State::STENCIL_OP_ZERO; - } else if (stencilOp == GL_REPLACE) { - return State::STENCIL_OP_REPLACE; - } else if (stencilOp == GL_INCR_WRAP) { - return State::STENCIL_OP_INCR_SAT; - } else if (stencilOp == GL_DECR_WRAP) { - return State::STENCIL_OP_DECR_SAT; - } else if (stencilOp == GL_INVERT) { - return State::STENCIL_OP_INVERT; - } else if (stencilOp == GL_INCR) { - return State::STENCIL_OP_INCR; - } else if (stencilOp == GL_DECR) { - return State::STENCIL_OP_DECR; - } - - return State::STENCIL_OP_KEEP; -} - -State::BlendOp blendOpFromGL(GLenum blendOp) { - if (blendOp == GL_FUNC_ADD) { - return State::BLEND_OP_ADD; - } else if (blendOp == GL_FUNC_SUBTRACT) { - return State::BLEND_OP_SUBTRACT; - } else if (blendOp == GL_FUNC_REVERSE_SUBTRACT) { - return State::BLEND_OP_REV_SUBTRACT; - } else if (blendOp == GL_MIN) { - return State::BLEND_OP_MIN; - } else if (blendOp == GL_MAX) { - return State::BLEND_OP_MAX; - } - - return State::BLEND_OP_ADD; -} - -State::BlendArg blendArgFromGL(GLenum blendArg) { - if (blendArg == GL_ZERO) { - return State::ZERO; - } else if (blendArg == GL_ONE) { - return State::ONE; - } else if (blendArg == GL_SRC_COLOR) { - return State::SRC_COLOR; - } else if (blendArg == GL_ONE_MINUS_SRC_COLOR) { - return State::INV_SRC_COLOR; - } else if (blendArg == GL_DST_COLOR) { - return State::DEST_COLOR; - } else if (blendArg == GL_ONE_MINUS_DST_COLOR) { - return State::INV_DEST_COLOR; - } else if (blendArg == GL_SRC_ALPHA) { - return State::SRC_ALPHA; - } else if (blendArg == GL_ONE_MINUS_SRC_ALPHA) { - return State::INV_SRC_ALPHA; - } else if (blendArg == GL_DST_ALPHA) { - return State::DEST_ALPHA; - } else if (blendArg == GL_ONE_MINUS_DST_ALPHA) { - return State::INV_DEST_ALPHA; - } else if (blendArg == GL_CONSTANT_COLOR) { - return State::FACTOR_COLOR; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_COLOR) { - return State::INV_FACTOR_COLOR; - } else if (blendArg == GL_CONSTANT_ALPHA) { - return State::FACTOR_ALPHA; - } else if (blendArg == GL_ONE_MINUS_CONSTANT_ALPHA) { - return State::INV_FACTOR_ALPHA; - } - - return State::ONE; -} - -void GLBackend::getCurrentGLState(State::Data& state) { - { - GLint modes[2]; - glGetIntegerv(GL_POLYGON_MODE, modes); - if (modes[0] == GL_FILL) { - state.fillMode = State::FILL_FACE; - } else { - if (modes[0] == GL_LINE) { - state.fillMode = State::FILL_LINE; - } else { - state.fillMode = State::FILL_POINT; - } - } - } - { - if (glIsEnabled(GL_CULL_FACE)) { - GLint mode; - glGetIntegerv(GL_CULL_FACE_MODE, &mode); - state.cullMode = (mode == GL_FRONT ? State::CULL_FRONT : State::CULL_BACK); - } else { - state.cullMode = State::CULL_NONE; - } - } - { - GLint winding; - glGetIntegerv(GL_FRONT_FACE, &winding); - state.frontFaceClockwise = (winding == GL_CW); - state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); - state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); - state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); - state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); - } - { - if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { - glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &state.depthBiasSlopeScale); - glGetFloatv(GL_POLYGON_OFFSET_UNITS, &state.depthBias); - } - } - { - GLboolean isEnabled = glIsEnabled(GL_DEPTH_TEST); - GLboolean writeMask; - glGetBooleanv(GL_DEPTH_WRITEMASK, &writeMask); - GLint func; - glGetIntegerv(GL_DEPTH_FUNC, &func); - - state.depthTest = State::DepthTest(isEnabled, writeMask, comparisonFuncFromGL(func)); - } - { - GLboolean isEnabled = glIsEnabled(GL_STENCIL_TEST); - - GLint frontWriteMask; - GLint frontReadMask; - GLint frontRef; - GLint frontFail; - GLint frontDepthFail; - GLint frontPass; - GLint frontFunc; - glGetIntegerv(GL_STENCIL_WRITEMASK, &frontWriteMask); - glGetIntegerv(GL_STENCIL_VALUE_MASK, &frontReadMask); - glGetIntegerv(GL_STENCIL_REF, &frontRef); - glGetIntegerv(GL_STENCIL_FAIL, &frontFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &frontDepthFail); - glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &frontPass); - glGetIntegerv(GL_STENCIL_FUNC, &frontFunc); - - GLint backWriteMask; - GLint backReadMask; - GLint backRef; - GLint backFail; - GLint backDepthFail; - GLint backPass; - GLint backFunc; - glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &backWriteMask); - glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &backReadMask); - glGetIntegerv(GL_STENCIL_BACK_REF, &backRef); - glGetIntegerv(GL_STENCIL_BACK_FAIL, &backFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &backDepthFail); - glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &backPass); - glGetIntegerv(GL_STENCIL_BACK_FUNC, &backFunc); - - state.stencilActivation = State::StencilActivation(isEnabled, frontWriteMask, backWriteMask); - state.stencilTestFront = State::StencilTest(frontRef, frontReadMask, comparisonFuncFromGL(frontFunc), stencilOpFromGL(frontFail), stencilOpFromGL(frontDepthFail), stencilOpFromGL(frontPass)); - state.stencilTestBack = State::StencilTest(backRef, backReadMask, comparisonFuncFromGL(backFunc), stencilOpFromGL(backFail), stencilOpFromGL(backDepthFail), stencilOpFromGL(backPass)); - } - { - GLint mask = 0xFFFFFFFF; - -#ifdef GPU_PROFILE_CORE - if (glIsEnabled(GL_SAMPLE_MASK)) { - glGetIntegerv(GL_SAMPLE_MASK, &mask); - state.sampleMask = mask; - } -#endif - state.sampleMask = mask; - } - { - state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); - } - { - GLboolean isEnabled = glIsEnabled(GL_BLEND); - GLint srcRGB; - GLint srcA; - GLint dstRGB; - GLint dstA; - glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcA); - glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dstA); - - GLint opRGB; - GLint opA; - glGetIntegerv(GL_BLEND_EQUATION_RGB, &opRGB); - glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &opA); - - state.blendFunction = State::BlendFunction(isEnabled, - blendArgFromGL(srcRGB), blendOpFromGL(opRGB), blendArgFromGL(dstRGB), - blendArgFromGL(srcA), blendOpFromGL(opA), blendArgFromGL(dstA)); - } - { - GLboolean mask[4]; - glGetBooleanv(GL_COLOR_WRITEMASK, mask); - state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) - | (mask[1] ? State::WRITE_GREEN : 0) - | (mask[2] ? State::WRITE_BLUE : 0) - | (mask[3] ? State::WRITE_ALPHA : 0); - } - - (void) CHECK_GL_ERROR(); -} - void GLBackend::syncPipelineStateCache() { State::Data state; @@ -500,21 +49,12 @@ void GLBackend::syncPipelineStateCache() { _pipeline._stateSignatureCache = signature; } -static GLenum GL_COMPARISON_FUNCTIONS[] = { - GL_NEVER, - GL_LESS, - GL_EQUAL, - GL_LEQUAL, - GL_GREATER, - GL_NOTEQUAL, - GL_GEQUAL, - GL_ALWAYS }; void GLBackend::do_setStateFillMode(int32 mode) { if (_pipeline._stateCache.fillMode != mode) { static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL }; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.fillMode = State::FillMode(mode); } @@ -530,7 +70,7 @@ void GLBackend::do_setStateCullMode(int32 mode) { glEnable(GL_CULL_FACE); glCullFace(GL_CULL_MODES[mode]); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.cullMode = State::CullMode(mode); } @@ -540,8 +80,8 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) { if (_pipeline._stateCache.frontFaceClockwise != isClockwise) { static GLenum GL_FRONT_FACES[] = { GL_CCW, GL_CW }; glFrontFace(GL_FRONT_FACES[isClockwise]); - (void) CHECK_GL_ERROR(); - + (void)CHECK_GL_ERROR(); + _pipeline._stateCache.frontFaceClockwise = isClockwise; } } @@ -553,7 +93,7 @@ void GLBackend::do_setStateDepthClampEnable(bool enable) { } else { glDisable(GL_DEPTH_CLAMP); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.depthClampEnable = enable; } @@ -566,7 +106,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) { } else { glDisable(GL_SCISSOR_TEST); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.scissorEnable = enable; } @@ -579,7 +119,7 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) { } else { glDisable(GL_MULTISAMPLE); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.multisampleEnable = enable; } @@ -592,14 +132,14 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) { } else { glDisable(GL_LINE_SMOOTH); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.antialisedLineEnable = enable; } } void GLBackend::do_setStateDepthBias(Vec2 bias) { - if ( (bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { + if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) { if ((bias.x != 0.0f) || (bias.y != 0.0f)) { glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_POLYGON_OFFSET_LINE); @@ -610,10 +150,10 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) { glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_POINT); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); - _pipeline._stateCache.depthBias = bias.x; - _pipeline._stateCache.depthBiasSlopeScale = bias.y; + _pipeline._stateCache.depthBias = bias.x; + _pipeline._stateCache.depthBiasSlopeScale = bias.y; } } @@ -622,15 +162,15 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { if (test.isEnabled()) { glEnable(GL_DEPTH_TEST); glDepthMask(test.getWriteMask()); - glDepthFunc(GL_COMPARISON_FUNCTIONS[test.getFunction()]); + glDepthFunc(COMPARISON_TO_GL[test.getFunction()]); } else { glDisable(GL_DEPTH_TEST); } if (CHECK_GL_ERROR()) { qDebug() << "DepthTest" << (test.isEnabled() ? "Enabled" : "Disabled") - << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") - << "Func=" << test.getFunction() - << "Raw=" << test.getRaw(); + << "Mask=" << (test.getWriteMask() ? "Write" : "no Write") + << "Func=" << test.getFunction() + << "Raw=" << test.getRaw(); } _pipeline._stateCache.depthTest = test; @@ -638,7 +178,7 @@ void GLBackend::do_setStateDepthTest(State::DepthTest test) { } void GLBackend::do_setStateStencil(State::StencilActivation activation, State::StencilTest frontTest, State::StencilTest backTest) { - + if ((_pipeline._stateCache.stencilActivation != activation) || (_pipeline._stateCache.stencilTestFront != frontTest) || (_pipeline._stateCache.stencilTestBack != backTest)) { @@ -665,18 +205,18 @@ void GLBackend::do_setStateStencil(State::StencilActivation activation, State::S if (frontTest != backTest) { glStencilOpSeparate(GL_FRONT, STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_FRONT, GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + glStencilFuncSeparate(GL_FRONT, COMPARISON_TO_GL[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); glStencilOpSeparate(GL_BACK, STENCIL_OPS[backTest.getFailOp()], STENCIL_OPS[backTest.getPassOp()], STENCIL_OPS[backTest.getDepthFailOp()]); - glStencilFuncSeparate(GL_BACK, GL_COMPARISON_FUNCTIONS[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); + glStencilFuncSeparate(GL_BACK, COMPARISON_TO_GL[backTest.getFunction()], backTest.getReference(), backTest.getReadMask()); } else { glStencilOp(STENCIL_OPS[frontTest.getFailOp()], STENCIL_OPS[frontTest.getPassOp()], STENCIL_OPS[frontTest.getDepthFailOp()]); - glStencilFunc(GL_COMPARISON_FUNCTIONS[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); + glStencilFunc(COMPARISON_TO_GL[frontTest.getFunction()], frontTest.getReference(), frontTest.getReadMask()); } } else { glDisable(GL_STENCIL_TEST); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.stencilActivation = activation; _pipeline._stateCache.stencilTestFront = frontTest; @@ -691,7 +231,7 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { } else { glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.alphaToCoverageEnable = enable; } @@ -699,15 +239,13 @@ void GLBackend::do_setStateAlphaToCoverageEnable(bool enable) { void GLBackend::do_setStateSampleMask(uint32 mask) { if (_pipeline._stateCache.sampleMask != mask) { -#ifdef GPU_CORE_PROFILE if (mask == 0xFFFFFFFF) { glDisable(GL_SAMPLE_MASK); } else { glEnable(GL_SAMPLE_MASK); glSampleMaski(0, mask); } - (void) CHECK_GL_ERROR(); -#endif + (void)CHECK_GL_ERROR(); _pipeline._stateCache.sampleMask = mask; } } @@ -717,40 +255,16 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { if (function.isEnabled()) { glEnable(GL_BLEND); - static GLenum GL_BLEND_OPS[] = { - GL_FUNC_ADD, - GL_FUNC_SUBTRACT, - GL_FUNC_REVERSE_SUBTRACT, - GL_MIN, - GL_MAX }; + glBlendEquationSeparate(BLEND_OPS_TO_GL[function.getOperationColor()], BLEND_OPS_TO_GL[function.getOperationAlpha()]); + (void)CHECK_GL_ERROR(); - glBlendEquationSeparate(GL_BLEND_OPS[function.getOperationColor()], GL_BLEND_OPS[function.getOperationAlpha()]); - (void) CHECK_GL_ERROR(); - static GLenum BLEND_ARGS[] = { - GL_ZERO, - GL_ONE, - GL_SRC_COLOR, - GL_ONE_MINUS_SRC_COLOR, - GL_SRC_ALPHA, - GL_ONE_MINUS_SRC_ALPHA, - GL_DST_ALPHA, - GL_ONE_MINUS_DST_ALPHA, - GL_DST_COLOR, - GL_ONE_MINUS_DST_COLOR, - GL_SRC_ALPHA_SATURATE, - GL_CONSTANT_COLOR, - GL_ONE_MINUS_CONSTANT_COLOR, - GL_CONSTANT_ALPHA, - GL_ONE_MINUS_CONSTANT_ALPHA, - }; - - glBlendFuncSeparate(BLEND_ARGS[function.getSourceColor()], BLEND_ARGS[function.getDestinationColor()], - BLEND_ARGS[function.getSourceAlpha()], BLEND_ARGS[function.getDestinationAlpha()]); + glBlendFuncSeparate(BLEND_ARGS_TO_GL[function.getSourceColor()], BLEND_ARGS_TO_GL[function.getDestinationColor()], + BLEND_ARGS_TO_GL[function.getSourceAlpha()], BLEND_ARGS_TO_GL[function.getDestinationAlpha()]); } else { glDisable(GL_BLEND); } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.blendFunction = function; } @@ -759,10 +273,10 @@ void GLBackend::do_setStateBlend(State::BlendFunction function) { void GLBackend::do_setStateColorWriteMask(uint32 mask) { if (_pipeline._stateCache.colorWriteMask != mask) { glColorMask(mask & State::ColorMask::WRITE_RED, - mask & State::ColorMask::WRITE_GREEN, - mask & State::ColorMask::WRITE_BLUE, - mask & State::ColorMask::WRITE_ALPHA ); - (void) CHECK_GL_ERROR(); + mask & State::ColorMask::WRITE_GREEN, + mask & State::ColorMask::WRITE_BLUE, + mask & State::ColorMask::WRITE_ALPHA); + (void)CHECK_GL_ERROR(); _pipeline._stateCache.colorWriteMask = mask; } @@ -771,12 +285,12 @@ void GLBackend::do_setStateColorWriteMask(uint32 mask) { void GLBackend::do_setStateBlendFactor(Batch& batch, size_t paramOffset) { Vec4 factor(batch._params[paramOffset + 0]._float, - batch._params[paramOffset + 1]._float, - batch._params[paramOffset + 2]._float, - batch._params[paramOffset + 3]._float); + batch._params[paramOffset + 1]._float, + batch._params[paramOffset + 2]._float, + batch._params[paramOffset + 3]._float); glBlendColor(factor.x, factor.y, factor.z, factor.w); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } void GLBackend::do_setStateScissorRect(Batch& batch, size_t paramOffset) { @@ -790,5 +304,6 @@ void GLBackend::do_setStateScissorRect(Batch& batch, size_t paramOffset) { } } glScissor(rect.x, rect.y, rect.z, rect.w); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } + diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp index 952e5bad8f..6d9b5fd2c7 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTexture.cpp @@ -9,591 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" - -#include -#include -#include - -#include "GLBackendShared.h" - -#include "GLBackendTextureTransfer.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; - -GLenum gpuToGLTextureType(const Texture& texture) { - switch (texture.getType()) { - case Texture::TEX_2D: - return GL_TEXTURE_2D; - break; - - case Texture::TEX_CUBE: - return GL_TEXTURE_CUBE_MAP; - break; - - default: - qFatal("Unsupported texture type"); - } - Q_UNREACHABLE(); - return GL_TEXTURE_2D; -} - -GLuint allocateSingleTexture() { - Backend::incrementTextureGPUCount(); - GLuint result; - glGenTextures(1, &result); - return result; -} - - -// FIXME placeholder for texture memory over-use -#define DEFAULT_MAX_MEMORY_MB 256 - -float GLBackend::GLTexture::getMemoryPressure() { - // Check for an explicit memory limit - auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); - - // If no memory limit has been set, use a percentage of the total dedicated memory - if (!availableTextureMemory) { - auto totalGpuMemory = GLBackend::getDedicatedMemory(); - - // If no limit has been explicitly set, and the dedicated memory can't be determined, - // just use a fallback fixed value of 256 MB - if (!totalGpuMemory) { - totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); - } - - // Allow 75% of all available GPU memory to be consumed by textures - // FIXME overly conservative? - availableTextureMemory = (totalGpuMemory >> 2) * 3; - } - - // Return the consumed texture memory divided by the available texture memory. - auto consumedGpuMemory = Context::getTextureGPUMemoryUsage(); - return (float)consumedGpuMemory / (float)availableTextureMemory; -} - -GLBackend::GLTexture::DownsampleSource::DownsampleSource(GLTexture& oldTexture) : - _texture(oldTexture._privateTexture), - _minMip(oldTexture._minMip), - _maxMip(oldTexture._maxMip) -{ - // Take ownership of the GL texture - oldTexture._texture = oldTexture._privateTexture = 0; -} - -GLBackend::GLTexture::DownsampleSource::~DownsampleSource() { - if (_texture) { - Backend::decrementTextureGPUCount(); - glDeleteTextures(1, &_texture); - } -} - -const GLenum GLBackend::GLTexture::CUBE_FACE_LAYOUT[6] = { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z -}; - -static std::map _textureCountByMips; -static uint16 _currentMaxMipCount { 0 }; - -GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture, bool init) : - _storageStamp(texture.getStamp()), - _target(gpuToGLTextureType(texture)), - _maxMip(texture.maxMip()), - _minMip(texture.minMip()), - _transferrable(transferrable), - _virtualSize(texture.evalTotalSize()), - _size(_virtualSize), - _gpuTexture(texture) -{ - Q_UNUSED(init); - - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); - if (!_textureCountByMips.count(mipCount)) { - _textureCountByMips[mipCount] = 1; - } else { - ++_textureCountByMips[mipCount]; - } - } else { - withPreservedTexture([&] { - createTexture(); - }); - _contentStamp = _gpuTexture.getDataStamp(); - postTransfer(); - } - - Backend::updateTextureGPUMemoryUsage(0, _size); - Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); -} - -// Create the texture and allocate storage -GLBackend::GLTexture::GLTexture(bool transferrable, const Texture& texture) : - GLTexture(transferrable, texture, true) -{ - Backend::setGPUObject(texture, this); -} - -// Create the texture and copy from the original higher resolution version -GLBackend::GLTexture::GLTexture(GLTexture& originalTexture, const gpu::Texture& texture) : - GLTexture(originalTexture._transferrable, texture, true) -{ - if (!originalTexture._texture) { - qFatal("Invalid original texture"); - } - Q_ASSERT(_minMip >= originalTexture._minMip); - // Our downsampler takes ownership of the texture - _downsampleSource = std::make_shared(originalTexture); - _texture = _downsampleSource->_texture; - - // Set the GPU object last because that implicitly destroys the originalTexture object - Backend::setGPUObject(texture, this); -} - -GLBackend::GLTexture::~GLTexture() { - if (_privateTexture != 0) { - Backend::decrementTextureGPUCount(); - glDeleteTextures(1, &_privateTexture); - } - - if (_transferrable) { - uint16 mipCount = usedMipLevels(); - Q_ASSERT(_textureCountByMips.count(mipCount)); - auto& numTexturesForMipCount = _textureCountByMips[mipCount]; - --numTexturesForMipCount; - if (0 == numTexturesForMipCount) { - _textureCountByMips.erase(mipCount); - if (mipCount == _currentMaxMipCount) { - _currentMaxMipCount = _textureCountByMips.rbegin()->first; - } - } - } - - Backend::updateTextureGPUMemoryUsage(_size, 0); - Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); -} - -const std::vector& GLBackend::GLTexture::getFaceTargets() const { - static std::vector cubeFaceTargets { - GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, - GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, - GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z - }; - static std::vector faceTargets { - GL_TEXTURE_2D - }; - switch (_target) { - case GL_TEXTURE_2D: - return faceTargets; - case GL_TEXTURE_CUBE_MAP: - return cubeFaceTargets; - default: - Q_UNREACHABLE(); - break; - } - Q_UNREACHABLE(); - return faceTargets; -} - -void GLBackend::GLTexture::withPreservedTexture(std::function f) { - GLint boundTex = -1; - switch (_target) { - case GL_TEXTURE_2D: - glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); - break; - - case GL_TEXTURE_CUBE_MAP: - glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); - break; - - default: - qFatal("Unsupported texture type"); - } - (void)CHECK_GL_ERROR(); - - f(); - - glBindTexture(_target, boundTex); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::GLTexture::createTexture() { - _privateTexture = allocateSingleTexture(); - - glBindTexture(_target, _privateTexture); - (void)CHECK_GL_ERROR(); - - allocateStorage(); - (void)CHECK_GL_ERROR(); - - syncSampler(_gpuTexture.getSampler(), _gpuTexture.getType(), this); - (void)CHECK_GL_ERROR(); -} - -void GLBackend::GLTexture::allocateStorage() { - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat()); - glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); - (void)CHECK_GL_ERROR(); - glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); - (void)CHECK_GL_ERROR(); - if (GLEW_VERSION_4_2 && !_gpuTexture.getTexelFormat().isCompressed()) { - // Get the dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuTexture.evalMipDimensions(_minMip); - glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } else { - for (uint16_t l = _minMip; l <= _maxMip; l++) { - // Get the mip level dimensions, accounting for the downgrade level - Vec3u dimensions = _gpuTexture.evalMipDimensions(l); - for (GLenum target : getFaceTargets()) { - glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); - (void)CHECK_GL_ERROR(); - } - } - } -} - - -void GLBackend::GLTexture::setSize(GLuint size) { - Backend::updateTextureGPUMemoryUsage(_size, size); - _size = size; -} - -void GLBackend::GLTexture::updateSize() { - setSize(_virtualSize); - if (!_texture) { - return; - } - - if (_gpuTexture.getTexelFormat().isCompressed()) { - GLenum proxyType = GL_TEXTURE_2D; - GLuint numFaces = 1; - if (_gpuTexture.getType() == gpu::Texture::TEX_CUBE) { - proxyType = CUBE_FACE_LAYOUT[0]; - numFaces = CUBE_NUM_FACES; - } - GLint gpuSize{ 0 }; - glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); - (void)CHECK_GL_ERROR(); - - if (gpuSize) { - for (GLuint level = _minMip; level < _maxMip; level++) { - GLint levelSize{ 0 }; - glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); - levelSize *= numFaces; - - if (levelSize <= 0) { - break; - } - gpuSize += levelSize; - } - (void)CHECK_GL_ERROR(); - setSize(gpuSize); - return; - } - } -} - -bool GLBackend::GLTexture::isInvalid() const { - return _storageStamp < _gpuTexture.getStamp(); -} - -bool GLBackend::GLTexture::isOutdated() const { - return GLTexture::Idle == _syncState && _contentStamp < _gpuTexture.getDataStamp(); -} - -bool GLBackend::GLTexture::isOverMaxMemory() const { - // FIXME switch to using the max mip count used from the previous frame - if (usedMipLevels() < _currentMaxMipCount) { - return false; - } - Q_ASSERT(usedMipLevels() == _currentMaxMipCount); - - if (getMemoryPressure() < 1.0f) { - return false; - } - - return true; -} - -bool GLBackend::GLTexture::isReady() const { - // If we have an invalid texture, we're never ready - if (isInvalid()) { - return false; - } - - // If we're out of date, but the transfer is in progress, report ready - // as a special case - auto syncState = _syncState.load(); - - if (isOutdated()) { - return Idle != syncState; - } - - if (Idle != syncState) { - return false; - } - - return true; -} - -// Move content bits from the CPU to the GPU for a given mip / face -void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { - auto mip = _gpuTexture.accessStoredMipFace(mipLevel, face); - GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuTexture.getTexelFormat(), mip->getFormat()); - //GLenum target = getFaceTargets()[face]; - GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; - auto size = _gpuTexture.evalMipDimensions(mipLevel); - glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); - (void)CHECK_GL_ERROR(); -} - -// This should never happen on the main thread -// Move content bits from the CPU to the GPU -void GLBackend::GLTexture::transfer() const { - PROFILE_RANGE(__FUNCTION__); - //qDebug() << "Transferring texture: " << _privateTexture; - // Need to update the content of the GPU object from the source sysmem of the texture - if (_contentStamp >= _gpuTexture.getDataStamp()) { - return; - } - - glBindTexture(_target, _privateTexture); - (void)CHECK_GL_ERROR(); - - if (_downsampleSource) { - GLuint fbo { 0 }; - glGenFramebuffers(1, &fbo); - (void)CHECK_GL_ERROR(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - (void)CHECK_GL_ERROR(); - // Find the distance between the old min mip and the new one - uint16 mipOffset = _minMip - _downsampleSource->_minMip; - for (uint16 i = _minMip; i <= _maxMip; ++i) { - uint16 targetMip = i - _minMip; - uint16 sourceMip = targetMip + mipOffset; - Vec3u dimensions = _gpuTexture.evalMipDimensions(i); - for (GLenum target : getFaceTargets()) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource->_texture, sourceMip); - (void)CHECK_GL_ERROR(); - glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); - (void)CHECK_GL_ERROR(); - } - } - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - } else { - // GO through the process of allocating the correct storage and/or update the content - switch (_gpuTexture.getType()) { - case Texture::TEX_2D: - { - for (uint16_t i = _minMip; i <= _maxMip; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i)) { - transferMip(i); - } - } - } - break; - - case Texture::TEX_CUBE: - // transfer pixels from each faces - for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { - transferMip(i, f); - } - } - } - break; - - default: - qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; - break; - } - } - if (_gpuTexture.isAutogenerateMips()) { - glGenerateMipmap(_target); - (void)CHECK_GL_ERROR(); - } -} - -// Do any post-transfer operations that might be required on the main context / rendering thread -void GLBackend::GLTexture::postTransfer() { - setSyncState(GLTexture::Idle); - - // The public gltexture becaomes available - _texture = _privateTexture; - - _downsampleSource.reset(); - - // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory - switch (_gpuTexture.getType()) { - case Texture::TEX_2D: - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i)) { - _gpuTexture.notifyMipFaceGPULoaded(i); - } - } - break; - - case Texture::TEX_CUBE: - // transfer pixels from each faces - for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { - for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { - if (_gpuTexture.isStoredMipFaceAvailable(i, f)) { - _gpuTexture.notifyMipFaceGPULoaded(i, f); - } - } - } - break; - - default: - qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuTexture.getType() << " not supported"; - break; - } -} - -GLBackend::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texturePointer, bool needTransfer) { - const Texture& texture = *texturePointer; - if (!texture.isDefined()) { - // NO texture definition yet so let's avoid thinking - return nullptr; - } - - // If the object hasn't been created, or the object definition is out of date, drop and re-create - GLTexture* object = Backend::getGPUObject(texture); - - // Create the texture if need be (force re-creation if the storage stamp changes - // for easier use of immutable storage) - if (!object || object->isInvalid()) { - // This automatically any previous texture - object = new GLTexture(needTransfer, texture); - } - - // Object maybe doens't neet to be tranasferred after creation - if (!object->_transferrable) { - return object; - } - - // If we just did a transfer, return the object after doing post-transfer work - if (GLTexture::Transferred == object->getSyncState()) { - object->postTransfer(); - return object; - } - - if (object->isReady()) { - // Do we need to reduce texture memory usage? - if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { - // This automatically destroys the old texture - object = new GLTexture(*object, texture); - _textureTransferHelper->transferTexture(texturePointer); - } - } else if (object->isOutdated()) { - // Object might be outdated, if so, start the transfer - // (outdated objects that are already in transfer will have reported 'true' for ready() - _textureTransferHelper->transferTexture(texturePointer); - } - - return object; -} - -std::shared_ptr GLBackend::_textureTransferHelper; - -void GLBackend::initTextureTransferHelper() { - _textureTransferHelper = std::make_shared(); -} - -GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) { - if (!texture) { - return 0; - } - GLTexture* object { nullptr }; - if (sync) { - object = GLBackend::syncGPUObject(texture); - } else { - object = Backend::getGPUObject(*texture); - } - if (object) { - if (object->getSyncState() == GLTexture::Idle) { - return object->_texture; - } else if (object->_downsampleSource) { - return object->_downsampleSource->_texture; - } else { - return 0; - } - } else { - return 0; - } -} - -void GLBackend::syncSampler(const Sampler& sampler, Texture::Type type, const GLTexture* object) { - if (!object) return; - - class GLFilterMode { - public: - GLint minFilter; - GLint magFilter; - }; - static const GLFilterMode filterModes[Sampler::NUM_FILTERS] = { - { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, - { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, - { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, - { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, - - { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, - { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, - { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, - { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, - { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, - }; - - auto fm = filterModes[sampler.getFilter()]; - glTexParameteri(object->_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); - glTexParameteri(object->_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); - - static const GLenum comparisonFuncs[NUM_COMPARISON_FUNCS] = { - GL_NEVER, - GL_LESS, - GL_EQUAL, - GL_LEQUAL, - GL_GREATER, - GL_NOTEQUAL, - GL_GEQUAL, - GL_ALWAYS }; - - if (sampler.doComparison()) { - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_FUNC, comparisonFuncs[sampler.getComparisonFunction()]); - } else { - glTexParameteri(object->_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - - static const GLenum wrapModes[Sampler::NUM_WRAP_MODES] = { - GL_REPEAT, // WRAP_REPEAT, - GL_MIRRORED_REPEAT, // WRAP_MIRROR, - GL_CLAMP_TO_EDGE, // WRAP_CLAMP, - GL_CLAMP_TO_BORDER, // WRAP_BORDER, - GL_MIRROR_CLAMP_TO_EDGE_EXT }; // WRAP_MIRROR_ONCE, - - glTexParameteri(object->_target, GL_TEXTURE_WRAP_S, wrapModes[sampler.getWrapModeU()]); - glTexParameteri(object->_target, GL_TEXTURE_WRAP_T, wrapModes[sampler.getWrapModeV()]); - glTexParameteri(object->_target, GL_TEXTURE_WRAP_R, wrapModes[sampler.getWrapModeW()]); - - glTexParameterfv(object->_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); - glTexParameteri(object->_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); - glTexParameterf(object->_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); - glTexParameterf(object->_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); - glTexParameterf(object->_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); -} - void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { TexturePointer resourceTexture = batch._textures.get(batch._params[paramOffset + 0]._uint); if (!resourceTexture) { @@ -601,28 +21,10 @@ void GLBackend::do_generateTextureMips(Batch& batch, size_t paramOffset) { } // DO not transfer the texture, this call is expected for rendering texture - GLTexture* object = GLBackend::syncGPUObject(resourceTexture, false); + GLTexture* object = syncGPUObject(resourceTexture, false); if (!object) { return; } - // IN 4.1 we still need to find an available slot - auto freeSlot = _resource.findEmptyTextureSlot(); - auto bindingSlot = (freeSlot < 0 ? 0 : freeSlot); - glActiveTexture(GL_TEXTURE0 + bindingSlot); - glBindTexture(object->_target, object->_texture); - - glGenerateMipmap(object->_target); - - if (freeSlot < 0) { - // If had to use slot 0 then restore state - GLTexture* boundObject = GLBackend::syncGPUObject(_resource._textures[0]); - if (boundObject) { - glBindTexture(boundObject->_target, boundObject->_texture); - } - } else { - // clean up - glBindTexture(object->_target, 0); - } - (void)CHECK_GL_ERROR(); + object->generateMips(); } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp index 7c60eac10e..6120df0bfc 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendTransform.cpp @@ -9,7 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "GLBackend.h" -#include "GLBackendShared.h" using namespace gpu; using namespace gpu::gl; @@ -51,26 +50,11 @@ void GLBackend::do_setDepthRangeTransform(Batch& batch, size_t paramOffset) { } } -void GLBackend::initTransform() { - glGenBuffers(1, &_transform._objectBuffer); - glGenBuffers(1, &_transform._cameraBuffer); - glGenBuffers(1, &_transform._drawCallInfoBuffer); -#ifndef GPU_SSBO_DRAW_CALL_INFO - glGenTextures(1, &_transform._objectBufferTexture); -#endif - size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); - while (_transform._cameraUboSize < cameraSize) { - _transform._cameraUboSize += _uboAlignment; - } -} - void GLBackend::killTransform() { glDeleteBuffers(1, &_transform._objectBuffer); glDeleteBuffers(1, &_transform._cameraBuffer); glDeleteBuffers(1, &_transform._drawCallInfoBuffer); -#ifndef GPU_SSBO_DRAW_CALL_INFO glDeleteTextures(1, &_transform._objectBufferTexture); -#endif } void GLBackend::syncTransformStateCache() { @@ -118,64 +102,6 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo _invalidView = _invalidProj = _invalidViewport = false; } -void GLBackend::TransformStageState::transfer(const Batch& batch) const { - // FIXME not thread safe - static std::vector bufferData; - if (!_cameras.empty()) { - bufferData.resize(_cameraUboSize * _cameras.size()); - for (size_t i = 0; i < _cameras.size(); ++i) { - memcpy(bufferData.data() + (_cameraUboSize * i), &_cameras[i], sizeof(CameraBufferElement)); - } - glBindBuffer(GL_UNIFORM_BUFFER, _cameraBuffer); - glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - } - - if (!batch._objects.empty()) { - auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject); - bufferData.resize(byteSize); - memcpy(bufferData.data(), batch._objects.data(), byteSize); - -#ifdef GPU_SSBO_DRAW_CALL_INFO - glBindBuffer(GL_SHADER_STORAGE_BUFFER, _objectBuffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); -#else - glBindBuffer(GL_TEXTURE_BUFFER, _objectBuffer); - glBufferData(GL_TEXTURE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_TEXTURE_BUFFER, 0); -#endif - } - - if (!batch._namedData.empty()) { - bufferData.clear(); - for (auto& data : batch._namedData) { - auto currentSize = bufferData.size(); - auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo); - bufferData.resize(currentSize + bytesToCopy); - memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy); - _drawCallInfoOffsets[data.first] = (GLvoid*)currentSize; - } - - glBindBuffer(GL_ARRAY_BUFFER, _drawCallInfoBuffer); - glBufferData(GL_ARRAY_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - -#ifdef GPU_SSBO_DRAW_CALL_INFO - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _objectBuffer); -#else - glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); - glBindTexture(GL_TEXTURE_BUFFER, _objectBufferTexture); - glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _objectBuffer); -#endif - - CHECK_GL_ERROR(); - - // Make sure the current Camera offset is unknown before render Draw - _currentCameraOffset = INVALID_OFFSET; -} - void GLBackend::TransformStageState::update(size_t commandIndex, const StereoState& stereo) const { size_t offset = INVALID_OFFSET; while ((_camerasItr != _cameraOffsets.end()) && (commandIndex >= (*_camerasItr).first)) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp new file mode 100644 index 0000000000..cd0f86a410 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLBuffer.cpp @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// + +#include "GLBuffer.h" + +using namespace gpu; +using namespace gpu::gl; + +GLBuffer::~GLBuffer() { + glDeleteBuffers(1, &_id); + Backend::decrementBufferGPUCount(); + Backend::updateBufferGPUMemoryUsage(_size, 0); +} + +GLBuffer::GLBuffer(const Buffer& buffer, GLuint id) : + GLObject(buffer, id), + _size((GLuint)buffer._sysmem.getSize()), + _stamp(buffer._sysmem.getStamp()) +{ + Backend::incrementBufferGPUCount(); + Backend::updateBufferGPUMemoryUsage(0, _size); +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLBuffer.h b/libraries/gpu-gl/src/gpu/gl/GLBuffer.h new file mode 100644 index 0000000000..4783541b11 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLBuffer.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLBuffer_h +#define hifi_gpu_gl_GLBuffer_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLBuffer : public GLObject { +public: + template + static GLBufferType* sync(const Buffer& buffer) { + GLBufferType* object = Backend::getGPUObject(buffer); + + // Has the storage size changed? + if (!object || object->_stamp != buffer.getSysmem().getStamp()) { + object = new GLBufferType(buffer, object); + } + + if (0 != (buffer._flags & Buffer::DIRTY)) { + object->transfer(); + } + + return object; + } + + template + static GLuint getId(const Buffer& buffer) { + GLBuffer* bo = sync(buffer); + if (bo) { + return bo->_buffer; + } else { + return 0; + } + } + + const GLuint& _buffer { _id }; + const GLuint _size; + const Stamp _stamp; + + ~GLBuffer(); + + virtual void transfer() = 0; + +protected: + GLBuffer(const Buffer& buffer, GLuint id); +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp new file mode 100644 index 0000000000..91f7fbd494 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.cpp @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// + +#include "GLFramebuffer.h" + +using namespace gpu; +using namespace gpu::gl; + + +bool GLFramebuffer::checkStatus(GLenum target) const { + bool result = false; + switch (_status) { + case GL_FRAMEBUFFER_COMPLETE: + // Success ! + result = true; + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER."; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER."; + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + qCDebug(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; + break; + } + return result; +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h new file mode 100644 index 0000000000..d54c181c20 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLFramebuffer.h @@ -0,0 +1,76 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLFramebuffer_h +#define hifi_gpu_gl_GLFramebuffer_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLFramebuffer : public GLObject { +public: + template + static GLFramebufferType* sync(const Framebuffer& framebuffer) { + GLFramebufferType* object = Backend::getGPUObject(framebuffer); + + bool needsUpate { false }; + if (!object || + framebuffer.getDepthStamp() != object->_depthStamp || + framebuffer.getColorStamps() != object->_colorStamps) { + needsUpate = true; + } + + // If GPU object already created and in sync + if (!needsUpate) { + return object; + } else if (framebuffer.isEmpty()) { + // NO framebuffer definition yet so let's avoid thinking + return nullptr; + } + + // need to have a gpu object? + if (!object) { + // All is green, assign the gpuobject to the Framebuffer + object = new GLFramebufferType(framebuffer); + Backend::setGPUObject(framebuffer, object); + (void)CHECK_GL_ERROR(); + } + + object->update(); + return object; + } + + template + static GLuint getId(const Framebuffer& framebuffer) { + GLFramebufferType* fbo = sync(framebuffer); + if (fbo) { + return fbo->_id; + } else { + return 0; + } + } + + const GLuint& _fbo { _id }; + std::vector _colorBuffers; + Stamp _depthStamp { 0 }; + std::vector _colorStamps; + +protected: + GLenum _status { GL_FRAMEBUFFER_COMPLETE }; + virtual void update() = 0; + bool checkStatus(GLenum target) const; + + GLFramebuffer(const Framebuffer& framebuffer, GLuint id) : GLObject(framebuffer, id) {} + ~GLFramebuffer() { if (_id) { glDeleteFramebuffers(1, &_id); } }; + +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp new file mode 100644 index 0000000000..19cf798b19 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLPipeline.cpp @@ -0,0 +1,48 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// + +#include "GLPipeline.h" + +#include "GLShader.h" +#include "GLState.h" + +using namespace gpu; +using namespace gpu::gl; + +GLPipeline* GLPipeline::sync(const Pipeline& pipeline) { + GLPipeline* object = Backend::getGPUObject(pipeline); + + // If GPU object already created then good + if (object) { + return object; + } + + // No object allocated yet, let's see if it's worth it... + ShaderPointer shader = pipeline.getProgram(); + GLShader* programObject = GLShader::sync(*shader); + if (programObject == nullptr) { + return nullptr; + } + + StatePointer state = pipeline.getState(); + GLState* stateObject = GLState::sync(*state); + if (stateObject == nullptr) { + return nullptr; + } + + // Program and state are valid, we can create the pipeline object + if (!object) { + object = new GLPipeline(); + Backend::setGPUObject(pipeline, object); + } + + object->_program = programObject; + object->_state = stateObject; + + return object; +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl/src/gpu/gl/GLPipeline.h new file mode 100644 index 0000000000..9ade2bb830 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLPipeline.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLPipeline_h +#define hifi_gpu_gl_GLPipeline_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLPipeline : public GPUObject { +public: + static GLPipeline* sync(const Pipeline& pipeline); + + GLShader* _program { nullptr }; + GLState* _state { nullptr }; +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp new file mode 100644 index 0000000000..b358db40c9 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp @@ -0,0 +1,12 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// + +#include "GLQuery.h" + +using namespace gpu; +using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.h b/libraries/gpu-gl/src/gpu/gl/GLQuery.h new file mode 100644 index 0000000000..7b14b227ab --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLQuery.h @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLQuery_h +#define hifi_gpu_gl_GLQuery_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLQuery : public GLObject { + using Parent = gpu::gl::GLObject; +public: + template + static GLQueryType* sync(const Query& query) { + GLQueryType* object = Backend::getGPUObject(query); + + // need to have a gpu object? + if (!object) { + // All is green, assign the gpuobject to the Query + object = new GLQueryType(query); + (void)CHECK_GL_ERROR(); + Backend::setGPUObject(query, object); + } + + return object; + } + + template + static GLuint getId(const QueryPointer& query) { + if (!query) { + return 0; + } + + GLQuery* object = sync(*query); + if (!object) { + return 0; + } + + return object->_qo; + } + + const GLuint& _qo { _id }; + GLuint64 _result { (GLuint64)-1 }; + +protected: + GLQuery(const Query& query, GLuint id) : Parent(query, id) {} + ~GLQuery() { if (_id) { glDeleteQueries(1, &_id); } } +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp new file mode 100644 index 0000000000..c89bc88899 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.cpp @@ -0,0 +1,182 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#include "GLShader.h" +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl; + +GLShader::GLShader() { +} + +GLShader::~GLShader() { + for (auto& so : _shaderObjects) { + if (so.glshader != 0) { + glDeleteShader(so.glshader); + } + if (so.glprogram != 0) { + glDeleteProgram(so.glprogram); + } + } +} + +GLShader* compileBackendShader(const Shader& shader) { + // Any GLSLprogram ? normally yes... + const std::string& shaderSource = shader.getSource().getCode(); + + // GLSL version + const std::string glslVersion = { + "#version 410 core" + }; + + // Shader domain + const int NUM_SHADER_DOMAINS = 2; + const GLenum SHADER_DOMAINS[NUM_SHADER_DOMAINS] = { + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER + }; + GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; + + // Domain specific defines + const std::string domainDefines[NUM_SHADER_DOMAINS] = { + "#define GPU_VERTEX_SHADER", + "#define GPU_PIXEL_SHADER" + }; + + // Versions specific of the shader + const std::string versionDefines[GLShader::NumVersions] = { + "" + }; + + GLShader::ShaderObjects shaderObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& shaderObject = shaderObjects[version]; + + std::string shaderDefines = glslVersion + "\n" + domainDefines[shader.getType()] + "\n" + versionDefines[version]; + + bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram); + if (!result) { + return nullptr; + } + } + + // So far so good, the shader is created successfully + GLShader* object = new GLShader(); + object->_shaderObjects = shaderObjects; + + return object; +} + +GLShader* compileBackendProgram(const Shader& program) { + if (!program.isProgram()) { + return nullptr; + } + + GLShader::ShaderObjects programObjects; + + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& programObject = programObjects[version]; + + // Let's go through every shaders and make sure they are ready to go + std::vector< GLuint > shaderGLObjects; + for (auto subShader : program.getShaders()) { + auto object = GLShader::sync(*subShader); + if (object) { + shaderGLObjects.push_back(object->_shaderObjects[version].glshader); + } else { + qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; + return nullptr; + } + } + + GLuint glprogram = compileProgram(shaderGLObjects); + if (glprogram == 0) { + return nullptr; + } + + programObject.glprogram = glprogram; + + makeProgramBindings(programObject); + } + + // So far so good, the program versions have all been created successfully + GLShader* object = new GLShader(); + object->_shaderObjects = programObjects; + + return object; +} + +GLShader* GLShader::sync(const Shader& shader) { + GLShader* object = Backend::getGPUObject(shader); + + // If GPU object already created then good + if (object) { + return object; + } + // need to have a gpu object? + if (shader.isProgram()) { + GLShader* tempObject = compileBackendProgram(shader); + if (tempObject) { + object = tempObject; + Backend::setGPUObject(shader, object); + } + } else if (shader.isDomain()) { + GLShader* tempObject = compileBackendShader(shader); + if (tempObject) { + object = tempObject; + Backend::setGPUObject(shader, object); + } + } + + return object; +} + +bool GLShader::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { + + // First make sure the Shader has been compiled + GLShader* object = sync(shader); + if (!object) { + return false; + } + + // Apply bindings to all program versions and generate list of slots from default version + for (int version = 0; version < GLShader::NumVersions; version++) { + auto& shaderObject = object->_shaderObjects[version]; + if (shaderObject.glprogram) { + Shader::SlotSet buffers; + makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); + + Shader::SlotSet uniforms; + Shader::SlotSet textures; + Shader::SlotSet samplers; + makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); + + Shader::SlotSet inputs; + makeInputSlots(shaderObject.glprogram, slotBindings, inputs); + + Shader::SlotSet outputs; + makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); + + // Define the public slots only from the default version + if (version == 0) { + shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs); + } else { + GLShader::UniformMapping mapping; + for (auto srcUniform : shader.getUniforms()) { + mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); + } + object->_uniformMappings.push_back(mapping); + } + } + } + + + return true; +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLShader.h b/libraries/gpu-gl/src/gpu/gl/GLShader.h new file mode 100644 index 0000000000..ca583e6d74 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShader.h @@ -0,0 +1,52 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLShader_h +#define hifi_gpu_gl_GLShader_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLShader : public GPUObject { +public: + static GLShader* sync(const Shader& shader); + static bool makeProgram(Shader& shader, const Shader::BindingSet& slotBindings); + + enum Version { + Mono = 0, + NumVersions + }; + + using ShaderObject = gpu::gl::ShaderObject; + using ShaderObjects = std::array< ShaderObject, NumVersions >; + + using UniformMapping = std::map; + using UniformMappingVersions = std::vector; + + GLShader(); + ~GLShader(); + + ShaderObjects _shaderObjects; + UniformMappingVersions _uniformMappings; + + GLuint getProgram(Version version = Mono) const { + return _shaderObjects[version].glprogram; + } + + 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; + } + +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp similarity index 57% rename from libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp rename to libraries/gpu-gl/src/gpu/gl/GLShared.cpp index 8ebf7751d1..17152733d1 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.cpp @@ -1,34 +1,648 @@ // -// GLBackendShader.cpp -// libraries/gpu/src/gpu -// -// Created by Sam Gateau on 2/28/2015. -// Copyright 2014 High Fidelity, Inc. +// Created by Bradley Austin Davis on 2016/05/14 +// Copyright 2013-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 // -#include "GLBackend.h" -#include "GLBackendShared.h" +#include "GLShared.h" -using namespace gpu; -using namespace gpu::gl; +#include -GLBackend::GLShader::GLShader() -{ -} +#include +#include -GLBackend::GLShader::~GLShader() { - for (auto& so : _shaderObjects) { - if (so.glshader != 0) { - glDeleteShader(so.glshader); - } - if (so.glprogram != 0) { - glDeleteProgram(so.glprogram); +Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") + +namespace gpu { namespace gl { + +bool checkGLError(const char* name) { + GLenum error = glGetError(); + if (!error) { + return false; + } else { + switch (error) { + case GL_INVALID_ENUM: + qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_INVALID_VALUE: + qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; + break; + case GL_INVALID_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_OUT_OF_MEMORY: + qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + break; + case GL_STACK_UNDERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; + break; + case GL_STACK_OVERFLOW: + qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; + break; } + return true; } } +bool checkGLErrorDebug(const char* name) { +#ifdef DEBUG + return checkGLError(name); +#else + Q_UNUSED(name); + return false; +#endif +} + +gpu::Size getDedicatedMemory() { + static Size dedicatedMemory { 0 }; + static std::once_flag once; + std::call_once(once, [&] { +#ifdef Q_OS_WIN + if (!dedicatedMemory && wglGetGPUIDsAMD && wglGetGPUInfoAMD) { + UINT maxCount = wglGetGPUIDsAMD(0, 0); + std::vector ids; + ids.resize(maxCount); + wglGetGPUIDsAMD(maxCount, &ids[0]); + GLuint memTotal; + wglGetGPUInfoAMD(ids[0], WGL_GPU_RAM_AMD, GL_UNSIGNED_INT, sizeof(GLuint), &memTotal); + dedicatedMemory = MB_TO_BYTES(memTotal); + } +#endif + + if (!dedicatedMemory) { + GLint atiGpuMemory[4]; + // not really total memory, but close enough if called early enough in the application lifecycle + glGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, atiGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(atiGpuMemory[0]); + } + } + + if (!dedicatedMemory) { + GLint nvGpuMemory { 0 }; + glGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &nvGpuMemory); + if (GL_NO_ERROR == glGetError()) { + dedicatedMemory = KB_TO_BYTES(nvGpuMemory); + } + } + + if (!dedicatedMemory) { + auto gpuIdent = GPUIdent::getInstance(); + if (gpuIdent && gpuIdent->isValid()) { + dedicatedMemory = MB_TO_BYTES(gpuIdent->getMemory()); + } + } + }); + + return dedicatedMemory; +} + + + + +ComparisonFunction comparisonFuncFromGL(GLenum func) { + if (func == GL_NEVER) { + return NEVER; + } else if (func == GL_LESS) { + return LESS; + } else if (func == GL_EQUAL) { + return EQUAL; + } else if (func == GL_LEQUAL) { + return LESS_EQUAL; + } else if (func == GL_GREATER) { + return GREATER; + } else if (func == GL_NOTEQUAL) { + return NOT_EQUAL; + } else if (func == GL_GEQUAL) { + return GREATER_EQUAL; + } else if (func == GL_ALWAYS) { + return ALWAYS; + } + + return ALWAYS; +} + +State::StencilOp stencilOpFromGL(GLenum stencilOp) { + if (stencilOp == GL_KEEP) { + return State::STENCIL_OP_KEEP; + } else if (stencilOp == GL_ZERO) { + return State::STENCIL_OP_ZERO; + } else if (stencilOp == GL_REPLACE) { + return State::STENCIL_OP_REPLACE; + } else if (stencilOp == GL_INCR_WRAP) { + return State::STENCIL_OP_INCR_SAT; + } else if (stencilOp == GL_DECR_WRAP) { + return State::STENCIL_OP_DECR_SAT; + } else if (stencilOp == GL_INVERT) { + return State::STENCIL_OP_INVERT; + } else if (stencilOp == GL_INCR) { + return State::STENCIL_OP_INCR; + } else if (stencilOp == GL_DECR) { + return State::STENCIL_OP_DECR; + } + + return State::STENCIL_OP_KEEP; +} + +State::BlendOp blendOpFromGL(GLenum blendOp) { + if (blendOp == GL_FUNC_ADD) { + return State::BLEND_OP_ADD; + } else if (blendOp == GL_FUNC_SUBTRACT) { + return State::BLEND_OP_SUBTRACT; + } else if (blendOp == GL_FUNC_REVERSE_SUBTRACT) { + return State::BLEND_OP_REV_SUBTRACT; + } else if (blendOp == GL_MIN) { + return State::BLEND_OP_MIN; + } else if (blendOp == GL_MAX) { + return State::BLEND_OP_MAX; + } + + return State::BLEND_OP_ADD; +} + +State::BlendArg blendArgFromGL(GLenum blendArg) { + if (blendArg == GL_ZERO) { + return State::ZERO; + } else if (blendArg == GL_ONE) { + return State::ONE; + } else if (blendArg == GL_SRC_COLOR) { + return State::SRC_COLOR; + } else if (blendArg == GL_ONE_MINUS_SRC_COLOR) { + return State::INV_SRC_COLOR; + } else if (blendArg == GL_DST_COLOR) { + return State::DEST_COLOR; + } else if (blendArg == GL_ONE_MINUS_DST_COLOR) { + return State::INV_DEST_COLOR; + } else if (blendArg == GL_SRC_ALPHA) { + return State::SRC_ALPHA; + } else if (blendArg == GL_ONE_MINUS_SRC_ALPHA) { + return State::INV_SRC_ALPHA; + } else if (blendArg == GL_DST_ALPHA) { + return State::DEST_ALPHA; + } else if (blendArg == GL_ONE_MINUS_DST_ALPHA) { + return State::INV_DEST_ALPHA; + } else if (blendArg == GL_CONSTANT_COLOR) { + return State::FACTOR_COLOR; + } else if (blendArg == GL_ONE_MINUS_CONSTANT_COLOR) { + return State::INV_FACTOR_COLOR; + } else if (blendArg == GL_CONSTANT_ALPHA) { + return State::FACTOR_ALPHA; + } else if (blendArg == GL_ONE_MINUS_CONSTANT_ALPHA) { + return State::INV_FACTOR_ALPHA; + } + + return State::ONE; +} + +void getCurrentGLState(State::Data& state) { + { + GLint modes[2]; + glGetIntegerv(GL_POLYGON_MODE, modes); + if (modes[0] == GL_FILL) { + state.fillMode = State::FILL_FACE; + } else { + if (modes[0] == GL_LINE) { + state.fillMode = State::FILL_LINE; + } else { + state.fillMode = State::FILL_POINT; + } + } + } + { + if (glIsEnabled(GL_CULL_FACE)) { + GLint mode; + glGetIntegerv(GL_CULL_FACE_MODE, &mode); + state.cullMode = (mode == GL_FRONT ? State::CULL_FRONT : State::CULL_BACK); + } else { + state.cullMode = State::CULL_NONE; + } + } + { + GLint winding; + glGetIntegerv(GL_FRONT_FACE, &winding); + state.frontFaceClockwise = (winding == GL_CW); + state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP); + state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST); + state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE); + state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH); + } + { + if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) { + glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &state.depthBiasSlopeScale); + glGetFloatv(GL_POLYGON_OFFSET_UNITS, &state.depthBias); + } + } + { + GLboolean isEnabled = glIsEnabled(GL_DEPTH_TEST); + GLboolean writeMask; + glGetBooleanv(GL_DEPTH_WRITEMASK, &writeMask); + GLint func; + glGetIntegerv(GL_DEPTH_FUNC, &func); + + state.depthTest = State::DepthTest(isEnabled, writeMask, comparisonFuncFromGL(func)); + } + { + GLboolean isEnabled = glIsEnabled(GL_STENCIL_TEST); + + GLint frontWriteMask; + GLint frontReadMask; + GLint frontRef; + GLint frontFail; + GLint frontDepthFail; + GLint frontPass; + GLint frontFunc; + glGetIntegerv(GL_STENCIL_WRITEMASK, &frontWriteMask); + glGetIntegerv(GL_STENCIL_VALUE_MASK, &frontReadMask); + glGetIntegerv(GL_STENCIL_REF, &frontRef); + glGetIntegerv(GL_STENCIL_FAIL, &frontFail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &frontDepthFail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &frontPass); + glGetIntegerv(GL_STENCIL_FUNC, &frontFunc); + + GLint backWriteMask; + GLint backReadMask; + GLint backRef; + GLint backFail; + GLint backDepthFail; + GLint backPass; + GLint backFunc; + glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &backWriteMask); + glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &backReadMask); + glGetIntegerv(GL_STENCIL_BACK_REF, &backRef); + glGetIntegerv(GL_STENCIL_BACK_FAIL, &backFail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &backDepthFail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &backPass); + glGetIntegerv(GL_STENCIL_BACK_FUNC, &backFunc); + + state.stencilActivation = State::StencilActivation(isEnabled, frontWriteMask, backWriteMask); + state.stencilTestFront = State::StencilTest(frontRef, frontReadMask, comparisonFuncFromGL(frontFunc), stencilOpFromGL(frontFail), stencilOpFromGL(frontDepthFail), stencilOpFromGL(frontPass)); + state.stencilTestBack = State::StencilTest(backRef, backReadMask, comparisonFuncFromGL(backFunc), stencilOpFromGL(backFail), stencilOpFromGL(backDepthFail), stencilOpFromGL(backPass)); + } + { + GLint mask = 0xFFFFFFFF; + if (glIsEnabled(GL_SAMPLE_MASK)) { + glGetIntegerv(GL_SAMPLE_MASK, &mask); + state.sampleMask = mask; + } + state.sampleMask = mask; + } + { + state.alphaToCoverageEnable = glIsEnabled(GL_SAMPLE_ALPHA_TO_COVERAGE); + } + { + GLboolean isEnabled = glIsEnabled(GL_BLEND); + GLint srcRGB; + GLint srcA; + GLint dstRGB; + GLint dstA; + glGetIntegerv(GL_BLEND_SRC_RGB, &srcRGB); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &srcA); + glGetIntegerv(GL_BLEND_DST_RGB, &dstRGB); + glGetIntegerv(GL_BLEND_DST_ALPHA, &dstA); + + GLint opRGB; + GLint opA; + glGetIntegerv(GL_BLEND_EQUATION_RGB, &opRGB); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &opA); + + state.blendFunction = State::BlendFunction(isEnabled, + blendArgFromGL(srcRGB), blendOpFromGL(opRGB), blendArgFromGL(dstRGB), + blendArgFromGL(srcA), blendOpFromGL(opA), blendArgFromGL(dstA)); + } + { + GLboolean mask[4]; + glGetBooleanv(GL_COLOR_WRITEMASK, mask); + state.colorWriteMask = (mask[0] ? State::WRITE_RED : 0) + | (mask[1] ? State::WRITE_GREEN : 0) + | (mask[2] ? State::WRITE_BLUE : 0) + | (mask[3] ? State::WRITE_ALPHA : 0); + } + + (void)CHECK_GL_ERROR(); +} + + +class ElementResource { +public: + gpu::Element _element; + uint16 _resource; + + ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} +}; + +ElementResource getFormatFromGLUniform(GLenum gltype) { + switch (gltype) { + case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + /* + case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + */ + case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); + case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); + + case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); +#if defined(Q_OS_WIN) + case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); + case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); +#endif + + case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); + case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); + + + case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); + case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); + + /* {GL_FLOAT_MAT2x3 mat2x3}, + {GL_FLOAT_MAT2x4 mat2x4}, + {GL_FLOAT_MAT3x2 mat3x2}, + {GL_FLOAT_MAT3x4 mat3x4}, + {GL_FLOAT_MAT4x2 mat4x2}, + {GL_FLOAT_MAT4x3 mat4x3}, + {GL_DOUBLE_MAT2 dmat2}, + {GL_DOUBLE_MAT3 dmat3}, + {GL_DOUBLE_MAT4 dmat4}, + {GL_DOUBLE_MAT2x3 dmat2x3}, + {GL_DOUBLE_MAT2x4 dmat2x4}, + {GL_DOUBLE_MAT3x2 dmat3x2}, + {GL_DOUBLE_MAT3x4 dmat3x4}, + {GL_DOUBLE_MAT4x2 dmat4x2}, + {GL_DOUBLE_MAT4x3 dmat4x3}, + */ + + case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); + case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); + + case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); + case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); + +#if defined(Q_OS_WIN) + case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); +#endif + + case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); +#if defined(Q_OS_WIN) + case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); + + case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); +#endif + + // {GL_SAMPLER_1D_SHADOW sampler1DShadow}, + // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, + + // {GL_SAMPLER_BUFFER samplerBuffer}, + // {GL_SAMPLER_2D_RECT sampler2DRect}, + // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, + +#if defined(Q_OS_WIN) + case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); + case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); + case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); + + // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, + // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, + + case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); + case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); + case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); + case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); + + case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); + case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); +#endif + // {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, + // {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, + /* + {GL_IMAGE_1D image1D}, + {GL_IMAGE_2D image2D}, + {GL_IMAGE_3D image3D}, + {GL_IMAGE_2D_RECT image2DRect}, + {GL_IMAGE_CUBE imageCube}, + {GL_IMAGE_BUFFER imageBuffer}, + {GL_IMAGE_1D_ARRAY image1DArray}, + {GL_IMAGE_2D_ARRAY image2DArray}, + {GL_IMAGE_2D_MULTISAMPLE image2DMS}, + {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, + {GL_INT_IMAGE_1D iimage1D}, + {GL_INT_IMAGE_2D iimage2D}, + {GL_INT_IMAGE_3D iimage3D}, + {GL_INT_IMAGE_2D_RECT iimage2DRect}, + {GL_INT_IMAGE_CUBE iimageCube}, + {GL_INT_IMAGE_BUFFER iimageBuffer}, + {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, + {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, + {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, + {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, + {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, + {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, + {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, + {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, + {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot + + {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, + {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, + {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, + {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} + */ + default: + return ElementResource(Element(), Resource::BUFFER); + } + +}; + +int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { + GLint uniformsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); + + for (int i = 0; i < uniformsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + GLint location = glGetUniformLocation(glprogram, name); + const GLint INVALID_UNIFORM_LOCATION = -1; + + // Try to make sense of the gltype + auto elementResource = getFormatFromGLUniform(type); + + // The uniform as a standard var type + if (location != INVALID_UNIFORM_LOCATION) { + // Let's make sure the name doesn't contains an array element + std::string sname(name); + auto foundBracket = sname.find_first_of('['); + if (foundBracket != std::string::npos) { + // std::string arrayname = sname.substr(0, foundBracket); + + if (sname[foundBracket + 1] == '0') { + sname = sname.substr(0, foundBracket); + } else { + // skip this uniform since it's not the first element of an array + continue; + } + } + + if (elementResource._resource == Resource::BUFFER) { + uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); + } else { + // For texture/Sampler, the location is the actual binding value + GLint binding = -1; + glGetUniformiv(glprogram, location, &binding); + + auto requestedBinding = slotBindings.find(std::string(sname)); + if (requestedBinding != slotBindings.end()) { + if (binding != (*requestedBinding)._location) { + binding = (*requestedBinding)._location; + glProgramUniform1i(glprogram, location, binding); + } + } + + textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); + } + } + } + + return uniformsCount; +} + +const GLint UNUSED_SLOT = -1; +bool isUnusedSlot(GLint binding) { + return (binding == UNUSED_SLOT); +} + +int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { + GLint buffersCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); + + // fast exit + if (buffersCount == 0) { + return 0; + } + + GLint maxNumUniformBufferSlots = 0; + glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); + std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); + + for (int i = 0; i < buffersCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLint binding = -1; + + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); + glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, name); + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_BINDING, &binding); + glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); + + GLuint blockIndex = glGetUniformBlockIndex(glprogram, name); + + // CHeck if there is a requested binding for this block + auto requestedBinding = slotBindings.find(std::string(name)); + if (requestedBinding != slotBindings.end()) { + // If yes force it + if (binding != (*requestedBinding)._location) { + binding = (*requestedBinding)._location; + glUniformBlockBinding(glprogram, blockIndex, binding); + } + } else if (binding == 0) { + // If no binding was assigned then just do it finding a free slot + auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), isUnusedSlot); + if (slotIt != uniformBufferSlotMap.end()) { + binding = slotIt - uniformBufferSlotMap.begin(); + glUniformBlockBinding(glprogram, blockIndex, binding); + } else { + // This should neve happen, an active ubo cannot find an available slot among the max available?! + binding = -1; + } + } + // If binding is valid record it + if (binding >= 0) { + uniformBufferSlotMap[binding] = blockIndex; + } + + Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); + buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER)); + } + return buffersCount; +} + +int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { + GLint inputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + GLint binding = glGetAttribLocation(glprogram, name); + + auto elementResource = getFormatFromGLUniform(type); + inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); + } + + return inputsCount; +} + +int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { + /* GLint outputsCount = 0; + + glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); + + for (int i = 0; i < inputsCount; i++) { + const GLint NAME_LENGTH = 256; + GLchar name[NAME_LENGTH]; + GLint length = 0; + GLint size = 0; + GLenum type = 0; + glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); + + auto element = getFormatFromGLUniform(type); + outputs.insert(Shader::Slot(name, i, element)); + } + */ + return 0; //inputsCount; +} + + bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject) { if (shaderSource.empty()) { qCDebug(gpugllogging) << "GLShader::compileShader - no GLSL shader source code ? so failed to create"; @@ -206,7 +820,7 @@ GLuint compileProgram(const std::vector& glshaders) { } -void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) { +void makeProgramBindings(ShaderObject& shaderObject) { if (!shaderObject.glprogram) { return; } @@ -294,475 +908,9 @@ void makeProgramBindings(GLBackend::GLShader::ShaderObject& shaderObject) { (void)CHECK_GL_ERROR(); } -GLBackend::GLShader* compileBackendShader(const Shader& shader) { - // Any GLSLprogram ? normally yes... - const std::string& shaderSource = shader.getSource().getCode(); - // GLSL version - const std::string glslVersion = { - "#version 410 core" - }; +} } - // Shader domain - const int NUM_SHADER_DOMAINS = 2; - const GLenum SHADER_DOMAINS[NUM_SHADER_DOMAINS] = { - GL_VERTEX_SHADER, - GL_FRAGMENT_SHADER - }; - GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; +using namespace gpu; - // Domain specific defines - const std::string domainDefines[NUM_SHADER_DOMAINS] = { - "#define GPU_VERTEX_SHADER", - "#define GPU_PIXEL_SHADER" - }; - - // Versions specific of the shader - const std::string versionDefines[GLBackend::GLShader::NumVersions] = { - "" - }; - - GLBackend::GLShader::ShaderObjects shaderObjects; - - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& shaderObject = shaderObjects[version]; - - std::string shaderDefines = glslVersion + "\n" + domainDefines[shader.getType()] + "\n" + versionDefines[version]; - - bool result = compileShader(shaderDomain, shaderSource, shaderDefines, shaderObject.glshader, shaderObject.glprogram); - if (!result) { - return nullptr; - } - } - - // So far so good, the shader is created successfully - GLBackend::GLShader* object = new GLBackend::GLShader(); - object->_shaderObjects = shaderObjects; - - return object; -} - -GLBackend::GLShader* compileBackendProgram(const Shader& program) { - if (!program.isProgram()) { - return nullptr; - } - - GLBackend::GLShader::ShaderObjects programObjects; - - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& programObject = programObjects[version]; - - // Let's go through every shaders and make sure they are ready to go - std::vector< GLuint > shaderGLObjects; - for (auto subShader : program.getShaders()) { - auto object = GLBackend::syncGPUObject(*subShader); - if (object) { - shaderGLObjects.push_back(object->_shaderObjects[version].glshader); - } else { - qCDebug(gpugllogging) << "GLShader::compileBackendProgram - One of the shaders of the program is not compiled?"; - return nullptr; - } - } - - GLuint glprogram = compileProgram(shaderGLObjects); - if (glprogram == 0) { - return nullptr; - } - - programObject.glprogram = glprogram; - - makeProgramBindings(programObject); - } - - // So far so good, the program versions have all been created successfully - GLBackend::GLShader* object = new GLBackend::GLShader(); - object->_shaderObjects = programObjects; - - return object; -} - -GLBackend::GLShader* GLBackend::syncGPUObject(const Shader& shader) { - GLShader* object = Backend::getGPUObject(shader); - - // If GPU object already created then good - if (object) { - return object; - } - // need to have a gpu object? - if (shader.isProgram()) { - GLShader* tempObject = compileBackendProgram(shader); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } else if (shader.isDomain()) { - GLShader* tempObject = compileBackendShader(shader); - if (tempObject) { - object = tempObject; - Backend::setGPUObject(shader, object); - } - } - - return object; -} - -class ElementResource { -public: - gpu::Element _element; - uint16 _resource; - - ElementResource(Element&& elem, uint16 resource) : _element(elem), _resource(resource) {} -}; - -ElementResource getFormatFromGLUniform(GLenum gltype) { - switch (gltype) { - case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -/* - case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER); -*/ - case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER); - case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER); - - case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER); -#if defined(Q_OS_WIN) - case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER); - case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER); -#endif - - case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER); - case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER); - - - case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER); - case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER); - -/* {GL_FLOAT_MAT2x3 mat2x3}, - {GL_FLOAT_MAT2x4 mat2x4}, - {GL_FLOAT_MAT3x2 mat3x2}, - {GL_FLOAT_MAT3x4 mat3x4}, - {GL_FLOAT_MAT4x2 mat4x2}, - {GL_FLOAT_MAT4x3 mat4x3}, - {GL_DOUBLE_MAT2 dmat2}, - {GL_DOUBLE_MAT3 dmat3}, - {GL_DOUBLE_MAT4 dmat4}, - {GL_DOUBLE_MAT2x3 dmat2x3}, - {GL_DOUBLE_MAT2x4 dmat2x4}, - {GL_DOUBLE_MAT3x2 dmat3x2}, - {GL_DOUBLE_MAT3x4 dmat3x4}, - {GL_DOUBLE_MAT4x2 dmat4x2}, - {GL_DOUBLE_MAT4x3 dmat4x3}, - */ - - case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D); - case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D); - - case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D); - case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE); - -#if defined(Q_OS_WIN) - case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif - - case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D); -#if defined(Q_OS_WIN) - case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE); - - case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY); -#endif - -// {GL_SAMPLER_1D_SHADOW sampler1DShadow}, - // {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow}, - -// {GL_SAMPLER_BUFFER samplerBuffer}, -// {GL_SAMPLER_2D_RECT sampler2DRect}, - // {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow}, - -#if defined(Q_OS_WIN) - case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D); - case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D); - case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); - - // {GL_INT_SAMPLER_BUFFER isamplerBuffer}, - // {GL_INT_SAMPLER_2D_RECT isampler2DRect}, - - case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D); - case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D); - case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D); - case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE); - - case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY); - case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY); -#endif -// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer}, -// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect}, -/* - {GL_IMAGE_1D image1D}, - {GL_IMAGE_2D image2D}, - {GL_IMAGE_3D image3D}, - {GL_IMAGE_2D_RECT image2DRect}, - {GL_IMAGE_CUBE imageCube}, - {GL_IMAGE_BUFFER imageBuffer}, - {GL_IMAGE_1D_ARRAY image1DArray}, - {GL_IMAGE_2D_ARRAY image2DArray}, - {GL_IMAGE_2D_MULTISAMPLE image2DMS}, - {GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray}, - {GL_INT_IMAGE_1D iimage1D}, - {GL_INT_IMAGE_2D iimage2D}, - {GL_INT_IMAGE_3D iimage3D}, - {GL_INT_IMAGE_2D_RECT iimage2DRect}, - {GL_INT_IMAGE_CUBE iimageCube}, - {GL_INT_IMAGE_BUFFER iimageBuffer}, - {GL_INT_IMAGE_1D_ARRAY iimage1DArray}, - {GL_INT_IMAGE_2D_ARRAY iimage2DArray}, - {GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS}, - {GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray}, - {GL_UNSIGNED_INT_IMAGE_1D uimage1D}, - {GL_UNSIGNED_INT_IMAGE_2D uimage2D}, - {GL_UNSIGNED_INT_IMAGE_3D uimage3D}, - {GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect}, - {GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot - - {GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer}, - {GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS}, - {GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray}, - {GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint} -*/ - default: - return ElementResource(Element(), Resource::BUFFER); - } - -}; - - -int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, - Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers) { - GLint uniformsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORMS, &uniformsCount); - - for (int i = 0; i < uniformsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveUniform(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - GLint location = glGetUniformLocation(glprogram, name); - const GLint INVALID_UNIFORM_LOCATION = -1; - - // Try to make sense of the gltype - auto elementResource = getFormatFromGLUniform(type); - - // The uniform as a standard var type - if (location != INVALID_UNIFORM_LOCATION) { - // Let's make sure the name doesn't contains an array element - std::string sname(name); - auto foundBracket = sname.find_first_of('['); - if (foundBracket != std::string::npos) { - // std::string arrayname = sname.substr(0, foundBracket); - - if (sname[foundBracket + 1] == '0') { - sname = sname.substr(0, foundBracket); - } else { - // skip this uniform since it's not the first element of an array - continue; - } - } - - if (elementResource._resource == Resource::BUFFER) { - uniforms.insert(Shader::Slot(sname, location, elementResource._element, elementResource._resource)); - } else { - // For texture/Sampler, the location is the actual binding value - GLint binding = -1; - glGetUniformiv(glprogram, location, &binding); - - auto requestedBinding = slotBindings.find(std::string(sname)); - if (requestedBinding != slotBindings.end()) { - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glProgramUniform1i(glprogram, location, binding); - } - } - - textures.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - samplers.insert(Shader::Slot(name, binding, elementResource._element, elementResource._resource)); - } - } - } - - return uniformsCount; -} - -const GLint UNUSED_SLOT = -1; -bool isUnusedSlot(GLint binding) { - return (binding == UNUSED_SLOT); -} - -int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers) { - GLint buffersCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_UNIFORM_BLOCKS, &buffersCount); - - // fast exit - if (buffersCount == 0) { - return 0; - } - - GLint maxNumUniformBufferSlots = 0; - glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxNumUniformBufferSlots); - std::vector uniformBufferSlotMap(maxNumUniformBufferSlots, -1); - - for (int i = 0; i < buffersCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLint binding = -1; - - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_NAME_LENGTH, &length); - glGetActiveUniformBlockName(glprogram, i, NAME_LENGTH, &length, name); - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_BINDING, &binding); - glGetActiveUniformBlockiv(glprogram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - - GLuint blockIndex = glGetUniformBlockIndex(glprogram, name); - - // CHeck if there is a requested binding for this block - auto requestedBinding = slotBindings.find(std::string(name)); - if (requestedBinding != slotBindings.end()) { - // If yes force it - if (binding != (*requestedBinding)._location) { - binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, blockIndex, binding); - } - } else if (binding == 0) { - // If no binding was assigned then just do it finding a free slot - auto slotIt = std::find_if(uniformBufferSlotMap.begin(), uniformBufferSlotMap.end(), isUnusedSlot); - if (slotIt != uniformBufferSlotMap.end()) { - binding = slotIt - uniformBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, blockIndex, binding); - } else { - // This should neve happen, an active ubo cannot find an available slot among the max available?! - binding = -1; - } - } - // If binding is valid record it - if (binding >= 0) { - uniformBufferSlotMap[binding] = blockIndex; - } - - Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER); - buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER)); - } - return buffersCount; -} - -int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs) { - GLint inputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_ATTRIBUTES, &inputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - GLint binding = glGetAttribLocation(glprogram, name); - - auto elementResource = getFormatFromGLUniform(type); - inputs.insert(Shader::Slot(name, binding, elementResource._element, -1)); - } - - return inputsCount; -} - -int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs) { - /* GLint outputsCount = 0; - - glGetProgramiv(glprogram, GL_ACTIVE_, &outputsCount); - - for (int i = 0; i < inputsCount; i++) { - const GLint NAME_LENGTH = 256; - GLchar name[NAME_LENGTH]; - GLint length = 0; - GLint size = 0; - GLenum type = 0; - glGetActiveAttrib(glprogram, i, NAME_LENGTH, &length, &size, &type, name); - - auto element = getFormatFromGLUniform(type); - outputs.insert(Shader::Slot(name, i, element)); - } - */ - return 0; //inputsCount; -} - -bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings) { - - // First make sure the Shader has been compiled - GLShader* object = GLBackend::syncGPUObject(shader); - if (!object) { - return false; - } - - // Apply bindings to all program versions and generate list of slots from default version - for (int version = 0; version < GLBackend::GLShader::NumVersions; version++) { - auto& shaderObject = object->_shaderObjects[version]; - if (shaderObject.glprogram) { - Shader::SlotSet buffers; - makeUniformBlockSlots(shaderObject.glprogram, slotBindings, buffers); - - Shader::SlotSet uniforms; - Shader::SlotSet textures; - Shader::SlotSet samplers; - makeUniformSlots(shaderObject.glprogram, slotBindings, uniforms, textures, samplers); - - Shader::SlotSet inputs; - makeInputSlots(shaderObject.glprogram, slotBindings, inputs); - - Shader::SlotSet outputs; - makeOutputSlots(shaderObject.glprogram, slotBindings, outputs); - - // Define the public slots only from the default version - if (version == 0) { - shader.defineSlots(uniforms, buffers, textures, samplers, inputs, outputs); - } else { - GLShader::UniformMapping mapping; - for (auto srcUniform : shader.getUniforms()) { - mapping[srcUniform._location] = uniforms.findLocation(srcUniform._name); - } - object->_uniformMappings.push_back(mapping); - } - } - } - - - return true; -} diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h new file mode 100644 index 0000000000..5d90badc1c --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -0,0 +1,158 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_GLShared_h +#define hifi_gpu_GLShared_h + +#include +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(gpugllogging) + +namespace gpu { namespace gl { + +gpu::Size getDedicatedMemory(); +ComparisonFunction comparisonFuncFromGL(GLenum func); +State::StencilOp stencilOpFromGL(GLenum stencilOp); +State::BlendOp blendOpFromGL(GLenum blendOp); +State::BlendArg blendArgFromGL(GLenum blendArg); +void getCurrentGLState(State::Data& state); + +struct ShaderObject { + GLuint glshader { 0 }; + GLuint glprogram { 0 }; + GLint transformCameraSlot { -1 }; + GLint transformObjectSlot { -1 }; +}; + +int makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, + Shader::SlotSet& uniforms, Shader::SlotSet& textures, Shader::SlotSet& samplers); +int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& buffers); +int makeInputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& inputs); +int makeOutputSlots(GLuint glprogram, const Shader::BindingSet& slotBindings, Shader::SlotSet& outputs); +bool compileShader(GLenum shaderDomain, const std::string& shaderSource, const std::string& defines, GLuint &shaderObject, GLuint &programObject); +GLuint compileProgram(const std::vector& glshaders); +void makeProgramBindings(ShaderObject& shaderObject); + +enum GLSyncState { + // The object is currently undergoing no processing, although it's content + // may be out of date, or it's storage may be invalid relative to the + // owning GPU object + Idle, + // The object has been queued for transfer to the GPU + Pending, + // The object has been transferred to the GPU, but is awaiting + // any post transfer operations that may need to occur on the + // primary rendering thread + Transferred, +}; + +static const GLenum BLEND_OPS_TO_GL[State::NUM_BLEND_OPS] = { + GL_FUNC_ADD, + GL_FUNC_SUBTRACT, + GL_FUNC_REVERSE_SUBTRACT, + GL_MIN, + GL_MAX +}; + +static const GLenum BLEND_ARGS_TO_GL[State::NUM_BLEND_ARGS] = { + GL_ZERO, + GL_ONE, + GL_SRC_COLOR, + GL_ONE_MINUS_SRC_COLOR, + GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA, + GL_DST_ALPHA, + GL_ONE_MINUS_DST_ALPHA, + GL_DST_COLOR, + GL_ONE_MINUS_DST_COLOR, + GL_SRC_ALPHA_SATURATE, + GL_CONSTANT_COLOR, + GL_ONE_MINUS_CONSTANT_COLOR, + GL_CONSTANT_ALPHA, + GL_ONE_MINUS_CONSTANT_ALPHA, +}; + +static const GLenum COMPARISON_TO_GL[gpu::NUM_COMPARISON_FUNCS] = { + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + +static const GLenum PRIMITIVE_TO_GL[gpu::NUM_PRIMITIVES] = { + GL_POINTS, + GL_LINES, + GL_LINE_STRIP, + GL_TRIANGLES, + GL_TRIANGLE_STRIP, + GL_TRIANGLE_FAN, +}; + +static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { + GL_FLOAT, + GL_INT, + GL_UNSIGNED_INT, + GL_HALF_FLOAT, + GL_SHORT, + GL_UNSIGNED_SHORT, + GL_BYTE, + GL_UNSIGNED_BYTE, + // Normalized values + GL_INT, + GL_UNSIGNED_INT, + GL_SHORT, + GL_UNSIGNED_SHORT, + GL_BYTE, + GL_UNSIGNED_BYTE +}; + +bool checkGLError(const char* name = nullptr); +bool checkGLErrorDebug(const char* name = nullptr); + +template +struct GLObject : public GPUObject { +public: + GLObject(const GPUType& gpuObject, GLuint id) : _gpuObject(gpuObject), _id(id) {} + + virtual ~GLObject() { } + + // Used by derived classes and helpers to ensure the actual GL object exceeds the lifetime of `this` + GLuint takeOwnership() { + GLuint result = _id; + const_cast(_id) = 0; + return result; + } + + const GPUType& _gpuObject; + const GLuint _id; +}; + +class GlBuffer; +class GLFramebuffer; +class GLPipeline; +class GLQuery; +class GLState; +class GLShader; +class GLTexture; +class GLTextureTransferHelper; + +} } // namespace gpu::gl + +#define CHECK_GL_ERROR() gpu::gl::checkGLErrorDebug(__FUNCTION__) + +#endif + + + diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.cpp b/libraries/gpu-gl/src/gpu/gl/GLState.cpp new file mode 100644 index 0000000000..8cb2efa7b4 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLState.cpp @@ -0,0 +1,233 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#include "GLState.h" +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl; + +typedef GLState::Command Command; +typedef GLState::CommandPointer CommandPointer; +typedef GLState::Command1 Command1U; +typedef GLState::Command1 Command1I; +typedef GLState::Command1 Command1B; +typedef GLState::Command1 CommandDepthBias; +typedef GLState::Command1 CommandDepthTest; +typedef GLState::Command3 CommandStencil; +typedef GLState::Command1 CommandBlend; + +const GLState::Commands makeResetStateCommands(); + +// NOTE: This must stay in sync with the ordering of the State::Field enum +const GLState::Commands makeResetStateCommands() { + // Since State::DEFAULT is a static defined in another .cpp the initialisation order is random + // and we have a 50/50 chance that State::DEFAULT is not yet initialized. + // Since State::DEFAULT = State::Data() it is much easier to not use the actual State::DEFAULT + // but another State::Data object with a default initialization. + const State::Data DEFAULT = State::Data(); + + auto depthBiasCommand = std::make_shared(&GLBackend::do_setStateDepthBias, + Vec2(DEFAULT.depthBias, DEFAULT.depthBiasSlopeScale)); + auto stencilCommand = std::make_shared(&GLBackend::do_setStateStencil, DEFAULT.stencilActivation, + DEFAULT.stencilTestFront, DEFAULT.stencilTestBack); + + // The state commands to reset to default, + // WARNING depending on the order of the State::Field enum + return { + std::make_shared(&GLBackend::do_setStateFillMode, DEFAULT.fillMode), + std::make_shared(&GLBackend::do_setStateCullMode, DEFAULT.cullMode), + std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, DEFAULT.frontFaceClockwise), + std::make_shared(&GLBackend::do_setStateDepthClampEnable, DEFAULT.depthClampEnable), + std::make_shared(&GLBackend::do_setStateScissorEnable, DEFAULT.scissorEnable), + std::make_shared(&GLBackend::do_setStateMultisampleEnable, DEFAULT.multisampleEnable), + std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, DEFAULT.antialisedLineEnable), + + // Depth bias has 2 fields in State but really one call in GLBackend + CommandPointer(depthBiasCommand), + CommandPointer(depthBiasCommand), + + std::make_shared(&GLBackend::do_setStateDepthTest, DEFAULT.depthTest), + + // Depth bias has 3 fields in State but really one call in GLBackend + CommandPointer(stencilCommand), + CommandPointer(stencilCommand), + CommandPointer(stencilCommand), + + std::make_shared(&GLBackend::do_setStateSampleMask, DEFAULT.sampleMask), + + std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, DEFAULT.alphaToCoverageEnable), + + std::make_shared(&GLBackend::do_setStateBlend, DEFAULT.blendFunction), + + std::make_shared(&GLBackend::do_setStateColorWriteMask, DEFAULT.colorWriteMask) + }; +} + +const GLState::Commands GLState::_resetStateCommands = makeResetStateCommands(); + + +void generateFillMode(GLState::Commands& commands, State::FillMode fillMode) { + commands.push_back(std::make_shared(&GLBackend::do_setStateFillMode, int32(fillMode))); +} + +void generateCullMode(GLState::Commands& commands, State::CullMode cullMode) { + commands.push_back(std::make_shared(&GLBackend::do_setStateCullMode, int32(cullMode))); +} + +void generateFrontFaceClockwise(GLState::Commands& commands, bool isClockwise) { + commands.push_back(std::make_shared(&GLBackend::do_setStateFrontFaceClockwise, isClockwise)); +} + +void generateDepthClampEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthClampEnable, enable)); +} + +void generateScissorEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateScissorEnable, enable)); +} + +void generateMultisampleEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateMultisampleEnable, enable)); +} + +void generateAntialiasedLineEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateAntialiasedLineEnable, enable)); +} + +void generateDepthBias(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthBias, Vec2(state.getDepthBias(), state.getDepthBiasSlopeScale()))); +} + +void generateDepthTest(GLState::Commands& commands, const State::DepthTest& test) { + commands.push_back(std::make_shared(&GLBackend::do_setStateDepthTest, int32(test.getRaw()))); +} + +void generateStencil(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateStencil, state.getStencilActivation(), state.getStencilTestFront(), state.getStencilTestBack())); +} + +void generateAlphaToCoverageEnable(GLState::Commands& commands, bool enable) { + commands.push_back(std::make_shared(&GLBackend::do_setStateAlphaToCoverageEnable, enable)); +} + +void generateSampleMask(GLState::Commands& commands, uint32 mask) { + commands.push_back(std::make_shared(&GLBackend::do_setStateSampleMask, mask)); +} + +void generateBlend(GLState::Commands& commands, const State& state) { + commands.push_back(std::make_shared(&GLBackend::do_setStateBlend, state.getBlendFunction())); +} + +void generateColorWriteMask(GLState::Commands& commands, uint32 mask) { + commands.push_back(std::make_shared(&GLBackend::do_setStateColorWriteMask, mask)); +} + +GLState* GLState::sync(const State& state) { + GLState* object = Backend::getGPUObject(state); + + // If GPU object already created then good + if (object) { + return object; + } + + // Else allocate and create the GLState + if (!object) { + object = new GLState(); + Backend::setGPUObject(state, object); + } + + // here, we need to regenerate something so let's do it all + object->_commands.clear(); + object->_stamp = state.getStamp(); + object->_signature = state.getSignature(); + + bool depthBias = false; + bool stencilState = false; + + // go thorugh the list of state fields in the State and record the corresponding gl command + for (int i = 0; i < State::NUM_FIELDS; i++) { + if (state.getSignature()[i]) { + switch (i) { + case State::FILL_MODE: { + generateFillMode(object->_commands, state.getFillMode()); + break; + } + case State::CULL_MODE: { + generateCullMode(object->_commands, state.getCullMode()); + break; + } + case State::DEPTH_BIAS: + case State::DEPTH_BIAS_SLOPE_SCALE: { + depthBias = true; + break; + } + case State::FRONT_FACE_CLOCKWISE: { + generateFrontFaceClockwise(object->_commands, state.isFrontFaceClockwise()); + break; + } + case State::DEPTH_CLAMP_ENABLE: { + generateDepthClampEnable(object->_commands, state.isDepthClampEnable()); + break; + } + case State::SCISSOR_ENABLE: { + generateScissorEnable(object->_commands, state.isScissorEnable()); + break; + } + case State::MULTISAMPLE_ENABLE: { + generateMultisampleEnable(object->_commands, state.isMultisampleEnable()); + break; + } + case State::ANTIALISED_LINE_ENABLE: { + generateAntialiasedLineEnable(object->_commands, state.isAntialiasedLineEnable()); + break; + } + case State::DEPTH_TEST: { + generateDepthTest(object->_commands, state.getDepthTest()); + break; + } + + case State::STENCIL_ACTIVATION: + case State::STENCIL_TEST_FRONT: + case State::STENCIL_TEST_BACK: { + stencilState = true; + break; + } + + case State::SAMPLE_MASK: { + generateSampleMask(object->_commands, state.getSampleMask()); + break; + } + case State::ALPHA_TO_COVERAGE_ENABLE: { + generateAlphaToCoverageEnable(object->_commands, state.isAlphaToCoverageEnabled()); + break; + } + + case State::BLEND_FUNCTION: { + generateBlend(object->_commands, state); + break; + } + + case State::COLOR_WRITE_MASK: { + generateColorWriteMask(object->_commands, state.getColorWriteMask()); + break; + } + } + } + } + + if (depthBias) { + generateDepthBias(object->_commands, state); + } + + if (stencilState) { + generateStencil(object->_commands, state); + } + + return object; +} + diff --git a/libraries/gpu-gl/src/gpu/gl/GLState.h b/libraries/gpu-gl/src/gpu/gl/GLState.h new file mode 100644 index 0000000000..82635db893 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLState.h @@ -0,0 +1,73 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLState_h +#define hifi_gpu_gl_GLState_h + +#include "GLShared.h" + +#include + +namespace gpu { namespace gl { + +class GLBackend; +class GLState : public GPUObject { +public: + static GLState* sync(const State& state); + + class Command { + public: + virtual void run(GLBackend* backend) = 0; + Command() {} + virtual ~Command() {}; + }; + + template class Command1 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T); + void run(GLBackend* backend) { (backend->*(_func))(_param); } + Command1(GLFunction func, T param) : _func(func), _param(param) {}; + GLFunction _func; + T _param; + }; + template class Command2 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T, U); + void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1); } + Command2(GLFunction func, T param0, U param1) : _func(func), _param0(param0), _param1(param1) {}; + GLFunction _func; + T _param0; + U _param1; + }; + + template class Command3 : public Command { + public: + typedef void (GLBackend::*GLFunction)(T, U, V); + void run(GLBackend* backend) { (backend->*(_func))(_param0, _param1, _param2); } + Command3(GLFunction func, T param0, U param1, V param2) : _func(func), _param0(param0), _param1(param1), _param2(param2) {}; + GLFunction _func; + T _param0; + U _param1; + V _param2; + }; + + typedef std::shared_ptr< Command > CommandPointer; + typedef std::vector< CommandPointer > Commands; + + Commands _commands; + Stamp _stamp; + State::Signature _signature; + + // The state commands to reset to default, + static const Commands _resetStateCommands; + + friend class GLBackend; +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp similarity index 82% rename from libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp rename to libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index deb48be1ec..4bff5c87bd 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendShared.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -1,59 +1,15 @@ // -// Created by Bradley Austin Davis on 2016/05/14 +// Created by Bradley Austin Davis on 2016/05/15 // Copyright 2013-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 // -#include "GLBackendShared.h" -#include +#include "GLTexelFormat.h" -Q_DECLARE_LOGGING_CATEGORY(gpugllogging) -Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl") - -namespace gpu { namespace gl { - -bool checkGLError(const char* name) { - GLenum error = glGetError(); - if (!error) { - return false; - } else { - switch (error) { - case GL_INVALID_ENUM: - qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_INVALID_VALUE: - qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; - break; - case GL_INVALID_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; - break; - case GL_INVALID_FRAMEBUFFER_OPERATION: - qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; - break; - case GL_OUT_OF_MEMORY: - qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; - break; - case GL_STACK_UNDERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to underflow."; - break; - case GL_STACK_OVERFLOW: - qCDebug(gpugllogging) << "GLBackend" << name << ": An attempt has been made to perform an operation that would cause an internal stack to overflow."; - break; - } - return true; - } -} - -bool checkGLErrorDebug(const char* name) { -#ifdef DEBUG - return checkGLError(name); -#else - Q_UNUSED(name); - return false; -#endif -} +using namespace gpu; +using namespace gpu::gl; GLTexelFormat GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { @@ -68,7 +24,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getDimension()) { case gpu::SCALAR: { texel.format = GL_RED; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -96,7 +52,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC2: { texel.format = GL_RG; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -113,7 +69,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC3: { texel.format = GL_RGB; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -135,7 +91,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC4: { texel.format = GL_RGBA; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (srcFormat.getSemantic()) { case gpu::BGRA: @@ -205,7 +161,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E switch (dstFormat.getDimension()) { case gpu::SCALAR: { texel.format = GL_RED; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::COMPRESSED_R: { @@ -340,7 +296,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC2: { texel.format = GL_RG; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -357,7 +313,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC3: { texel.format = GL_RGB; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -382,7 +338,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::VEC4: { texel.format = GL_RGBA; - texel.type = _elementTypeToGLType[dstFormat.getType()]; + texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()]; switch (dstFormat.getSemantic()) { case gpu::RGB: @@ -468,5 +424,3 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E return texel; } } - -} } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h new file mode 100644 index 0000000000..bc3ec55066 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLTexelFormat_h +#define hifi_gpu_gl_GLTexelFormat_h + +#include "GLShared.h" + +namespace gpu { namespace gl { + +class GLTexelFormat { +public: + GLenum internalFormat; + GLenum format; + GLenum type; + + static GLTexelFormat evalGLTexelFormat(const Element& dstFormat) { + return evalGLTexelFormat(dstFormat, dstFormat); + } + static GLTexelFormat evalGLTexelFormatInternal(const Element& dstFormat); + + static GLTexelFormat evalGLTexelFormat(const Element& dstFormat, const Element& srcFormat); +}; + +} } + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp new file mode 100644 index 0000000000..47fc583d77 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -0,0 +1,292 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// + +#include "GLTexture.h" + +#include + +#include "GLTextureTransfer.h" + +using namespace gpu; +using namespace gpu::gl; + +std::shared_ptr GLTexture::_textureTransferHelper; +static std::map _textureCountByMips; +static uint16 _currentMaxMipCount { 0 }; + +// FIXME placeholder for texture memory over-use +#define DEFAULT_MAX_MEMORY_MB 256 + +const GLenum GLTexture::CUBE_FACE_LAYOUT[6] = { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +const GLenum GLTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { + GL_REPEAT, // WRAP_REPEAT, + GL_MIRRORED_REPEAT, // WRAP_MIRROR, + GL_CLAMP_TO_EDGE, // WRAP_CLAMP, + GL_CLAMP_TO_BORDER, // WRAP_BORDER, + GL_MIRROR_CLAMP_TO_EDGE_EXT // WRAP_MIRROR_ONCE, +}; + +const GLFilterMode GLTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { + { GL_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_POINT, + { GL_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, + { GL_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, + { GL_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR, + + { GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, + { GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, + { GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, + { GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR } //FILTER_ANISOTROPIC, +}; + +GLenum GLTexture::getGLTextureType(const Texture& texture) { + switch (texture.getType()) { + case Texture::TEX_2D: + return GL_TEXTURE_2D; + break; + + case Texture::TEX_CUBE: + return GL_TEXTURE_CUBE_MAP; + break; + + default: + qFatal("Unsupported texture type"); + } + Q_UNREACHABLE(); + return GL_TEXTURE_2D; +} + + +const std::vector& GLTexture::getFaceTargets(GLenum target) { + static std::vector cubeFaceTargets { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z + }; + static std::vector faceTargets { + GL_TEXTURE_2D + }; + switch (target) { + case GL_TEXTURE_2D: + return faceTargets; + case GL_TEXTURE_CUBE_MAP: + return cubeFaceTargets; + default: + Q_UNREACHABLE(); + break; + } + Q_UNREACHABLE(); + return faceTargets; +} + +float GLTexture::getMemoryPressure() { + // Check for an explicit memory limit + auto availableTextureMemory = Texture::getAllowedGPUMemoryUsage(); + + // If no memory limit has been set, use a percentage of the total dedicated memory + if (!availableTextureMemory) { + auto totalGpuMemory = gpu::gl::getDedicatedMemory(); + + // If no limit has been explicitly set, and the dedicated memory can't be determined, + // just use a fallback fixed value of 256 MB + if (!totalGpuMemory) { + totalGpuMemory = MB_TO_BYTES(DEFAULT_MAX_MEMORY_MB); + } + + // Allow 75% of all available GPU memory to be consumed by textures + // FIXME overly conservative? + availableTextureMemory = (totalGpuMemory >> 2) * 3; + } + + // Return the consumed texture memory divided by the available texture memory. + auto consumedGpuMemory = Context::getTextureGPUMemoryUsage(); + return (float)consumedGpuMemory / (float)availableTextureMemory; +} + +GLTexture::DownsampleSource::DownsampleSource(GLTexture* oldTexture) : + _texture(oldTexture ? oldTexture->takeOwnership() : 0), + _minMip(oldTexture ? oldTexture->_minMip : 0), + _maxMip(oldTexture ? oldTexture->_maxMip : 0) +{ +} + +GLTexture::DownsampleSource::~DownsampleSource() { + if (_texture) { + glDeleteTextures(1, &_texture); + Backend::decrementTextureGPUCount(); + } +} + +GLTexture::GLTexture(const gpu::Texture& texture, GLuint id, GLTexture* originalTexture, bool transferrable) : + GLObject(texture, id), + _storageStamp(texture.getStamp()), + _target(getGLTextureType(texture)), + _maxMip(texture.maxMip()), + _minMip(texture.minMip()), + _virtualSize(texture.evalTotalSize()), + _transferrable(transferrable), + _downsampleSource(originalTexture) +{ + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + _currentMaxMipCount = std::max(_currentMaxMipCount, mipCount); + if (!_textureCountByMips.count(mipCount)) { + _textureCountByMips[mipCount] = 1; + } else { + ++_textureCountByMips[mipCount]; + } + } + Backend::incrementTextureGPUCount(); + Backend::updateTextureGPUVirtualMemoryUsage(0, _virtualSize); +} + + +// Create the texture and allocate storage +GLTexture::GLTexture(const Texture& texture, GLuint id, bool transferrable) : + GLTexture(texture, id, nullptr, transferrable) +{ + // FIXME, do during allocation + //Backend::updateTextureGPUMemoryUsage(0, _size); + Backend::setGPUObject(texture, this); +} + +// Create the texture and copy from the original higher resolution version +GLTexture::GLTexture(const gpu::Texture& texture, GLuint id, GLTexture* originalTexture) : + GLTexture(texture, id, originalTexture, originalTexture->_transferrable) +{ + Q_ASSERT(_minMip >= originalTexture->_minMip); + // Set the GPU object last because that implicitly destroys the originalTexture object + Backend::setGPUObject(texture, this); +} + +GLTexture::~GLTexture() { + if (_transferrable) { + uint16 mipCount = usedMipLevels(); + Q_ASSERT(_textureCountByMips.count(mipCount)); + auto& numTexturesForMipCount = _textureCountByMips[mipCount]; + --numTexturesForMipCount; + if (0 == numTexturesForMipCount) { + _textureCountByMips.erase(mipCount); + if (mipCount == _currentMaxMipCount) { + _currentMaxMipCount = _textureCountByMips.rbegin()->first; + } + } + } + + Backend::decrementTextureGPUCount(); + Backend::updateTextureGPUMemoryUsage(_size, 0); + Backend::updateTextureGPUVirtualMemoryUsage(_virtualSize, 0); +} + +void GLTexture::createTexture() { + withPreservedTexture([&] { + allocateStorage(); + (void)CHECK_GL_ERROR(); + syncSampler(); + (void)CHECK_GL_ERROR(); + }); +} + +void GLTexture::setSize(GLuint size) const { + Backend::updateTextureGPUMemoryUsage(_size, size); + const_cast(_size) = size; +} + +bool GLTexture::isInvalid() const { + return _storageStamp < _gpuObject.getStamp(); +} + +bool GLTexture::isOutdated() const { + return GLSyncState::Idle == _syncState && _contentStamp < _gpuObject.getDataStamp(); +} + +bool GLTexture::isOverMaxMemory() const { + // FIXME switch to using the max mip count used from the previous frame + if (usedMipLevels() < _currentMaxMipCount) { + return false; + } + Q_ASSERT(usedMipLevels() == _currentMaxMipCount); + + if (getMemoryPressure() < 1.0f) { + return false; + } + + return true; +} + +bool GLTexture::isReady() const { + // If we have an invalid texture, we're never ready + if (isInvalid()) { + return false; + } + + // If we're out of date, but the transfer is in progress, report ready + // as a special case + auto syncState = _syncState.load(); + + if (isOutdated()) { + return Idle != syncState; + } + + if (Idle != syncState) { + return false; + } + + return true; +} + + +// Do any post-transfer operations that might be required on the main context / rendering thread +void GLTexture::postTransfer() { + setSyncState(GLSyncState::Idle); + ++_transferCount; + + //// The public gltexture becaomes available + //_id = _privateTexture; + + _downsampleSource.reset(); + + // At this point the mip pixels have been loaded, we can notify the gpu texture to abandon it's memory + switch (_gpuObject.getType()) { + case Texture::TEX_2D: + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i)) { + _gpuObject.notifyMipFaceGPULoaded(i); + } + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + _gpuObject.notifyMipFaceGPULoaded(i, f); + } + } + } + break; + + default: + qCWarning(gpugllogging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; + break; + } +} + +void GLTexture::initTextureTransferHelper() { + _textureTransferHelper = std::make_shared(); +} diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h new file mode 100644 index 0000000000..fa09fb49f2 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -0,0 +1,192 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#ifndef hifi_gpu_gl_GLTexture_h +#define hifi_gpu_gl_GLTexture_h + +#include "GLShared.h" +#include "GLTextureTransfer.h" + +namespace gpu { namespace gl { + +struct GLFilterMode { + GLint minFilter; + GLint magFilter; +}; + +class GLTexture : public GLObject { +public: + static void initTextureTransferHelper(); + static std::shared_ptr _textureTransferHelper; + + template + static GLTextureType* sync(const TexturePointer& texturePointer, bool needTransfer) { + const Texture& texture = *texturePointer; + if (!texture.isDefined()) { + // NO texture definition yet so let's avoid thinking + return nullptr; + } + + // If the object hasn't been created, or the object definition is out of date, drop and re-create + GLTextureType* object = Backend::getGPUObject(texture); + + // Create the texture if need be (force re-creation if the storage stamp changes + // for easier use of immutable storage) + if (!object || object->isInvalid()) { + // This automatically any previous texture + object = new GLTextureType(texture, needTransfer); + if (!object->_transferrable) { + object->createTexture(); + object->_contentStamp = texture.getDataStamp(); + object->postTransfer(); + } + } + + // Object maybe doens't neet to be tranasferred after creation + if (!object->_transferrable) { + return object; + } + + // If we just did a transfer, return the object after doing post-transfer work + if (GLSyncState::Transferred == object->getSyncState()) { + object->postTransfer(); + return object; + } + + if (object->isReady()) { + // Do we need to reduce texture memory usage? + if (object->isOverMaxMemory() && texturePointer->incremementMinMip()) { + // WARNING, this code path will essentially `delete this`, + // so no dereferencing of this instance should be done past this point + object = new GLTextureType(texture, object); + _textureTransferHelper->transferTexture(texturePointer); + } + } else if (object->isOutdated()) { + // Object might be outdated, if so, start the transfer + // (outdated objects that are already in transfer will have reported 'true' for ready() + _textureTransferHelper->transferTexture(texturePointer); + } + + return object; + } + + template + static GLuint getId(const TexturePointer& texture, bool shouldSync) { + if (!texture) { + return 0; + } + GLTextureType* object { nullptr }; + if (shouldSync) { + object = sync(texture, shouldSync); + } else { + object = Backend::getGPUObject(*texture); + } + if (!object) { + return 0; + } + + GLuint result = object->_id; + + // Don't return textures that are in transfer state + if ((object->getSyncState() != GLSyncState::Idle) || + // Don't return transferrable textures that have never completed transfer + (!object->_transferrable || 0 != object->_transferCount)) { + // Will be either 0 or the original texture being downsampled. + result = object->_downsampleSource._texture; + } + + return result; + } + + ~GLTexture(); + + const GLuint& _texture { _id }; + const Stamp _storageStamp; + const GLenum _target; + const uint16 _maxMip; + const uint16 _minMip; + const GLuint _virtualSize; // theoretical size as expected + Stamp _contentStamp { 0 }; + const bool _transferrable; + Size _transferCount { 0 }; + + struct DownsampleSource { + using Pointer = std::shared_ptr; + DownsampleSource() : _texture(0), _minMip(0), _maxMip(0) {} + DownsampleSource(GLTexture* originalTexture); + ~DownsampleSource(); + void reset() const { const_cast(_texture) = 0; } + const GLuint _texture { 0 }; + const uint16 _minMip { 0 }; + const uint16 _maxMip { 0 }; + } _downsampleSource; + + GLuint size() const { return _size; } + GLSyncState getSyncState() const { return _syncState; } + + // Is the storage out of date relative to the gpu texture? + bool isInvalid() const; + + // Is the content out of date relative to the gpu texture? + bool isOutdated() const; + + // Is the texture in a state where it can be rendered with no work? + bool isReady() const; + + // Execute any post-move operations that must occur only on the main thread + void postTransfer(); + + bool isOverMaxMemory() const; + +protected: + static const size_t CUBE_NUM_FACES = 6; + static const GLenum CUBE_FACE_LAYOUT[6]; + static const GLFilterMode FILTER_MODES[Sampler::NUM_FILTERS]; + static const GLenum WRAP_MODES[Sampler::NUM_WRAP_MODES]; + + static const std::vector& getFaceTargets(GLenum textureType); + + static GLenum getGLTextureType(const Texture& texture); + // Return a floating point value indicating how much of the allowed + // texture memory we are currently consuming. A value of 0 indicates + // no texture memory usage, while a value of 1 indicates all available / allowed memory + // is consumed. A value above 1 indicates that there is a problem. + static float getMemoryPressure(); + + + const GLuint _size { 0 }; // true size as reported by the gl api + std::atomic _syncState { GLSyncState::Idle }; + + GLTexture(const Texture& texture, GLuint id, bool transferrable); + GLTexture(const Texture& texture, GLuint id, GLTexture* originalTexture); + + void setSyncState(GLSyncState syncState) { _syncState = syncState; } + uint16 usedMipLevels() const { return (_maxMip - _minMip) + 1; } + + void createTexture(); + + virtual void allocateStorage() const = 0; + virtual void updateSize() const = 0; + virtual void transfer() const = 0; + virtual void syncSampler() const = 0; + virtual void generateMips() const = 0; + virtual void withPreservedTexture(std::function f) const = 0; + +protected: + void setSize(GLuint size) const; + +private: + + GLTexture(const gpu::Texture& gpuTexture, GLuint id, GLTexture* originalTexture, bool transferrable); + + friend class GLTextureTransferHelper; + friend class GLBackend; +}; + +} } + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp similarity index 84% rename from libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp rename to libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index a9635e8307..ca2e7061f5 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -5,21 +5,18 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackendTextureTransfer.h" +#include "GLTextureTransfer.h" #ifdef THREADED_TEXTURE_TRANSFER #include #include #endif - -#include "GLBackendShared.h" +#include "GLShared.h" using namespace gpu; using namespace gpu::gl; -#include "GLBackend.h" - GLTextureTransferHelper::GLTextureTransferHelper() { #ifdef THREADED_TEXTURE_TRANSFER _canvas = QSharedPointer(new OffscreenGLCanvas(), &QObject::deleteLater); @@ -45,7 +42,7 @@ GLTextureTransferHelper::~GLTextureTransferHelper() { } void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texturePointer) { - GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); + GLTexture* object = Backend::getGPUObject(*texturePointer); Backend::incrementTextureGPUTransferCount(); #ifdef THREADED_TEXTURE_TRANSFER GLsync fence { 0 }; @@ -53,14 +50,14 @@ void GLTextureTransferHelper::transferTexture(const gpu::TexturePointer& texture //glFlush(); TextureTransferPackage package { texturePointer, fence }; - object->setSyncState(GLBackend::GLTexture::Pending); + object->setSyncState(GLSyncState::Pending); queueItem(package); #else object->withPreservedTexture([&] { do_transfer(*object); }); object->_contentStamp = texturePointer->getDataStamp(); - object->setSyncState(GLBackend::GLTexture::Transferred); + object->setSyncState(GLSyncState::Transferred); #endif } @@ -78,7 +75,7 @@ void GLTextureTransferHelper::shutdown() { #endif } -void GLTextureTransferHelper::do_transfer(GLBackend::GLTexture& texture) { +void GLTextureTransferHelper::do_transfer(GLTexture& texture) { texture.createTexture(); texture.transfer(); texture.updateSize(); @@ -99,7 +96,7 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { package.fence = 0; } - GLBackend::GLTexture* object = Backend::getGPUObject(*texturePointer); + GLTexture* object = Backend::getGPUObject(*texturePointer); do_transfer(*object); glBindTexture(object->_target, 0); @@ -108,7 +105,7 @@ bool GLTextureTransferHelper::processQueueItems(const Queue& messages) { glDeleteSync(writeSync); object->_contentStamp = texturePointer->getDataStamp(); - object->setSyncState(GLBackend::GLTexture::Transferred); + object->setSyncState(GLSyncState::Transferred); } return true; } diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h similarity index 78% rename from libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h rename to libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index f344827e53..deb470c572 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -5,12 +5,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#ifndef hifi_gpu_gl_GLTextureTransfer_h +#define hifi_gpu_gl_GLTextureTransfer_h #include -#include +#include + #include -#include "GLBackendShared.h" -#include "GLBackend.h" + +#include "GLShared.h" +#include "GLTexture.h" #ifdef Q_OS_WIN #define THREADED_TEXTURE_TRANSFER @@ -27,6 +31,7 @@ struct TextureTransferPackage { class GLTextureTransferHelper : public GenericQueueThread { public: + using Pointer = std::shared_ptr; GLTextureTransferHelper(); ~GLTextureTransferHelper(); void transferTexture(const gpu::TexturePointer& texturePointer); @@ -36,10 +41,12 @@ protected: void setup() override; void shutdown() override; bool processQueueItems(const Queue& messages) override; - void do_transfer(GLBackend::GLTexture& texturePointer); + void do_transfer(GLTexture& texturePointer); private: QSharedPointer _canvas; }; } } + +#endif \ No newline at end of file diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp new file mode 100644 index 0000000000..e46c739593 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp @@ -0,0 +1,175 @@ +// +// Created by Sam Gateau on 10/27/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(gpugl41logging, "hifi.gpu.gl41") + +using namespace gpu; +using namespace gpu::gl41; + +void GLBackend::do_draw(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 1]._uint; + uint32 startVertex = batch._params[paramOffset + 0]._uint; + + if (isStereo()) { + setupStereoSide(0); + glDrawArrays(mode, startVertex, numVertices); + setupStereoSide(1); + glDrawArrays(mode, startVertex, numVertices); + + _stats._DSNumTriangles += 2 * numVertices / 3; + _stats._DSNumDrawcalls += 2; + + } else { + glDrawArrays(mode, startVertex, numVertices); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { + Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numIndices = batch._params[paramOffset + 1]._uint; + uint32 startIndex = batch._params[paramOffset + 0]._uint; + + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + setupStereoSide(0); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + setupStereoSide(1); + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + + _stats._DSNumTriangles += 2 * numIndices / 3; + _stats._DSNumDrawcalls += 2; + } else { + glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + _stats._DSNumTriangles += numIndices / 3; + _stats._DSNumDrawcalls++; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; + uint32 numVertices = batch._params[paramOffset + 2]._uint; + uint32 startVertex = batch._params[paramOffset + 1]._uint; + + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + + setupStereoSide(0); + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + setupStereoSide(1); + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + + _stats._DSNumTriangles += (trueNumInstances * numVertices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + _stats._DSNumTriangles += (numInstances * numVertices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + _stats._DSNumAPIDrawcalls++; + + (void) CHECK_GL_ERROR(); +} + +void glbackend_glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices, GLsizei primcount, GLint basevertex, GLuint baseinstance) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + glDrawElementsInstancedBaseVertexBaseInstance(mode, count, type, indices, primcount, basevertex, baseinstance); +#else + glDrawElementsInstanced(mode, count, type, indices, primcount); +#endif +} + +void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { + GLint numInstances = batch._params[paramOffset + 4]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 3]._uint]; + uint32 numIndices = batch._params[paramOffset + 2]._uint; + uint32 startIndex = batch._params[paramOffset + 1]._uint; + // FIXME glDrawElementsInstancedBaseVertexBaseInstance is only available in GL 4.3 + // and higher, so currently we ignore this field + uint32 startInstance = batch._params[paramOffset + 0]._uint; + GLenum glType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + auto typeByteSize = TYPE_SIZE[_input._indexBufferType]; + GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); + + if (isStereo()) { + GLint trueNumInstances = 2 * numInstances; + + setupStereoSide(0); + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + setupStereoSide(1); + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + + _stats._DSNumTriangles += (trueNumInstances * numIndices) / 3; + _stats._DSNumDrawcalls += trueNumInstances; + } else { + glbackend_glDrawElementsInstancedBaseVertexBaseInstance(mode, numIndices, glType, indexBufferByteOffset, numInstances, 0, startInstance); + _stats._DSNumTriangles += (numInstances * numIndices) / 3; + _stats._DSNumDrawcalls += numInstances; + } + + _stats._DSNumAPIDrawcalls++; + + (void)CHECK_GL_ERROR(); +} + + +void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + + glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; + +#else + // FIXME implement the slow path +#endif + (void)CHECK_GL_ERROR(); + +} + +void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { +#if (GPU_INPUT_PROFILE == GPU_CORE_43) + uint commandCount = batch._params[paramOffset + 0]._uint; + GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + GLenum indexType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; + + glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + _stats._DSNumAPIDrawcalls++; +#else + // FIXME implement the slow path +#endif + (void)CHECK_GL_ERROR(); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackend.h b/libraries/gpu-gl/src/gpu/gl41/GLBackend.h new file mode 100644 index 0000000000..8a0f4bb912 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackend.h @@ -0,0 +1,96 @@ +// +// GLBackend.h +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 10/27/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_41_GLBackend_h +#define hifi_gpu_41_GLBackend_h + +#include + +#include "../gl/GLBackend.h" +#include "../gl/GLTexture.h" + +#define GPU_CORE_41 410 +#define GPU_CORE_43 430 + +#ifdef Q_OS_MAC +#define GPU_INPUT_PROFILE GPU_CORE_41 +#else +#define GPU_INPUT_PROFILE GPU_CORE_43 +#endif + +namespace gpu { namespace gl41 { + +class GLBackend : public gl::GLBackend { + using Parent = gl::GLBackend; + // Context Backend static interface required + friend class Context; + +public: + explicit GLBackend(bool syncCache) : Parent(syncCache) {} + GLBackend() : Parent() {} + + class GLTexture : public gpu::gl::GLTexture { + using Parent = gpu::gl::GLTexture; + GLuint allocate(); + public: + GLTexture(const Texture& buffer, bool transferrable); + GLTexture(const Texture& buffer, GLTexture* original); + + protected: + void transferMip(uint16_t mipLevel, uint8_t face = 0) const; + void allocateStorage() const override; + void updateSize() const override; + void transfer() const override; + void syncSampler() const override; + void generateMips() const override; + void withPreservedTexture(std::function f) const override; + }; + + +protected: + GLuint getFramebufferID(const FramebufferPointer& framebuffer) override; + gl::GLFramebuffer* syncGPUObject(const Framebuffer& framebuffer) override; + + GLuint getBufferID(const Buffer& buffer) override; + gl::GLBuffer* syncGPUObject(const Buffer& buffer) override; + + GLuint getTextureID(const TexturePointer& texture, bool needTransfer = true) override; + gl::GLTexture* syncGPUObject(const TexturePointer& texture, bool sync = true) override; + + GLuint getQueryID(const QueryPointer& query) override; + gl::GLQuery* syncGPUObject(const Query& query) override; + + // Draw Stage + void do_draw(Batch& batch, size_t paramOffset) override; + void do_drawIndexed(Batch& batch, size_t paramOffset) override; + void do_drawInstanced(Batch& batch, size_t paramOffset) override; + void do_drawIndexedInstanced(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndirect(Batch& batch, size_t paramOffset) override; + void do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) override; + + // Input Stage + void updateInput() override; + + // Synchronize the state cache of this Backend with the actual real state of the GL Context + void transferTransformState(const Batch& batch) const override; + void initTransform() override; + void updateTransform(const Batch& batch); + void resetTransformStage(); + + // Output stage + void do_blit(Batch& batch, size_t paramOffset) override; +}; + +} } + +Q_DECLARE_LOGGING_CATEGORY(gpugl41logging) + + +#endif diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp new file mode 100644 index 0000000000..9c6b8b8124 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp @@ -0,0 +1,62 @@ +// +// Created by Bradley Austin Davis on 2016/05/15 +// Copyright 2013-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 +// +#include "GLBackend.h" +#include "../gl/GLBuffer.h" + +using namespace gpu; +using namespace gpu::gl41; + +class GLBuffer : public gl::GLBuffer { + using Parent = gpu::gl::GLBuffer; + static GLuint allocate() { + GLuint result; + glGenBuffers(1, &result); + return result; + } + +public: + GLBuffer(const Buffer& buffer, GLBuffer* original) : Parent(buffer, allocate()) { + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + if (original) { + glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer); + glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + (void)CHECK_GL_ERROR(); + } + Backend::setGPUObject(buffer, this); + } + + void transfer() override { + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + (void)CHECK_GL_ERROR(); + Size offset; + Size size; + Size currentPage { 0 }; + auto data = _gpuObject.getSysmem().readData(); + while (_gpuObject.getNextTransferBlock(offset, size, currentPage)) { + glBufferSubData(GL_ARRAY_BUFFER, offset, size, data + offset); + (void)CHECK_GL_ERROR(); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); + (void)CHECK_GL_ERROR(); + _gpuObject._flags &= ~Buffer::DIRTY; + } +}; + +GLuint GLBackend::getBufferID(const Buffer& buffer) { + return GLBuffer::getId(buffer); +} + +gl::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { + return GLBuffer::sync(buffer); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp new file mode 100644 index 0000000000..0486d1cfd2 --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp @@ -0,0 +1,185 @@ +// +// GLBackendInput.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl41; + +// Core 41 doesn't expose the features to really separate the vertex format from the vertex buffers binding +// Core 43 does :) +// FIXME crashing problem with glVertexBindingDivisor / glVertexAttribFormat +#if 1 || (GPU_INPUT_PROFILE == GPU_CORE_41) +#define NO_SUPPORT_VERTEX_ATTRIB_FORMAT +#else +#define SUPPORT_VERTEX_ATTRIB_FORMAT +#endif + +void GLBackend::updateInput() { +#if defined(SUPPORT_VERTEX_ATTRIB_FORMAT) + if (_input._invalidFormat) { + + InputStageState::ActivationCache newActivation; + + // Assign the vertex format required + if (_input._format) { + for (auto& it : _input._format->getAttributes()) { + const Stream::Attribute& attrib = (it).second; + + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = _elementTypeToGLType[attrib._element.getType()]; + GLuint offset = attrib._offset;; + GLboolean isNormalized = attrib._element.isNormalized(); + + GLenum perLocationSize = attrib._element.getLocationSize(); + + for (size_t locNum = 0; locNum < locationCount; ++locNum) { + newActivation.set(slot + locNum); + glVertexAttribFormat(slot + locNum, count, type, isNormalized, offset + locNum * perLocationSize); + glVertexAttribBinding(slot + locNum, attrib._channel); + } + glVertexBindingDivisor(attrib._channel, attrib._frequency); + } + (void) CHECK_GL_ERROR(); + } + + // Manage Activation what was and what is expected now + for (size_t i = 0; i < newActivation.size(); i++) { + bool newState = newActivation[i]; + if (newState != _input._attributeActivation[i]) { + if (newState) { + glEnableVertexAttribArray(i); + } else { + glDisableVertexAttribArray(i); + } + _input._attributeActivation.flip(i); + } + } + (void) CHECK_GL_ERROR(); + + _input._invalidFormat = false; + _stats._ISNumFormatChanges++; + } + + if (_input._invalidBuffers.any()) { + int numBuffers = _input._buffers.size(); + auto buffer = _input._buffers.data(); + auto vbo = _input._bufferVBOs.data(); + auto offset = _input._bufferOffsets.data(); + auto stride = _input._bufferStrides.data(); + + for (int bufferNum = 0; bufferNum < numBuffers; bufferNum++) { + if (_input._invalidBuffers.test(bufferNum)) { + glBindVertexBuffer(bufferNum, (*vbo), (*offset), (*stride)); + } + buffer++; + vbo++; + offset++; + stride++; + } + _input._invalidBuffers.reset(); + (void) CHECK_GL_ERROR(); + } +#else + if (_input._invalidFormat || _input._invalidBuffers.any()) { + + if (_input._invalidFormat) { + InputStageState::ActivationCache newActivation; + + _stats._ISNumFormatChanges++; + + // Check expected activation + if (_input._format) { + for (auto& it : _input._format->getAttributes()) { + const Stream::Attribute& attrib = (it).second; + uint8_t locationCount = attrib._element.getLocationCount(); + for (int i = 0; i < locationCount; ++i) { + newActivation.set(attrib._slot + i); + } + } + } + + // Manage Activation what was and what is expected now + for (unsigned int i = 0; i < newActivation.size(); i++) { + bool newState = newActivation[i]; + if (newState != _input._attributeActivation[i]) { + + if (newState) { + glEnableVertexAttribArray(i); + } else { + glDisableVertexAttribArray(i); + } + (void) CHECK_GL_ERROR(); + + _input._attributeActivation.flip(i); + } + } + } + + // now we need to bind the buffers and assign the attrib pointers + if (_input._format) { + const Buffers& buffers = _input._buffers; + const Offsets& offsets = _input._bufferOffsets; + const Offsets& strides = _input._bufferStrides; + + const Stream::Format::AttributeMap& attributes = _input._format->getAttributes(); + auto& inputChannels = _input._format->getChannels(); + _stats._ISNumInputBufferChanges++; + + GLuint boundVBO = 0; + for (auto& channelIt : inputChannels) { + const Stream::Format::ChannelMap::value_type::second_type& channel = (channelIt).second; + if ((channelIt).first < buffers.size()) { + int bufferNum = (channelIt).first; + + if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { + // GLuint vbo = gpu::GLBackend::getBufferID((*buffers[bufferNum])); + GLuint vbo = _input._bufferVBOs[bufferNum]; + if (boundVBO != vbo) { + glBindBuffer(GL_ARRAY_BUFFER, vbo); + (void) CHECK_GL_ERROR(); + boundVBO = vbo; + } + _input._invalidBuffers[bufferNum] = false; + + for (unsigned int i = 0; i < channel._slots.size(); i++) { + const Stream::Attribute& attrib = attributes.at(channel._slots[i]); + GLuint slot = attrib._slot; + GLuint count = attrib._element.getLocationScalarCount(); + uint8_t locationCount = attrib._element.getLocationCount(); + GLenum type = gl::ELEMENT_TYPE_TO_GL[attrib._element.getType()]; + // GLenum perLocationStride = strides[bufferNum]; + GLenum perLocationStride = attrib._element.getLocationSize(); + GLuint stride = (GLuint)strides[bufferNum]; + GLuint pointer = (GLuint)(attrib._offset + offsets[bufferNum]); + GLboolean isNormalized = attrib._element.isNormalized(); + + for (size_t locNum = 0; locNum < locationCount; ++locNum) { + glVertexAttribPointer(slot + (GLuint)locNum, count, type, isNormalized, stride, + reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); + } + + // TODO: Support properly the IAttrib version + + (void) CHECK_GL_ERROR(); + } + } + } + } + } + // everything format related should be in sync now + _input._invalidFormat = false; + } +#endif +} + diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp new file mode 100644 index 0000000000..a34a0aaebd --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp @@ -0,0 +1,169 @@ +// +// GLBackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +#include + +#include "../gl/GLFramebuffer.h" +#include "../gl/GLTexture.h" + +namespace gpu { namespace gl41 { + +class GLFramebuffer : public gl::GLFramebuffer { + using Parent = gl::GLFramebuffer; + static GLuint allocate() { + GLuint result; + glGenFramebuffers(1, &result); + return result; + } +public: + void update() override { + GLint currentFBO = -1; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + gl::GLTexture* gltexture = nullptr; + TexturePointer surface; + if (_gpuObject.getColorStamps() != _colorStamps) { + if (_gpuObject.hasColor()) { + _colorBuffers.clear(); + static const GLenum colorAttachments[] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + GL_COLOR_ATTACHMENT8, + GL_COLOR_ATTACHMENT9, + GL_COLOR_ATTACHMENT10, + GL_COLOR_ATTACHMENT11, + GL_COLOR_ATTACHMENT12, + GL_COLOR_ATTACHMENT13, + GL_COLOR_ATTACHMENT14, + GL_COLOR_ATTACHMENT15 }; + + int unit = 0; + for (auto& b : _gpuObject.getRenderBuffers()) { + surface = b._texture; + if (surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } else { + gltexture = nullptr; + } + + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + _colorBuffers.push_back(colorAttachments[unit]); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); + } + unit++; + } + } + _colorStamps = _gpuObject.getColorStamps(); + } + + GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; + if (!_gpuObject.hasStencil()) { + attachement = GL_DEPTH_ATTACHMENT; + } else if (!_gpuObject.hasDepth()) { + attachement = GL_STENCIL_ATTACHMENT; + } + + if (_gpuObject.getDepthStamp() != _depthStamp) { + auto surface = _gpuObject.getDepthStencilBuffer(); + if (_gpuObject.hasDepthStencil() && surface) { + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + } + + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); + } + _depthStamp = _gpuObject.getDepthStamp(); + } + + + // Last but not least, define where we draw + if (!_colorBuffers.empty()) { + glDrawBuffers((GLsizei)_colorBuffers.size(), _colorBuffers.data()); + } else { + glDrawBuffer(GL_NONE); + } + + // Now check for completness + _status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + // restore the current framebuffer + if (currentFBO != -1) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); + } + + checkStatus(GL_DRAW_FRAMEBUFFER); + } + + +public: + GLFramebuffer(const gpu::Framebuffer& framebuffer) + : Parent(framebuffer, allocate()) { } +}; + +gl::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { + return GLFramebuffer::sync(framebuffer); +} + +GLuint GLBackend::getFramebufferID(const FramebufferPointer& framebuffer) { + return framebuffer ? GLFramebuffer::getId(*framebuffer) : 0; +} + +void GLBackend::do_blit(Batch& batch, size_t paramOffset) { + auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); + Vec4i srcvp; + for (auto i = 0; i < 4; ++i) { + srcvp[i] = batch._params[paramOffset + 1 + i]._int; + } + + auto dstframebuffer = batch._framebuffers.get(batch._params[paramOffset + 5]._uint); + Vec4i dstvp; + for (auto i = 0; i < 4; ++i) { + dstvp[i] = batch._params[paramOffset + 6 + i]._int; + } + + // Assign dest framebuffer if not bound already + auto newDrawFBO = getFramebufferID(dstframebuffer); + if (_output._drawFBO != newDrawFBO) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, newDrawFBO); + } + + // always bind the read fbo + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFramebufferID(srcframebuffer)); + + // Blit! + glBlitFramebuffer(srcvp.x, srcvp.y, srcvp.z, srcvp.w, + dstvp.x, dstvp.y, dstvp.z, dstvp.w, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // Always clean the read fbo to 0 + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + // Restore draw fbo if changed + if (_output._drawFBO != newDrawFBO) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _output._drawFBO); + } + + (void) CHECK_GL_ERROR(); +} + + +} } diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp new file mode 100644 index 0000000000..4915adbc1d --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp @@ -0,0 +1,37 @@ +// +// GLBackendQuery.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 7/7/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +#include "../gl/GLQuery.h" + +using namespace gpu; +using namespace gpu::gl41; + +class GLQuery : public gpu::gl::GLQuery { + using Parent = gpu::gl::GLBuffer; +public: + static GLuint allocateQuery() { + GLuint result; + glGenQueries(1, &result); + return result; + } + + GLQuery(const Query& query) + : gl::GLQuery(query, allocateQuery()) { } +}; + +gl::GLQuery* GLBackend::syncGPUObject(const Query& query) { + return GLQuery::sync(query); +} + +GLuint GLBackend::getQueryID(const QueryPointer& query) { + return GLQuery::getId(query); +} diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp new file mode 100644 index 0000000000..f5e8531ddc --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp @@ -0,0 +1,237 @@ +// +// GLBackendTexture.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 1/19/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +#include +#include +#include + +#include "../gl/GLTexelFormat.h" + +using namespace gpu; +using namespace gpu::gl41; + +using GLTexelFormat = gl::GLTexelFormat; +using GLTexture = GLBackend::GLTexture; + +GLuint GLTexture::allocate() { + Backend::incrementTextureGPUCount(); + GLuint result; + glGenTextures(1, &result); + return result; +} + +GLuint GLBackend::getTextureID(const TexturePointer& texture, bool transfer) { + return GLTexture::getId(texture, transfer); +} + +gl::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texture, bool transfer) { + return GLTexture::sync(texture, transfer); +} + +GLTexture::GLTexture(const Texture& texture, bool transferrable) : gl::GLTexture(texture, allocate(), transferrable) {} + +GLTexture::GLTexture(const Texture& texture, GLTexture* original) : gl::GLTexture(texture, allocate(), original) {} + +void GLBackend::GLTexture::withPreservedTexture(std::function f) const { + GLint boundTex = -1; + switch (_target) { + case GL_TEXTURE_2D: + glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTex); + break; + + case GL_TEXTURE_CUBE_MAP: + glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &boundTex); + break; + + default: + qFatal("Unsupported texture type"); + } + (void)CHECK_GL_ERROR(); + + glBindTexture(_target, _texture); + f(); + glBindTexture(_target, boundTex); + (void)CHECK_GL_ERROR(); +} + +void GLBackend::GLTexture::generateMips() const { + withPreservedTexture([&] { + glGenerateMipmap(_target); + }); + (void)CHECK_GL_ERROR(); +} + +void GLBackend::GLTexture::allocateStorage() const { + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); + (void)CHECK_GL_ERROR(); + glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, _maxMip - _minMip); + (void)CHECK_GL_ERROR(); + if (GLEW_VERSION_4_2 && !_gpuObject.getTexelFormat().isCompressed()) { + // Get the dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(_minMip); + glTexStorage2D(_target, usedMipLevels(), texelFormat.internalFormat, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } else { + for (uint16_t l = _minMip; l <= _maxMip; l++) { + // Get the mip level dimensions, accounting for the downgrade level + Vec3u dimensions = _gpuObject.evalMipDimensions(l); + for (GLenum target : getFaceTargets(_target)) { + glTexImage2D(target, l - _minMip, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, NULL); + (void)CHECK_GL_ERROR(); + } + } + } +} + +void GLBackend::GLTexture::updateSize() const { + setSize(_virtualSize); + if (!_id) { + return; + } + + if (_gpuObject.getTexelFormat().isCompressed()) { + GLenum proxyType = GL_TEXTURE_2D; + GLuint numFaces = 1; + if (_gpuObject.getType() == gpu::Texture::TEX_CUBE) { + proxyType = CUBE_FACE_LAYOUT[0]; + numFaces = (GLuint)CUBE_NUM_FACES; + } + GLint gpuSize{ 0 }; + glGetTexLevelParameteriv(proxyType, 0, GL_TEXTURE_COMPRESSED, &gpuSize); + (void)CHECK_GL_ERROR(); + + if (gpuSize) { + for (GLuint level = _minMip; level < _maxMip; level++) { + GLint levelSize{ 0 }; + glGetTexLevelParameteriv(proxyType, level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &levelSize); + levelSize *= numFaces; + + if (levelSize <= 0) { + break; + } + gpuSize += levelSize; + } + (void)CHECK_GL_ERROR(); + setSize(gpuSize); + return; + } + } +} + +// Move content bits from the CPU to the GPU for a given mip / face +void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { + auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); + gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); + //GLenum target = getFaceTargets()[face]; + GLenum target = _target == GL_TEXTURE_2D ? GL_TEXTURE_2D : CUBE_FACE_LAYOUT[face]; + auto size = _gpuObject.evalMipDimensions(mipLevel); + glTexSubImage2D(target, mipLevel, 0, 0, size.x, size.y, texelFormat.format, texelFormat.type, mip->readData()); + (void)CHECK_GL_ERROR(); +} + +// This should never happen on the main thread +// Move content bits from the CPU to the GPU +void GLBackend::GLTexture::transfer() const { + PROFILE_RANGE(__FUNCTION__); + //qDebug() << "Transferring texture: " << _privateTexture; + // Need to update the content of the GPU object from the source sysmem of the texture + if (_contentStamp >= _gpuObject.getDataStamp()) { + return; + } + + glBindTexture(_target, _id); + (void)CHECK_GL_ERROR(); + + if (_downsampleSource._texture) { + GLuint fbo { 0 }; + glGenFramebuffers(1, &fbo); + (void)CHECK_GL_ERROR(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + (void)CHECK_GL_ERROR(); + // Find the distance between the old min mip and the new one + uint16 mipOffset = _minMip - _downsampleSource._minMip; + for (uint16 i = _minMip; i <= _maxMip; ++i) { + uint16 targetMip = i - _minMip; + uint16 sourceMip = targetMip + mipOffset; + Vec3u dimensions = _gpuObject.evalMipDimensions(i); + for (GLenum target : getFaceTargets(_target)) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, _downsampleSource._texture, sourceMip); + (void)CHECK_GL_ERROR(); + glCopyTexSubImage2D(target, targetMip, 0, 0, 0, 0, dimensions.x, dimensions.y); + (void)CHECK_GL_ERROR(); + } + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + } else { + // GO through the process of allocating the correct storage and/or update the content + switch (_gpuObject.getType()) { + case Texture::TEX_2D: + { + for (uint16_t i = _minMip; i <= _maxMip; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i)) { + transferMip(i); + } + } + } + break; + + case Texture::TEX_CUBE: + // transfer pixels from each faces + for (uint8_t f = 0; f < CUBE_NUM_FACES; f++) { + for (uint16_t i = 0; i < Sampler::MAX_MIP_LEVEL; ++i) { + if (_gpuObject.isStoredMipFaceAvailable(i, f)) { + transferMip(i, f); + } + } + } + break; + + default: + qCWarning(gpugl41logging) << __FUNCTION__ << " case for Texture Type " << _gpuObject.getType() << " not supported"; + break; + } + } + if (_gpuObject.isAutogenerateMips()) { + glGenerateMipmap(_target); + (void)CHECK_GL_ERROR(); + } +} + +void GLBackend::GLTexture::syncSampler() const { + const Sampler& sampler = _gpuObject.getSampler(); + Texture::Type type = _gpuObject.getType(); + auto object = this; + + const auto& fm = FILTER_MODES[sampler.getFilter()]; + glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); + glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); + + if (sampler.doComparison()) { + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, gl::COMPARISON_TO_GL[sampler.getComparisonFunction()]); + } else { + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + + glTexParameteri(_target, GL_TEXTURE_WRAP_S, WRAP_MODES[sampler.getWrapModeU()]); + glTexParameteri(_target, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); + glTexParameteri(_target, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); + + glTexParameterfv(_target, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); + glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, (uint16)sampler.getMipOffset()); + glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); + glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); + glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); +} + diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp new file mode 100644 index 0000000000..6601eef86e --- /dev/null +++ b/libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp @@ -0,0 +1,83 @@ +// +// GLBackendTransform.cpp +// libraries/gpu/src/gpu +// +// Created by Sam Gateau on 3/8/2015. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "GLBackend.h" + +using namespace gpu; +using namespace gpu::gl41; + +void GLBackend::initTransform() { + glGenBuffers(1, &_transform._objectBuffer); + glGenBuffers(1, &_transform._cameraBuffer); + glGenBuffers(1, &_transform._drawCallInfoBuffer); + glGenTextures(1, &_transform._objectBufferTexture); + size_t cameraSize = sizeof(TransformStageState::CameraBufferElement); + while (_transform._cameraUboSize < cameraSize) { + _transform._cameraUboSize += _uboAlignment; + } +} + +void GLBackend::transferTransformState(const Batch& batch) const { + // FIXME not thread safe + static std::vector bufferData; + if (!_transform._cameras.empty()) { + bufferData.resize(_transform._cameraUboSize * _transform._cameras.size()); + for (size_t i = 0; i < _transform._cameras.size(); ++i) { + memcpy(bufferData.data() + (_transform._cameraUboSize * i), &_transform._cameras[i], sizeof(TransformStageState::CameraBufferElement)); + } + glBindBuffer(GL_UNIFORM_BUFFER, _transform._cameraBuffer); + glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + } + + if (!batch._objects.empty()) { + auto byteSize = batch._objects.size() * sizeof(Batch::TransformObject); + bufferData.resize(byteSize); + memcpy(bufferData.data(), batch._objects.data(), byteSize); + +#ifdef GPU_SSBO_DRAW_CALL_INFO + glBindBuffer(GL_SHADER_STORAGE_BUFFER, _transform._objectBuffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + glBindBuffer(GL_TEXTURE_BUFFER, _transform._objectBuffer); + glBufferData(GL_TEXTURE_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_TEXTURE_BUFFER, 0); +#endif + } + + if (!batch._namedData.empty()) { + bufferData.clear(); + for (auto& data : batch._namedData) { + auto currentSize = bufferData.size(); + auto bytesToCopy = data.second.drawCallInfos.size() * sizeof(Batch::DrawCallInfo); + bufferData.resize(currentSize + bytesToCopy); + memcpy(bufferData.data() + currentSize, data.second.drawCallInfos.data(), bytesToCopy); + _transform._drawCallInfoOffsets[data.first] = (GLvoid*)currentSize; + } + + glBindBuffer(GL_ARRAY_BUFFER, _transform._drawCallInfoBuffer); + glBufferData(GL_ARRAY_BUFFER, bufferData.size(), bufferData.data(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + +#ifdef GPU_SSBO_DRAW_CALL_INFO + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, TRANSFORM_OBJECT_SLOT, _transform._objectBuffer); +#else + glActiveTexture(GL_TEXTURE0 + TRANSFORM_OBJECT_SLOT); + glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); +#endif + + CHECK_GL_ERROR(); + + // Make sure the current Camera offset is unknown before render Draw + _transform._currentCameraOffset = INVALID_OFFSET; +} diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index ae1e9b4427..ecddeb07ad 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -2,3 +2,5 @@ set(TARGET_NAME gpu) AUTOSCRIBE_SHADER_LIB(gpu) setup_hifi_library() link_hifi_libraries(shared) + +target_nsight() diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index 18e923831f..9126f11acf 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -10,9 +10,10 @@ // #include "Batch.h" -#include #include +#include + #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" @@ -481,3 +482,106 @@ void Batch::popProfileRange() { ADD_COMMAND(popProfileRange); #endif } + +#define GL_TEXTURE0 0x84C0 + +void Batch::_glActiveBindTexture(uint32 unit, uint32 target, uint32 texture) { + // clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine + setResourceTexture(unit - GL_TEXTURE0, nullptr); + + ADD_COMMAND(glActiveBindTexture); + _params.push_back(texture); + _params.push_back(target); + _params.push_back(unit); +} + +void Batch::_glUniform1i(int32 location, int32 v0) { + if (location < 0) { + return; + } + ADD_COMMAND(glUniform1i); + _params.push_back(v0); + _params.push_back(location); +} + +void Batch::_glUniform1f(int32 location, float v0) { + if (location < 0) { + return; + } + ADD_COMMAND(glUniform1f); + _params.push_back(v0); + _params.push_back(location); +} + +void Batch::_glUniform2f(int32 location, float v0, float v1) { + ADD_COMMAND(glUniform2f); + + _params.push_back(v1); + _params.push_back(v0); + _params.push_back(location); +} + +void Batch::_glUniform3f(int32 location, float v0, float v1, float v2) { + ADD_COMMAND(glUniform3f); + + _params.push_back(v2); + _params.push_back(v1); + _params.push_back(v0); + _params.push_back(location); +} + +void Batch::_glUniform4f(int32 location, float v0, float v1, float v2, float v3) { + ADD_COMMAND(glUniform4f); + + _params.push_back(v3); + _params.push_back(v2); + _params.push_back(v1); + _params.push_back(v0); + _params.push_back(location); +} + +void Batch::_glUniform3fv(int32 location, int count, const float* value) { + ADD_COMMAND(glUniform3fv); + + const int VEC3_SIZE = 3 * sizeof(float); + _params.push_back(cacheData(count * VEC3_SIZE, value)); + _params.push_back(count); + _params.push_back(location); +} + +void Batch::_glUniform4fv(int32 location, int count, const float* value) { + ADD_COMMAND(glUniform4fv); + + const int VEC4_SIZE = 4 * sizeof(float); + _params.push_back(cacheData(count * VEC4_SIZE, value)); + _params.push_back(count); + _params.push_back(location); +} + +void Batch::_glUniform4iv(int32 location, int count, const int32* value) { + ADD_COMMAND(glUniform4iv); + + const int VEC4_SIZE = 4 * sizeof(int); + _params.push_back(cacheData(count * VEC4_SIZE, value)); + _params.push_back(count); + _params.push_back(location); +} + +void Batch::_glUniformMatrix4fv(int32 location, int count, uint8 transpose, const float* value) { + ADD_COMMAND(glUniformMatrix4fv); + + const int MATRIX4_SIZE = 16 * sizeof(float); + _params.push_back(cacheData(count * MATRIX4_SIZE, value)); + _params.push_back(transpose); + _params.push_back(count); + _params.push_back(location); +} + +void Batch::_glColor4f(float red, float green, float blue, float alpha) { + ADD_COMMAND(glColor4f); + + _params.push_back(alpha); + _params.push_back(blue); + _params.push_back(green); + _params.push_back(red); +} \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 621ccee2f9..83645f73e3 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -84,6 +84,13 @@ namespace gpu { namespace gl { class GLBuffer; + } + + namespace gl41 { + class GLBackend; + } + + namespace gl45 { class GLBackend; } } diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 21164955d6..10c83dfb0e 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -184,8 +184,34 @@ public: return append(sizeof(T) * t.size(), reinterpret_cast(&t[0])); } + bool getNextTransferBlock(Size& outOffset, Size& outSize, Size& currentPage) const { + Size pageCount = _pages.size(); + // Advance to the first dirty page + while (currentPage < pageCount && (0 == (Buffer::DIRTY & _pages[currentPage]))) { + ++currentPage; + } + + // If we got to the end, we're done + if (currentPage >= pageCount) { + return false; + } + + // Advance to the next clean page + outOffset = static_cast(currentPage * _pageSize); + while (currentPage < pageCount && (0 != (Buffer::DIRTY & _pages[currentPage]))) { + _pages[currentPage] &= ~Buffer::DIRTY; + ++currentPage; + } + outSize = static_cast((currentPage * _pageSize) - outOffset); + return true; + } + const GPUObjectPointer gpuObject {}; + // Access the sysmem object, limited to ourselves and GPUObject derived classes + const Sysmem& getSysmem() const { return _sysmem; } + // FIXME find a better access mechanism for clearing this + mutable uint8_t _flags; protected: void markDirty(Size offset, Size bytes); @@ -194,21 +220,18 @@ protected: markDirty(sizeof(T) * index, sizeof(T) * count); } - // Access the sysmem object, limited to ourselves and GPUObject derived classes - const Sysmem& getSysmem() const { return _sysmem; } Sysmem& editSysmem() { return _sysmem; } Byte* editData() { return editSysmem().editData(); } Size getRequiredPageCount() const; Size _end { 0 }; - mutable uint8_t _flags; mutable PageFlags _pages; const Size _pageSize; Sysmem _sysmem; // FIXME find a more generic way to do this. - friend class gl::GLBackend; + friend class gl::GLBuffer; friend class BufferView; }; From c48fce4f5ab1cebf53e191d40dbd9b41d218faf4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 15:24:10 -0700 Subject: [PATCH 132/264] WIP commit, socket errors when AvatarIdentity is larger then MTU --- assignment-client/src/avatars/AvatarMixer.cpp | 4 +- libraries/avatars/src/AvatarData.cpp | 48 ++++++++++------- libraries/avatars/src/AvatarData.h | 14 ++++- libraries/avatars/src/AvatarHashMap.cpp | 52 ++++++------------- 4 files changed, 60 insertions(+), 58 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index cc94e4f1b7..46599396ca 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -417,7 +417,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes AvatarData& avatar = nodeData->getAvatar(); // parse the identity packet and update the change timestamp if appropriate - if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) { + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); + if (avatar.processAvatarIdentity(identity)) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4b7e5cfb3c..abf3f52ed6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -612,7 +612,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // the wrong bones, resulting in a twisted avatar, An un-animated avatar is preferable to this. bool skipJoints = false; if (_networkJointIndexMap.empty()) { - skipJoints = true; + qCDebug(avatars) << "AJT: parseAvatarDataPacket _networkJointIndexMap.size = " << _networkJointIndexMap.size(); + skipJoints = false; } int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); @@ -726,7 +727,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } } - } // numJoints * 12 bytes + } // numJoints * 6 bytes #ifdef WANT_DEBUG if (numValidJointRotations > 15) { @@ -981,42 +982,45 @@ void AvatarData::clearJointsData() { } } -bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray& data) { +void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); + packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.jointIndices; + + // AJT: just got a new networkJointIndicesMap. + qCDebug(avatars) << "AJT: receiving identity.jointIndices.size = " << identityOut.jointIndices.size(); +} + +bool AvatarData::processAvatarIdentity(const Identity& identity) { - QUuid avatarUUID; - QUrl skeletonModelURL; - QVector attachmentData; - QString displayName; - QHash networkJointIndices; - packetStream >> avatarUUID >> skeletonModelURL >> attachmentData >> displayName >> networkJointIndices; bool hasIdentityChanged = false; - if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !networkJointIndices.empty()) { + qCDebug(avatars) << "AJT: processAvatarIdentity got here!"; + + if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !identity.jointIndices.empty()) { // build networkJointIndexMap from _jointIndices and networkJointIndices. - _networkJointIndexMap.fill(networkJointIndices.size(), -1); - for (auto iter = networkJointIndices.cbegin(); iter != networkJointIndices.end(); ++iter) { + _networkJointIndexMap.fill(identity.jointIndices.size(), -1); + for (auto iter = identity.jointIndices.cbegin(); iter != identity.jointIndices.end(); ++iter) { int jointIndex = getJointIndex(iter.key()); _networkJointIndexMap[iter.value()] = jointIndex; + qCDebug(avatars) << "AJT: mapping " << iter.value() << " -> " << jointIndex; } } - // AJT: just got a new networkJointIndicesMap. - qCDebug(avatars) << "AJT: receiving networkJointIndices.size = " << networkJointIndices.size(); + qCDebug(avatars) << "AJT: processAvatarIdentity got here!"; - if (_firstSkeletonCheck || (skeletonModelURL != _skeletonModelURL)) { - setSkeletonModelURL(skeletonModelURL); + if (_firstSkeletonCheck || (identity.skeletonModelURL != _skeletonModelURL)) { + setSkeletonModelURL(identity.skeletonModelURL); hasIdentityChanged = true; _firstSkeletonCheck = false; } - if (displayName != _displayName) { - setDisplayName(displayName); + if (identity.displayName != _displayName) { + setDisplayName(identity.displayName); hasIdentityChanged = true; } - if (attachmentData != _attachmentData) { - setAttachmentData(attachmentData); + if (identity.attachmentData != _attachmentData) { + setAttachmentData(identity.attachmentData); hasIdentityChanged = true; } @@ -1204,6 +1208,8 @@ void AvatarData::sendIdentityPacket() { QByteArray identityData = identityByteArray(); + qCDebug(avatars) << "AJT: sendIdentityPacket() size = " << identityData.size(); + auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); packetList->write(identityData); nodeList->eachMatchingNode( @@ -1213,6 +1219,8 @@ void AvatarData::sendIdentityPacket() { [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); + + qCDebug(avatars) << "AJT: sendIdentityPacket() done!"; } void AvatarData::updateJointMappings() { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 43bc682bda..feabfad544 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -280,7 +280,19 @@ public: const HeadData* getHeadData() const { return _headData; } - bool hasIdentityChangedAfterParsing(const QByteArray& data); + struct Identity { + QUuid uuid; + QUrl skeletonModelURL; + QVector attachmentData; + QString displayName; + QHash jointIndices; + }; + + static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); + + // returns true if identity has changed, false otherwise. + bool processAvatarIdentity(const Identity& identity); + QByteArray identityByteArray(); const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index f14e2b0ad3..1b1cb14e41 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -50,26 +50,26 @@ AvatarSharedPointer AvatarHashMap::newSharedAvatar() { AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - + auto avatar = newSharedAvatar(); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); - + _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); - + return avatar; } AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { QWriteLocker locker(&_hashLock); - + auto avatar = _avatarHash.value(sessionUUID); - + if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); } - + return avatar; } @@ -86,14 +86,14 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (message->getBytesLeftToRead()) { QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); - + int positionBeforeRead = message->getPosition(); QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); - + if (sessionUUID != _lastOwnerSessionUUID) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - + // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); @@ -107,33 +107,13 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { - // setup a data stream to parse the packet - QDataStream identityStream(message->getMessage()); + AvatarData::Identity identity; + AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); - QUuid sessionUUID; - - while (!identityStream.atEnd()) { + // mesh URL for a UUID, find avatar in our list + auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); - QUrl faceMeshURL, skeletonURL; - QVector attachmentData; - QString displayName; - identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> attachmentData >> displayName; - - // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); - - if (avatar->getSkeletonModelURL().isEmpty() || (avatar->getSkeletonModelURL() != skeletonURL)) { - avatar->setSkeletonModelURL(skeletonURL); // Will expand "" to default and so will not continuously fire - } - - if (avatar->getAttachmentData() != attachmentData) { - avatar->setAttachmentData(attachmentData); - } - - if (avatar->getDisplayName() != displayName) { - avatar->setDisplayName(displayName); - } - } + avatar->processAvatarIdentity(identity); } void AvatarHashMap::processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode) { @@ -144,9 +124,9 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S void AvatarHashMap::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); - + auto removedAvatar = _avatarHash.take(sessionUUID); - + if (removedAvatar) { handleRemovedAvatar(removedAvatar); } From cf49c8e2bfbcafe5ef95265f5416610eed7935fc Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 6 May 2016 11:11:50 -0700 Subject: [PATCH 133/264] Add getUserAgent function that includes plugin information --- interface/src/Application.cpp | 34 ++++++++++++++++++++++++++++++++++ interface/src/Application.h | 3 +++ 2 files changed, 37 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5f08877ae2..edc6227bfc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1057,6 +1057,40 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : firstRun.set(false); } +QString Application::getUserAgent() { + if (QThread::currentThread() != thread()) { + QString userAgent; + + QMetaObject::invokeMethod(this, "getUserAgent", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userAgent)); + + return userAgent; + } + + QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; " + + QSysInfo::productType() + " " + QSysInfo::productVersion() + ")"; + + auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); }; + + // For each plugin, add to userAgent + auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); + foreach(auto dp, displayPlugins) { + if (dp->isActive() && dp->isHmd()) { + userAgent += " " + formatPluginName(dp->getName()); + } + } + + // For each plugin, add to userAgent + auto inputPlugins= PluginManager::getInstance()->getInputPlugins(); + foreach(auto ip, inputPlugins) { + if (ip->isActive()) { + userAgent += " " + formatPluginName(ip->getName()); + } + } + + return userAgent; +} + + void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); diff --git a/interface/src/Application.h b/interface/src/Application.h index 558190c8d1..5616722c92 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -112,6 +112,9 @@ public: QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); + // Return an HTTP User-Agent string with OS and device information. + Q_INVOKABLE QString getUserAgent(); + void initializeGL(); void initializeUi(); void paintGL(); From dd093e3fcdc2716f22d8b3013bbb08a0e8fd3071 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 6 May 2016 15:25:19 -0700 Subject: [PATCH 134/264] Make AccountManager a DependencyManager singleton --- assignment-client/src/AssignmentClient.cpp | 10 +++-- domain-server/src/DomainGatekeeper.cpp | 2 +- domain-server/src/DomainServer.cpp | 45 ++++++++++--------- interface/src/Application.cpp | 17 +++---- interface/src/DiscoverabilityManager.cpp | 12 ++--- interface/src/Menu.cpp | 6 +-- interface/src/main.cpp | 12 ++--- .../scripting/AccountScriptingInterface.cpp | 12 ++--- .../GlobalServicesScriptingInterface.cpp | 18 ++++---- interface/src/ui/LoginDialog.cpp | 15 ++++--- interface/src/ui/Snapshot.cpp | 9 ++-- libraries/networking/src/AccountManager.cpp | 10 ----- libraries/networking/src/AccountManager.h | 3 ++ libraries/networking/src/AddressManager.cpp | 7 ++- libraries/networking/src/DomainHandler.cpp | 6 +-- libraries/networking/src/NodeList.cpp | 20 ++++----- .../src/OAuthNetworkAccessManager.cpp | 6 +-- .../networking/src/UserActivityLogger.cpp | 5 ++- .../script-engine/src/XMLHttpRequestClass.cpp | 6 +-- libraries/ui/src/OffscreenUi.cpp | 4 +- libraries/ui/src/Tooltip.cpp | 10 ++--- 21 files changed, 118 insertions(+), 117 deletions(-) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 0455377d89..4fc8975262 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -51,6 +51,8 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri LogUtils::init(); QSettings::setDefaultFormat(QSettings::IniFormat); + + DependencyManager::set(); auto scriptableAvatar = DependencyManager::set(); auto addressManager = DependencyManager::set(); @@ -116,7 +118,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri _requestTimer.start(ASSIGNMENT_REQUEST_INTERVAL_MSECS); // connections to AccountManager for authentication - connect(&AccountManager::getInstance(), &AccountManager::authRequired, + connect(DependencyManager::get().data(), &AccountManager::authRequired, this, &AssignmentClient::handleAuthenticationRequest); // Create Singleton objects on main thread @@ -309,13 +311,13 @@ void AssignmentClient::handleAuthenticationRequest() { QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV); QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV); - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); if (!username.isEmpty() && !password.isEmpty()) { // ask the account manager to log us in from the env variables - accountManager.requestAccessToken(username, password); + accountManager->requestAccessToken(username, password); } else { - qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager.getAuthURL().toString()) + qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString()) << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate."; diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 94e79416a5..919ac37ee9 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -485,7 +485,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) { qDebug() << "Requesting public key for user" << username; - AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username), + DependencyManager::get()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, callbackParams); } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a5c485ebe2..f37954c4e6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -77,7 +78,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) - AccountManager::getInstance(true); + DependencyManager::set(); auto args = arguments(); @@ -195,8 +196,8 @@ bool DomainServer::optionallySetupOAuth() { _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL; } - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_oauthProviderURL); + auto accountManager = DependencyManager::get(); + accountManager->setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); @@ -239,7 +240,7 @@ void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { // we've been asked to grab a temporary name from the API // so fire off that request now - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); // get callbacks for temporary domain result JSONCallbackParameters callbackParameters; @@ -248,8 +249,8 @@ void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) { callbackParameters.errorCallbackReceiver = this; callbackParameters.errorCallbackMethod = "handleTempDomainError"; - accountManager.sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, - QNetworkAccessManager::PostOperation, callbackParameters); + accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, + QNetworkAccessManager::PostOperation, callbackParameters); } } @@ -397,7 +398,7 @@ bool DomainServer::resetAccountManagerAccessToken() { << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; // clear any existing access token from AccountManager - AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString()); + DependencyManager::get()->setAccessTokenForCurrentAuthURL(QString()); return false; } @@ -407,7 +408,7 @@ bool DomainServer::resetAccountManagerAccessToken() { } // give this access token to the AccountManager - AccountManager::getInstance().setAccessTokenForCurrentAuthURL(accessToken); + DependencyManager::get()->setAccessTokenForCurrentAuthURL(accessToken); return true; @@ -499,17 +500,17 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { limitedNodeList->startSTUNPublicSocketUpdate(); // to send ICE heartbeats we'd better have a private key locally with an uploaded public key - auto& accountManager = AccountManager::getInstance(); - auto domainID = accountManager.getAccountInfo().getDomainID(); + auto accountManager = DependencyManager::get(); + auto domainID = accountManager->getAccountInfo().getDomainID(); // if we have an access token and we don't have a private key or the current domain ID has changed // we should generate a new keypair - if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { - accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) { + accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } // hookup to the signal from account manager that tells us when keypair is available - connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); + connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange); if (!_iceHeartbeatTimer) { // setup a timer to heartbeat with the ice-server every so often @@ -962,9 +963,9 @@ void DomainServer::setupPendingAssignmentCredits() { void DomainServer::sendPendingTransactionsToServer() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken()) { + if (accountManager->hasValidAccessToken()) { // enumerate the pending transactions and send them to the server to complete payment TransactionHash::iterator i = _pendingAssignmentCredits.begin(); @@ -975,7 +976,7 @@ void DomainServer::sendPendingTransactionsToServer() { transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback"; while (i != _pendingAssignmentCredits.end()) { - accountManager.sendRequest("api/v1/transactions", + accountManager->sendRequest("api/v1/transactions", AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, transactionCallbackParams, i.value()->postJson().toJson()); @@ -1073,7 +1074,7 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); - AccountManager::getInstance().sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, JSONCallbackParameters(), @@ -1103,7 +1104,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address"; - AccountManager::getInstance().sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), + DependencyManager::get()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)), AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, @@ -1123,15 +1124,15 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestRepl void DomainServer::sendHeartbeatToIceServer() { if (!_iceServerSocket.getAddress().isNull()) { - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); auto limitedNodeList = DependencyManager::get(); - if (!accountManager.getAccountInfo().hasPrivateKey()) { + if (!accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "Cannot send an ice-server heartbeat without a private key for signature."; qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat."; if (!limitedNodeList->getSessionUUID().isNull()) { - accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); } else { qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported"; } @@ -2101,7 +2102,7 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer(); - AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID()); + DependencyManager::get()->generateNewDomainKeypair(limitedNodeList->getSessionUUID()); // reset our number of heartbeat denials _numHeartbeatDenials = 0; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index edc6227bfc..3fffc4d9f3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -402,6 +402,7 @@ bool setupEssentials(int& argc, char** argv) { Setting::init(); // Set dependencies + DependencyManager::set(std::bind(&Application::getUserAgent, qApp)); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -656,15 +657,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); auto dialogsManager = DependencyManager::get(); - connect(&accountManager, &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); - connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); + connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // set the account manager's root URL and trigger a login request if we don't have the access token - accountManager.setIsAgent(true); - accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); + accountManager->setIsAgent(true); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. @@ -4169,7 +4170,7 @@ void Application::updateWindowTitle() const { auto nodeList = DependencyManager::get(); QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED) "; - QString username = AccountManager::getInstance().getAccountInfo().getUsername(); + QString username = DependencyManager::get()->getAccountInfo().getUsername(); QString currentPlaceName = DependencyManager::get()->getHost(); if (currentPlaceName.isEmpty()) { @@ -4809,8 +4810,8 @@ void Application::takeSnapshot() { QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); - AccountManager& accountManager = AccountManager::getInstance(); - if (!accountManager.isLoggedIn()) { + auto accountManager = DependencyManager::get(); + if (!accountManager->isLoggedIn()) { return; } diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index a8b0a265c9..83f87f82ba 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -35,9 +35,9 @@ const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat"; const QString SESSION_ID_KEY = "session_id"; void DiscoverabilityManager::updateLocation() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (_mode.get() != Discoverability::None && accountManager.isLoggedIn()) { + if (_mode.get() != Discoverability::None && accountManager->isLoggedIn()) { auto addressManager = DependencyManager::get(); DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -98,7 +98,7 @@ void DiscoverabilityManager::updateLocation() { apiPath = API_USER_LOCATION_PATH; } - accountManager.sendRequest(apiPath, AccountManagerAuth::Required, + accountManager->sendRequest(apiPath, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation, callbackParameters, QJsonDocument(rootObject).toJson()); @@ -116,7 +116,7 @@ void DiscoverabilityManager::updateLocation() { heartbeatObject[SESSION_ID_KEY] = QJsonValue(); } - accountManager.sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, + accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, QJsonDocument(heartbeatObject).toJson()); } @@ -131,8 +131,8 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply } void DiscoverabilityManager::removeLocation() { - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation); + auto accountManager = DependencyManager::get(); + accountManager->sendRequest(API_USER_LOCATION_PATH, AccountManagerAuth::Required, QNetworkAccessManager::DeleteOperation); } void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 05bb03bf86..8b69bb8022 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -54,7 +54,7 @@ Menu* Menu::getInstance() { Menu::Menu() { auto dialogsManager = DependencyManager::get(); - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); // File/Application menu ---------------------------------- MenuWrapper* fileMenu = addMenu("File"); @@ -64,9 +64,9 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); // connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item - connect(&accountManager, &AccountManager::profileChanged, + connect(accountManager.data(), &AccountManager::profileChanged, dialogsManager.data(), &DialogsManager::toggleLoginDialog); - connect(&accountManager, &AccountManager::logoutComplete, + connect(accountManager.data(), &AccountManager::logoutComplete, dialogsManager.data(), &DialogsManager::toggleLoginDialog); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 1726d47045..13f9470fda 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -144,11 +144,11 @@ int main(int argc, const char* argv[]) { // If we failed the OpenGLVersion check, log it. if (override) { - auto& accountManager = AccountManager::getInstance(); - if (accountManager.isLoggedIn()) { + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { UserActivityLogger::getInstance().insufficientGLVersion(glData); } else { - QObject::connect(&AccountManager::getInstance(), &AccountManager::loginComplete, [glData](){ + QObject::connect(accountManager.data(), &AccountManager::loginComplete, [glData](){ static bool loggedInsufficientGL = false; if (!loggedInsufficientGL) { UserActivityLogger::getInstance().insufficientGLVersion(glData); @@ -168,9 +168,9 @@ int main(int argc, const char* argv[]) { QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection); #ifdef HAS_BUGSPLAT - AccountManager& accountManager = AccountManager::getInstance(); - crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername())); - QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { + auto accountManager = DependencyManager::get(); + crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername())); + QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) { crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername)); }); diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 1b6d52ac2a..377dbe8e35 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -13,20 +13,22 @@ #include "AccountScriptingInterface.h" +#include + AccountScriptingInterface* AccountScriptingInterface::getInstance() { static AccountScriptingInterface sharedInstance; return &sharedInstance; } bool AccountScriptingInterface::isLoggedIn() { - AccountManager& accountManager = AccountManager::getInstance(); - return accountManager.isLoggedIn(); + auto accountManager = DependencyManager::get(); + return accountManager->isLoggedIn(); } QString AccountScriptingInterface::getUsername() { - AccountManager& accountManager = AccountManager::getInstance(); - if (accountManager.isLoggedIn()) { - return accountManager.getAccountInfo().getUsername(); + auto accountManager = DependencyManager::get(); + if (accountManager->isLoggedIn()) { + return accountManager->getAccountInfo().getUsername(); } else { return "Unknown user"; } diff --git a/interface/src/scripting/GlobalServicesScriptingInterface.cpp b/interface/src/scripting/GlobalServicesScriptingInterface.cpp index e8d63a6d99..d7e5bae3f8 100644 --- a/interface/src/scripting/GlobalServicesScriptingInterface.cpp +++ b/interface/src/scripting/GlobalServicesScriptingInterface.cpp @@ -17,10 +17,10 @@ #include "GlobalServicesScriptingInterface.h" GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { - AccountManager& accountManager = AccountManager::getInstance(); - connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); - connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - connect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + connect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + connect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); _downloading = false; QTimer* checkDownloadTimer = new QTimer(this); @@ -34,10 +34,10 @@ GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() { } GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() { - AccountManager& accountManager = AccountManager::getInstance(); - disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); - disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); - disconnect(&accountManager, &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); + auto accountManager = DependencyManager::get(); + disconnect(accountManager.data(), &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged); + disconnect(accountManager.data(), &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut); + disconnect(accountManager.data(), &AccountManager::loginComplete, this, &GlobalServicesScriptingInterface::connected); } GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() { @@ -46,7 +46,7 @@ GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance( } const QString& GlobalServicesScriptingInterface::getUsername() const { - return AccountManager::getInstance().getAccountInfo().getUsername(); + return DependencyManager::get()->getAccountInfo().getUsername(); } void GlobalServicesScriptingInterface::loggedOut() { diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 60d19df355..80d52b7a07 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -24,14 +24,15 @@ HIFI_QML_DEF(LoginDialog) LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent), _rootUrl(NetworkingConstants::METAVERSE_SERVER_URL.toString()) { - connect(&AccountManager::getInstance(), &AccountManager::loginComplete, + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::loginComplete, this, &LoginDialog::handleLoginCompleted); - connect(&AccountManager::getInstance(), &AccountManager::loginFailed, + connect(accountManager.data(), &AccountManager::loginFailed, this, &LoginDialog::handleLoginFailed); } void LoginDialog::toggleAction() { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); QAction* loginAction = Menu::getInstance()->getActionForOption(MenuOption::Login); Q_CHECK_PTR(loginAction); static QMetaObject::Connection connection; @@ -39,10 +40,10 @@ void LoginDialog::toggleAction() { disconnect(connection); } - if (accountManager.isLoggedIn()) { + if (accountManager->isLoggedIn()) { // change the menu item to logout - loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername()); - connection = connect(loginAction, &QAction::triggered, &accountManager, &AccountManager::logout); + loginAction->setText("Logout " + accountManager->getAccountInfo().getUsername()); + connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout); } else { // change the menu item to login loginAction->setText("Login"); @@ -78,7 +79,7 @@ QString LoginDialog::rootUrl() const { void LoginDialog::login(const QString& username, const QString& password) { qDebug() << "Attempting to login " << username; setStatusText("Logging in..."); - AccountManager::getInstance().requestAccessToken(username, password); + DependencyManager::get()->requestAccessToken(username, password); } void LoginDialog::openUrl(const QString& url) { diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index e35039df3d..b8be2bb8c4 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -92,7 +92,7 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { QUrl currentURL = DependencyManager::get()->currentAddress(); shot.setText(URL, currentURL.toString()); - QString username = AccountManager::getInstance().getAccountInfo().getUsername(); + QString username = DependencyManager::get()->getAccountInfo().getUsername(); // normalize username, replace all non alphanumeric with '-' username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); @@ -144,14 +144,15 @@ const QString SUCCESS_LABEL_TEMPLATE = "Success!!! Go check out your image ...(); + if (accountManager->getAccountInfo().getDiscourseApiKey().isEmpty()) { OffscreenUi::warning(nullptr, "", "Your Discourse API key is missing, you cannot share snapshots. Please try to relog."); return QString(); } QHttpPart apiKeyPart; apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"api_key\"")); - apiKeyPart.setBody(AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().toLatin1()); + apiKeyPart.setBody(accountManager->getAccountInfo().getDiscourseApiKey().toLatin1()); QString filename = fileUrl.toLocalFile(); qDebug() << filename; @@ -206,7 +207,7 @@ QString SnapshotUploader::sendForumPost(const QString& snapshotPath, const QStri QUrl forumUrl(FORUM_POST_URL); QUrlQuery query; - query.addQueryItem("api_key", AccountManager::getInstance().getAccountInfo().getDiscourseApiKey()); + query.addQueryItem("api_key", DependencyManager::get()->getAccountInfo().getDiscourseApiKey()); query.addQueryItem("topic_id", FORUM_REPLY_TO_TOPIC); query.addQueryItem("raw", FORUM_POST_TEMPLATE.arg(snapshotPath, notes)); forumUrl.setQuery(query); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 8c23844f4e..324301a9d6 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -36,16 +36,6 @@ const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; -AccountManager& AccountManager::getInstance(bool forceReset) { - static std::unique_ptr sharedInstance(new AccountManager()); - - if (forceReset) { - sharedInstance.reset(new AccountManager()); - } - - return *sharedInstance; -} - Q_DECLARE_METATYPE(OAuthAccessToken) Q_DECLARE_METATYPE(DataServerAccountInfo) Q_DECLARE_METATYPE(QNetworkAccessManager::Operation) diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 108b49f678..7306eaef14 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -20,6 +20,9 @@ #include "NetworkAccessManager.h" #include "DataServerAccountInfo.h" +#include "SharedUtil.h" + +#include class JSONCallbackParameters { public: diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 27647d2694..a97f4df35d 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -374,7 +374,7 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q // remember how this lookup was triggered for history storage handling later requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); - AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName), + DependencyManager::get()->sendRequest(GET_PLACE.arg(placeName), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, apiCallbackParameters(), @@ -397,7 +397,7 @@ void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QS // remember how this lookup was triggered for history storage handling later requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); - AccountManager::getInstance().sendRequest(GET_DOMAIN_ID.arg(domainID), + DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(domainID), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, apiCallbackParameters(), @@ -577,7 +577,6 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 return false; } - bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { bool hostChanged = setHost(hostname, trigger, port); @@ -600,7 +599,7 @@ void AddressManager::goToUser(const QString& username) { requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); // this is a username - pull the captured name and lookup that user's location - AccountManager::getInstance().sendRequest(GET_USER_LOCATION.arg(formattedUsername), + DependencyManager::get()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, QNetworkAccessManager::GetOperation, apiCallbackParameters(), diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b2eb0cd680..44ce63e6c6 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -371,10 +371,10 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); if (!_hasCheckedForAccessToken) { - accountManager.checkAndSignalForAccessToken(); + accountManager->checkAndSignalForAccessToken(); _hasCheckedForAccessToken = true; } @@ -382,7 +382,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { - accountManager.generateNewUserKeypair(); + accountManager->generateNewUserKeypair(); _connectionDenialsSinceKeypairRegen = 0; } } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 25dfe884db..482d0366fd 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -80,16 +80,16 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send a ping punch immediately connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); - auto &accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); // assume that we may need to send a new DS check in anytime a new keypair is generated - connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); + connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); // clear out NodeList when login is finished - connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset); + connect(accountManager.data(), &AccountManager::loginComplete , this, &NodeList::reset); // clear our NodeList when logout is requested - connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset); + connect(accountManager.data(), &AccountManager::logoutComplete , this, &NodeList::reset); // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); @@ -273,7 +273,7 @@ void NodeList::sendDomainServerCheckIn() { } // check if we're missing a keypair we need to verify ourselves with the domain-server - auto& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); const QUuid& connectionToken = _domainHandler.getConnectionToken(); // we assume that we're on the same box as the DS if it has the same local address and @@ -283,10 +283,10 @@ void NodeList::sendDomainServerCheckIn() { bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain; - if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) { + if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { qWarning() << "A keypair is required to present a username signature to the domain-server" << "but no keypair is present. Waiting for keypair generation to complete."; - accountManager.generateNewUserKeypair(); + accountManager->generateNewUserKeypair(); // don't send the check in packet - wait for the keypair first return; @@ -318,12 +318,12 @@ void NodeList::sendDomainServerCheckIn() { packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); if (!_domainHandler.isConnected()) { - DataServerAccountInfo& accountInfo = accountManager.getAccountInfo(); + DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); // if this is a connect request, and we can present a username signature, send it along - if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) { - const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken); + if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { + const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } } diff --git a/libraries/networking/src/OAuthNetworkAccessManager.cpp b/libraries/networking/src/OAuthNetworkAccessManager.cpp index fa6f3b8340..92e7a2ff4f 100644 --- a/libraries/networking/src/OAuthNetworkAccessManager.cpp +++ b/libraries/networking/src/OAuthNetworkAccessManager.cpp @@ -32,14 +32,14 @@ OAuthNetworkAccessManager* OAuthNetworkAccessManager::getInstance() { QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest& req, QIODevice* outgoingData) { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken() + if (accountManager->hasValidAccessToken() && req.url().host() == NetworkingConstants::METAVERSE_SERVER_URL.host()) { QNetworkRequest authenticatedRequest(req); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, - accountManager.getAccountInfo().getAccessToken().authorizationHeaderValue()); + accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); return QNetworkAccessManager::createRequest(op, authenticatedRequest, outgoingData); } else { diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 0d7690840d..83c6eb304e 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -17,6 +17,7 @@ #include "NetworkLogging.h" #include "UserActivityLogger.h" +#include static const QString USER_ACTIVITY_URL = "/api/v1/user_activities"; @@ -34,7 +35,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall return; } - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); // Adding the action name @@ -59,7 +60,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params.errorCallbackMethod = "requestError"; } - accountManager.sendRequest(USER_ACTIVITY_URL, + accountManager->sendRequest(USER_ACTIVITY_URL, AccountManagerAuth::Optional, QNetworkAccessManager::PostOperation, params, NULL, multipart); diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 9a6f81b19f..15b2576331 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -140,11 +140,11 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a _async = async; if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); - if (accountManager.hasValidAccessToken()) { + if (accountManager->hasValidAccessToken()) { QUrlQuery urlQuery(_url.query()); - urlQuery.addQueryItem("access_token", accountManager.getAccountInfo().getAccessToken().token); + urlQuery.addQueryItem("access_token", accountManager->getAccountInfo().getAccessToken().token); _url.setQuery(urlQuery); } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 0c1ad69d72..4fb25e3e3f 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -53,8 +53,8 @@ QString fixupHifiUrl(const QString& urlString) { QUrl url(urlString); QUrlQuery query(url); if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { - AccountManager& accountManager = AccountManager::getInstance(); - query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); + auto accountManager = DependencyManager::get(); + query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token); url.setQuery(query.query()); return url.toString(); } diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index 56ba8a95b9..3c0902b378 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -84,16 +84,16 @@ void Tooltip::requestHyperlinkImage() { // should the network link be removed from UI at a later date. // we possibly have a valid place name - so ask the API for the associated info - AccountManager& accountManager = AccountManager::getInstance(); + auto accountManager = DependencyManager::get(); JSONCallbackParameters callbackParams; callbackParams.jsonCallbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; - accountManager.sendRequest(GET_PLACE.arg(_title), - AccountManagerAuth::None, - QNetworkAccessManager::GetOperation, - callbackParams); + accountManager->sendRequest(GET_PLACE.arg(_title), + AccountManagerAuth::None, + QNetworkAccessManager::GetOperation, + callbackParams); } } } From bfb4bb089638ab61955638fcccda07b654c116fe Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 6 May 2016 15:33:52 -0700 Subject: [PATCH 135/264] Add userAgentGetter to AccountManager for custom UserAgent --- libraries/networking/src/AccountManager.cpp | 12 +++++++----- libraries/networking/src/AccountManager.h | 9 ++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 324301a9d6..9080e3cc53 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -69,7 +69,8 @@ QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) } } -AccountManager::AccountManager() : +AccountManager::AccountManager(UserAgentGetter userAgentGetter) : + _userAgentGetter(userAgentGetter), _authURL(), _pendingCallbackMap() { @@ -212,8 +213,9 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - + + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + QUrl requestURL = _authURL; if (path.startsWith("/")) { @@ -463,7 +465,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest request; - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); QUrl grantURL = _authURL; grantURL.setPath("/oauth/token"); @@ -533,7 +535,7 @@ void AccountManager::requestProfile() { profileURL.setPath("/api/v1/user/profile"); QNetworkRequest profileRequest(profileURL); - profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + profileRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue()); QNetworkReply* profileReply = networkAccessManager.get(profileRequest); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 7306eaef14..d3c855e001 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -52,10 +52,12 @@ Q_DECLARE_METATYPE(AccountManagerAuth::Type); const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; -class AccountManager : public QObject { +using UserAgentGetter = std::function; + +class AccountManager : public QObject, public Dependency { Q_OBJECT public: - static AccountManager& getInstance(bool forceReset = false); + AccountManager(UserAgentGetter = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, @@ -112,7 +114,6 @@ private slots: void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); private: - AccountManager(); AccountManager(AccountManager const& other) = delete; void operator=(AccountManager const& other) = delete; @@ -122,6 +123,8 @@ private: void passSuccessToCallback(QNetworkReply* reply); void passErrorToCallback(QNetworkReply* reply); + UserAgentGetter _userAgentGetter; + QUrl _authURL; QMap _pendingCallbackMap; From ce6ae4043277a6cd1204bd1d2cf1ed279637283f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 6 May 2016 15:59:59 -0700 Subject: [PATCH 136/264] Replace foreach use with for() --- interface/src/Application.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3fffc4d9f3..9ad24526c1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1074,15 +1074,13 @@ QString Application::getUserAgent() { // For each plugin, add to userAgent auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); - foreach(auto dp, displayPlugins) { + for (auto& dp : displayPlugins) { if (dp->isActive() && dp->isHmd()) { userAgent += " " + formatPluginName(dp->getName()); } } - - // For each plugin, add to userAgent auto inputPlugins= PluginManager::getInstance()->getInputPlugins(); - foreach(auto ip, inputPlugins) { + for (auto& ip : inputPlugins) { if (ip->isActive()) { userAgent += " " + formatPluginName(ip->getName()); } From 79ce64aa3afdf028cef04b697fc57a453bf9e287 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 10 May 2016 15:36:22 -0700 Subject: [PATCH 137/264] Fix compilation error in DomainServer.cpp --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f37954c4e6..0aab6b7e31 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1209,7 +1209,7 @@ void DomainServer::sendHeartbeatToIceServer() { auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize()); // generate a signature for the plaintext data in the packet - auto signature = accountManager.getAccountInfo().signPlaintext(plaintext); + auto signature = accountManager->getAccountInfo().signPlaintext(plaintext); // pack the signature with the data heartbeatDataStream << signature; From e622c17f6b184ec109098cbc98e3f743cf3124d2 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 May 2016 15:22:52 -0700 Subject: [PATCH 138/264] Pull default UserAgentGetter out of func signature --- libraries/networking/src/AccountManager.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index d3c855e001..d9a5ad8d83 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -53,11 +53,12 @@ Q_DECLARE_METATYPE(AccountManagerAuth::Type); const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; using UserAgentGetter = std::function; +auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }; class AccountManager : public QObject, public Dependency { Q_OBJECT public: - AccountManager(UserAgentGetter = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }); + AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, From 01944f51004dbe8ecf71557e2923e0afe3f55de0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 17 May 2016 15:23:44 -0700 Subject: [PATCH 139/264] Remove unused DependencyManager include --- interface/src/scripting/AccountScriptingInterface.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 377dbe8e35..1328197195 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -13,8 +13,6 @@ #include "AccountScriptingInterface.h" -#include - AccountScriptingInterface* AccountScriptingInterface::getInstance() { static AccountScriptingInterface sharedInstance; return &sharedInstance; From 64c89a5dde45d83fb785ce40d8e66b07bc69a204 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 18 May 2016 08:34:26 -0700 Subject: [PATCH 140/264] Fix AccountManager not compiling on Windows --- libraries/networking/src/AccountManager.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index d9a5ad8d83..89a2240bbb 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -53,7 +53,8 @@ Q_DECLARE_METATYPE(AccountManagerAuth::Type); const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; using UserAgentGetter = std::function; -auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }; + +const auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }; class AccountManager : public QObject, public Dependency { Q_OBJECT From 135fa8c2aaa6fb8987d7fe5eda9b16518ecb8ec0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 15:41:21 -0700 Subject: [PATCH 141/264] Moved jointIndices transmission behind #ifdef --- libraries/avatars/src/AvatarData.cpp | 40 ++++++++++++---------------- libraries/avatars/src/AvatarData.h | 4 +++ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index abf3f52ed6..09ea34a5c4 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -611,10 +611,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // an earlier AvatarIdentity packet. Because if we do, we risk applying the joint data // the wrong bones, resulting in a twisted avatar, An un-animated avatar is preferable to this. bool skipJoints = false; +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET if (_networkJointIndexMap.empty()) { - qCDebug(avatars) << "AJT: parseAvatarDataPacket _networkJointIndexMap.size = " << _networkJointIndexMap.size(); - skipJoints = false; + skipJoints = true; } +#endif int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; @@ -984,29 +985,28 @@ void AvatarData::clearJointsData() { void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); - packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.jointIndices; - // AJT: just got a new networkJointIndicesMap. - qCDebug(avatars) << "AJT: receiving identity.jointIndices.size = " << identityOut.jointIndices.size(); +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET + packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.jointIndices; +#else + packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName; +#endif } bool AvatarData::processAvatarIdentity(const Identity& identity) { bool hasIdentityChanged = false; - qCDebug(avatars) << "AJT: processAvatarIdentity got here!"; - + #ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !identity.jointIndices.empty()) { // build networkJointIndexMap from _jointIndices and networkJointIndices. _networkJointIndexMap.fill(identity.jointIndices.size(), -1); for (auto iter = identity.jointIndices.cbegin(); iter != identity.jointIndices.end(); ++iter) { int jointIndex = getJointIndex(iter.key()); _networkJointIndexMap[iter.value()] = jointIndex; - qCDebug(avatars) << "AJT: mapping " << iter.value() << " -> " << jointIndex; } } - - qCDebug(avatars) << "AJT: processAvatarIdentity got here!"; +#endif if (_firstSkeletonCheck || (identity.skeletonModelURL != _skeletonModelURL)) { setSkeletonModelURL(identity.skeletonModelURL); @@ -1033,10 +1033,11 @@ QByteArray AvatarData::identityByteArray() { QUrl emptyURL(""); const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; - // AJT: just got a sending networkJointIndices - qCDebug(avatars) << "AJT: sending _jointIndices.size = " << _jointIndices.size(); - +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET identityStream << QUuid() << urlToSend << _attachmentData << _displayName << _jointIndices; +#else + identityStream << QUuid() << urlToSend << _attachmentData << _displayName; +#endif return identityData; } @@ -1141,8 +1142,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { void AvatarData::setJointMappingsFromNetworkReply() { QNetworkReply* networkReply = static_cast(sender()); - qCDebug(avatars) << "AJT: GOT HERE! finished fst network request"; - QByteArray line; while (!(line = networkReply->readLine()).isEmpty()) { line = line.trimmed(); @@ -1180,8 +1179,6 @@ void AvatarData::setJointMappingsFromNetworkReply() { // now that we have the jointIndices send them to the AvatarMixer. sendIdentityPacket(); - qCDebug(avatars) << "AJT: _jointIndices.size = " << _jointIndices.size(); - networkReply->deleteLater(); } @@ -1208,8 +1205,6 @@ void AvatarData::sendIdentityPacket() { QByteArray identityData = identityByteArray(); - qCDebug(avatars) << "AJT: sendIdentityPacket() size = " << identityData.size(); - auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); packetList->write(identityData); nodeList->eachMatchingNode( @@ -1219,16 +1214,15 @@ void AvatarData::sendIdentityPacket() { [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); }); - - qCDebug(avatars) << "AJT: sendIdentityPacket() done!"; } void AvatarData::updateJointMappings() { _jointIndices.clear(); _jointNames.clear(); - _networkJointIndexMap.clear(); - qCDebug(avatars) << "AJT: GOT HERE! kicking off fst network request"; +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET + _networkJointIndexMap.clear(); +#endif if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index feabfad544..5af66fae4e 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -285,7 +285,9 @@ public: QUrl skeletonModelURL; QVector attachmentData; QString displayName; +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET QHash jointIndices; +#endif }; static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); @@ -378,7 +380,9 @@ protected: float _displayNameAlpha; QHash _jointIndices; ///< 1-based, since zero is returned for missing keys +#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET QVector _networkJointIndexMap; // maps network joint indices to local model joint indices. +#endif QStringList _jointNames; ///< in order of depth-first traversal quint64 _errorLogExpiry; ///< time in future when to log an error From e792e8eecfcf7e4565090720fd332c55d285f222 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 16:05:16 -0700 Subject: [PATCH 142/264] Fix for identity packet pong --- libraries/avatars/src/AvatarData.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 09ea34a5c4..e5fe31217f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -606,7 +606,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // joint rotations int numJoints = *sourceBuffer++; - // do not process any jointData until we've received a valid jointIndices hash from // an earlier AvatarIdentity packet. Because if we do, we risk applying the joint data // the wrong bones, resulting in a twisted avatar, An un-animated avatar is preferable to this. @@ -1034,22 +1033,21 @@ QByteArray AvatarData::identityByteArray() { const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; #ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - identityStream << QUuid() << urlToSend << _attachmentData << _displayName << _jointIndices; + identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << _jointIndices; #else - identityStream << QUuid() << urlToSend << _attachmentData << _displayName; + identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName; #endif return identityData; } - void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; if (expanded == _skeletonModelURL) { return; } _skeletonModelURL = expanded; - qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); + qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); updateJointMappings(); } From eb80990c1096247c8dd191e7dcd5e7e798ebeaa8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 16:08:56 -0700 Subject: [PATCH 143/264] Another fix for avatarIdentity pong --- libraries/avatars/src/AvatarData.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e5fe31217f..f956305c3f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1174,9 +1174,6 @@ void AvatarData::setJointMappingsFromNetworkReply() { _jointIndices.insert(_jointNames.at(i), i + 1); } - // now that we have the jointIndices send them to the AvatarMixer. - sendIdentityPacket(); - networkReply->deleteLater(); } From b4df7cada73dc8f8b5ec6b7ea52af38fc3424100 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 18 May 2016 16:16:31 -0700 Subject: [PATCH 144/264] don't mouse-grab things through an overlay --- scripts/system/controllers/grab.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a7e08451bc..ef39e95880 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -316,7 +316,12 @@ Grabber.prototype.pressEvent = function(event) { return; } - if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) { + if (event.isLeftButton !== true || event.isRightButton === true || event.isMiddleButton === true) { + return; + } + + if (Overlays.getOverlayAtPoint(Reticle.position) > 0) { + // the mouse is pointing at an overlay; don't look for entities underneath the overlay. return; } From c480dcfddd740a939c60e0ef477256591612e8b2 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 16:26:54 -0700 Subject: [PATCH 145/264] Check thread validity after event processing --- libraries/script-engine/src/ScriptEngine.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 15e896fac4..5f68dcfbf8 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -294,13 +294,6 @@ void ScriptEngine::waitTillDoneRunning() { auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { - // NOTE: This will be called on the main application thread from stopAllScripts. - // The application thread will need to continue to process events, because - // the scripts will likely need to marshall messages across to the main thread, e.g. - // if they access Settings or Menu in any of their shutdown code. So: - // Process events for the main application thread, allowing invokeMethod calls to pass between threads. - QCoreApplication::processEvents(); - // If the final evaluation takes too long, then tell the script engine to stop running auto elapsedUsecs = usecTimestampNow() - startedWaiting; static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; @@ -326,6 +319,17 @@ void ScriptEngine::waitTillDoneRunning() { } } + // NOTE: This will be called on the main application thread from stopAllScripts. + // The application thread will need to continue to process events, because + // the scripts will likely need to marshall messages across to the main thread, e.g. + // if they access Settings or Menu in any of their shutdown code. So: + // Process events for the main application thread, allowing invokeMethod calls to pass between threads. + QCoreApplication::processEvents(); + // In some cases (debugging), processEvents may give the thread enough time to shut down, so recheck it. + if (!thread()) { + break; + } + // Avoid a pure busy wait QThread::yieldCurrentThread(); } From 862a88cf292cdb2cd1e6712cf156db0d6793f427 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 19 May 2016 11:29:21 +1200 Subject: [PATCH 146/264] Insert an intermediary ListModel suitable for sorting and drives --- .../resources/qml/dialogs/FileDialog.qml | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 4f6e7ef050..fbe8df79c0 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -184,7 +184,7 @@ ModalWindow { return; } - currentSelectionUrl = fileTableView.model.get(row, "fileURL"); + currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); currentSelectionIsFolder = fileTableView.model.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = helper.urlToPath(currentSelectionUrl); @@ -220,12 +220,49 @@ ModalWindow { showFiles = !root.selectDirectory } onFolderChanged: { + fileTableModel.update(); fileTableView.selection.clear(); fileTableView.selection.select(0); fileTableView.currentRow = 0; } } + ListModel { + id: fileTableModel + + // FolderListModel has a couple of problems: + // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757 + // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901 + // + // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with + // drive information when viewing at the computer level. + + property var folder + + onFolderChanged: folderListModel.folder = folder; + + function isFolder(row) { + if (row === -1) { + return false; + } + return get(row).fileIsDir; + } + + function update() { + var i; + clear(); + for (i = 0; i < folderListModel.count; i++) { + append({ + fileName: folderListModel.get(i, "fileName"), + fileModified: folderListModel.get(i, "fileModified"), + fileSize: folderListModel.get(i, "fileSize"), + filePath: folderListModel.get(i, "filePath"), + fileIsDir: folderListModel.get(i, "fileIsDir") + }); + } + } + } + Table { id: fileTableView colorScheme: hifi.colorSchemes.light @@ -247,7 +284,7 @@ ModalWindow { sortIndicatorOrder: Qt.AscendingOrder sortIndicatorVisible: true - model: folderListModel + model: fileTableModel function updateSort() { model.sortReversed = sortIndicatorColumn == 0 @@ -284,12 +321,17 @@ ModalWindow { } size: hifi.fontSizes.tableText color: hifi.colors.baseGrayHighlight - font.family: fileTableView.model.get(styleData.row, "fileIsDir") ? firaSansSemiBold.name : firaSansRegular.name + font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) + ? firaSansSemiBold.name : firaSansRegular.name function getText() { + if (styleData.row === -1) { + return styleData.value; + } + switch (styleData.column) { - case 1: return fileTableView.model.get(styleData.row, "fileIsDir") ? "" : styleData.value; - case 2: return fileTableView.model.get(styleData.row, "fileIsDir") ? "" : formatSize(styleData.value); + case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value; + case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value); default: return styleData.value; } } @@ -343,9 +385,9 @@ ModalWindow { function navigateToCurrentRow() { var row = fileTableView.currentRow var isFolder = model.isFolder(row); - var file = model.get(row, "fileURL"); + var file = model.get(row).filePath; if (isFolder) { - fileTableView.model.folder = file; + fileTableView.model.folder = helper.pathToUrl(file); } else { okAction.trigger(); } @@ -360,7 +402,7 @@ ModalWindow { var newPrefix = prefix + event.text.toLowerCase(); var matchedIndex = -1; for (var i = 0; i < model.count; ++i) { - var name = model.get(i, "fileName").toLowerCase(); + var name = model.get(i).fileName.toLowerCase(); if (0 === name.indexOf(newPrefix)) { matchedIndex = i; break; @@ -401,7 +443,6 @@ ModalWindow { } break; } - } } From 0294066668af90f684cffe1bf852b238a8549e1e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 16:41:50 -0700 Subject: [PATCH 147/264] Removed pupilData and translationRadix from AvatarData packet. --- interface/src/avatar/MyAvatar.cpp | 4 ---- interface/src/ui/PreferencesDialog.cpp | 5 ----- libraries/avatars/src/AvatarData.cpp | 28 +++++++------------------- libraries/avatars/src/HeadData.cpp | 6 ++---- libraries/avatars/src/HeadData.h | 22 +++++++++----------- 5 files changed, 18 insertions(+), 47 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ddc0407f14..6d1d80b7f6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -670,8 +670,6 @@ void MyAvatar::saveData() { settings.setValue("headPitch", getHead()->getBasePitch()); - settings.setValue("pupilDilation", getHead()->getPupilDilation()); - settings.setValue("leanScale", _leanScale); settings.setValue("scale", _targetScale); @@ -778,8 +776,6 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); - getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f)); - _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); setScale(glm::vec3(_targetScale)); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9b1146340e..cb68b36c24 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -144,11 +144,6 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } - { - auto getter = [=]()->float { return myAvatar->getHead()->getPupilDilation(); }; - auto setter = [=](float value) { myAvatar->getHead()->setPupilDilation(value); }; - preferences->addPreference(new SliderPreference(AVATAR_TUNING, "Pupil dilation", getter, setter)); - } { auto getter = []()->float { return DependencyManager::get()->getEyeClosingThreshold(); }; auto setter = [](float value) { DependencyManager::get()->setEyeClosingThreshold(value); }; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f956305c3f..9d010e3dc4 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -219,9 +219,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); } - // pupil dilation - destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f); - // joint rotation data *destinationBuffer++ = _jointData.size(); unsigned char* validityPosition = destinationBuffer; @@ -306,15 +303,11 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { } } - if (validityBit != 0) { *destinationBuffer++ = validity; } - // TODO -- automatically pick translationCompressionRadix - int translationCompressionRadix = 12; - - *destinationBuffer++ = translationCompressionRadix; + const int TRANSLATION_COMPRESSION_RADIX = 12; validityBit = 0; validity = *validityPosition++; @@ -322,7 +315,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { const JointData& data = _jointData[ i ]; if (validity & (1 << validityBit)) { destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix); + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); } if (++validityBit == BITS_IN_BYTE) { validityBit = 0; @@ -335,7 +328,6 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { qDebug() << "AvatarData::toByteArray" << cullSmallChanges << sendAll << "rotations:" << rotationSentCount << "translations:" << translationSentCount << "largest:" << maxTranslationDimension - << "radix:" << translationCompressionRadix << "size:" << (beforeRotations - startPosition) << "+" << (beforeTranslations - beforeRotations) << "+" @@ -408,7 +400,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // audioLoudness = 4 // } // + 1 byte for varying data - // + 1 byte for pupilSize // + 1 byte for numJoints (0) // = 39 bytes int minPossibleSize = 39; @@ -598,11 +589,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bitItemsDataSize bytes - { // pupil dilation - sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); - } // 1 byte - - // joint rotations int numJoints = *sourceBuffer++; @@ -650,6 +636,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } // 1 + bytesOfValidity bytes // each joint rotation is stored in 6 bytes. + const size_t COMPRESSED_QUATERNION_SIZE = 6; minPossibleSize += numValidJointRotations * COMPRESSED_QUATERNION_SIZE; if (minPossibleSize > maxAvailableSize) { @@ -699,9 +686,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bytesOfValidity bytes - // each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix + // each joint translation component is stored in 6 bytes. const size_t COMPRESSED_TRANSLATION_SIZE = 6; - minPossibleSize += numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE + 1; + minPossibleSize += numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE; if (minPossibleSize > maxAvailableSize) { if (shouldLogError(now)) { qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" @@ -712,7 +699,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { return maxAvailableSize; } - int translationCompressionRadix = *sourceBuffer++; + const int TRANSLATION_COMPRESSION_RADIX = 12; { // joint data for (int i = 0; i < numJoints; i++) { @@ -721,7 +708,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (skipJoints) { sourceBuffer += COMPRESSED_TRANSLATION_SIZE; } else { - sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); _hasNewJointTranslations = true; data.translationSet = true; } @@ -733,7 +720,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { if (numValidJointRotations > 15) { qDebug() << "RECEIVING -- rotations:" << numValidJointRotations << "translations:" << numValidJointTranslations - << "radix:" << translationCompressionRadix << "size:" << (int)(sourceBuffer - startPosition); } #endif diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index b98112d6e0..1aee85b2cd 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -43,10 +43,9 @@ HeadData::HeadData(AvatarData* owningAvatar) : _averageLoudness(0.0f), _browAudioLift(0.0f), _audioAverageLoudness(0.0f), - _pupilDilation(0.0f), _owningAvatar(owningAvatar) { - + } glm::quat HeadData::getRawOrientation() const { @@ -72,7 +71,7 @@ void HeadData::setOrientation(const glm::quat& orientation) { glm::vec3 newFront = glm::inverse(bodyOrientation) * (orientation * IDENTITY_FRONT); bodyOrientation = bodyOrientation * glm::angleAxis(atan2f(-newFront.x, -newFront.z), glm::vec3(0.0f, 1.0f, 0.0f)); _owningAvatar->setOrientation(bodyOrientation); - + // the rest goes to the head glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation)); _basePitch = eulers.x; @@ -186,4 +185,3 @@ void HeadData::fromJson(const QJsonObject& json) { } } } - diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index fef77c6f8f..535aa12847 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -34,7 +34,7 @@ class HeadData { public: explicit HeadData(AvatarData* owningAvatar); virtual ~HeadData() { }; - + // degrees float getBaseYaw() const { return _baseYaw; } void setBaseYaw(float yaw) { _baseYaw = glm::clamp(yaw, MIN_HEAD_YAW, MAX_HEAD_YAW); } @@ -42,7 +42,7 @@ public: void setBasePitch(float pitch) { _basePitch = glm::clamp(pitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); } float getBaseRoll() const { return _baseRoll; } void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } - + virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; } virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; } virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; } @@ -64,26 +64,23 @@ public: void setBlendshape(QString name, float val); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } void setBlendshapeCoefficients(const QVector& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; } - - float getPupilDilation() const { return _pupilDilation; } - void setPupilDilation(float pupilDilation) { _pupilDilation = pupilDilation; } - + const glm::vec3& getLookAtPosition() const { return _lookAtPosition; } void setLookAtPosition(const glm::vec3& lookAtPosition) { _lookAtPosition = lookAtPosition; } - - + + float getLeanSideways() const { return _leanSideways; } float getLeanForward() const { return _leanForward; } float getTorsoTwist() const { return _torsoTwist; } virtual float getFinalLeanSideways() const { return _leanSideways; } virtual float getFinalLeanForward() const { return _leanForward; } - + void setLeanSideways(float leanSideways) { _leanSideways = leanSideways; } void setLeanForward(float leanForward) { _leanForward = leanForward; } void setTorsoTwist(float torsoTwist) { _torsoTwist = torsoTwist; } - + friend class AvatarData; - + QJsonObject toJson() const; void fromJson(const QJsonObject& json); @@ -106,9 +103,8 @@ protected: float _browAudioLift; float _audioAverageLoudness; QVector _blendshapeCoefficients; - float _pupilDilation; AvatarData* _owningAvatar; - + private: // privatize copy ctor and assignment operator so copies of this object cannot be made HeadData(const HeadData&); From 8025a3f14cb7602204c02e08a7c5cafaa1ab2eb5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 16:35:07 -0700 Subject: [PATCH 148/264] Prevent crash from script timers on shutdown --- libraries/script-engine/src/ScriptEngine.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 15e896fac4..67b87ae5e0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -979,6 +979,11 @@ void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { } void ScriptEngine::timerFired() { + if (DependencyManager::get()->isStopped()) { + qCDebug(scriptengine) << "Script.timerFired() while shutting down is ignored... parent script:" << getFilename(); + return; // bail early + } + QTimer* callingTimer = reinterpret_cast(sender()); CallbackData timerData = _timerFunctionMap.value(callingTimer); From 2c9b0f7233f00938addc2031b84972f865a30d64 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 17:31:47 -0700 Subject: [PATCH 149/264] Rm failing VrMenu assertion --- libraries/ui/src/VrMenu.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 2d6113ad63..a0a1399c78 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -236,5 +236,4 @@ void VrMenu::removeAction(QAction* action) { QQuickMenuBase* qmlItem = reinterpret_cast(item); bool invokeResult = QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, Q_ARG(QQuickMenuBase*, qmlItem)); - Q_ASSERT(invokeResult); } From 107b1b830eb156077546016e2042ad0867a24679 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 17:46:12 -0700 Subject: [PATCH 150/264] Stop ScriptCache callbacks when scripts are stopped --- libraries/script-engine/src/ScriptCache.cpp | 43 ++++++++++++--------- libraries/script-engine/src/ScriptEngines.h | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 3ebd3d53ce..2114289095 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ScriptCache.h" + #include #include #include @@ -20,7 +22,7 @@ #include #include -#include "ScriptCache.h" +#include "ScriptEngines.h" #include "ScriptEngineLogging.h" ScriptCache::ScriptCache(QObject* parent) { @@ -78,22 +80,25 @@ void ScriptCache::scriptDownloaded() { QList scriptUsers = _scriptUsers.values(url); _scriptUsers.remove(url); - if (req->getResult() == ResourceRequest::Success) { - auto scriptContents = req->getData(); - _scriptCache[url] = scriptContents; - lock.unlock(); - qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); + if (!DependencyManager::get()->isStopped()) { + if (req->getResult() == ResourceRequest::Success) { + auto scriptContents = req->getData(); + _scriptCache[url] = scriptContents; + lock.unlock(); + qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); - foreach(ScriptUser* user, scriptUsers) { - user->scriptContentsAvailable(url, scriptContents); - } - } else { - lock.unlock(); - qCWarning(scriptengine) << "Error loading script from URL " << url; - foreach(ScriptUser* user, scriptUsers) { - user->errorInLoadingScript(url); + foreach(ScriptUser* user, scriptUsers) { + user->scriptContentsAvailable(url, scriptContents); + } + } else { + lock.unlock(); + qCWarning(scriptengine) << "Error loading script from URL " << url; + foreach(ScriptUser* user, scriptUsers) { + user->errorInLoadingScript(url); + } } } + req->deleteLater(); } @@ -162,9 +167,11 @@ void ScriptCache::scriptContentAvailable() { } } - foreach(contentAvailableCallback thisCallback, allCallbacks) { - thisCallback(url.toString(), scriptContent, true, success); - } req->deleteLater(); -} + if (!DependencyManager::get()->isStopped()) { + foreach(contentAvailableCallback thisCallback, allCallbacks) { + thisCallback(url.toString(), scriptContent, true, success); + } + } +} diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 72bf7d529e..a9c273b2a7 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -68,6 +68,7 @@ public: // Called at shutdown time void shutdownScripting(); + bool isStopped() const { return _isStopped; } signals: void scriptCountChanged(); @@ -86,7 +87,6 @@ protected: void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); void launchScriptEngine(ScriptEngine* engine); - bool isStopped() const { return _isStopped; } QReadWriteLock _scriptEnginesHashLock; QHash _scriptEnginesHash; From de1204c42d9bff5b4496da56595901fbbe62a5d3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 May 2016 18:19:28 -0700 Subject: [PATCH 151/264] Enable transmission of jointIndices --- libraries/avatars/src/AvatarData.cpp | 10 +++++++--- libraries/avatars/src/AvatarData.h | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 9d010e3dc4..b70c021f61 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -37,7 +37,7 @@ #include "AvatarLogging.h" -#define WANT_DEBUG +//#define WANT_DEBUG quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; @@ -984,11 +984,15 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) { #ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !identity.jointIndices.empty()) { + // build networkJointIndexMap from _jointIndices and networkJointIndices. - _networkJointIndexMap.fill(identity.jointIndices.size(), -1); + _networkJointIndexMap.fill(-1, identity.jointIndices.size()); for (auto iter = identity.jointIndices.cbegin(); iter != identity.jointIndices.end(); ++iter) { int jointIndex = getJointIndex(iter.key()); - _networkJointIndexMap[iter.value()] = jointIndex; + int networkJointIndex = iter.value(); + if (networkJointIndex >= 0 && networkJointIndex < identity.jointIndices.size()) { + _networkJointIndexMap[networkJointIndex - 1] = jointIndex; + } } } #endif diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5af66fae4e..1a1a07410d 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -128,6 +128,8 @@ enum KeyState { DELETE_KEY_DOWN }; +#define TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET + class QDataStream; class AttachmentData; From 87b102ae0f0c1ebc7e2b771fd9f1b899271b0f99 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 18:19:44 -0700 Subject: [PATCH 152/264] Fix -Wunused-result from invokeResult --- libraries/ui/src/VrMenu.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index a0a1399c78..c2732197d3 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -234,6 +234,5 @@ void VrMenu::removeAction(QAction* action) { QObject* menu = item->parent(); // Proxy QuickItem requests through the QML layer QQuickMenuBase* qmlItem = reinterpret_cast(item); - bool invokeResult = QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, - Q_ARG(QQuickMenuBase*, qmlItem)); + QMetaObject::invokeMethod(menu, "removeItem", Qt::DirectConnection, Q_ARG(QQuickMenuBase*, qmlItem)); } From e1971f5ed34f7bb692e49d1c53066277149cc2a9 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 May 2016 18:58:28 -0700 Subject: [PATCH 153/264] Fix shadow bounds checking --- libraries/render-utils/src/RenderShadowTask.cpp | 9 ++++++--- libraries/render-utils/src/Shadow.slh | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index f695e2d04c..5680660122 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -52,17 +52,19 @@ void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const batch.setFramebuffer(fbo); batch.clearFramebuffer( gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 1.0), 1.0, 0, true); + vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true); batch.setProjectionTransform(shadow.getProjection()); batch.setViewTransform(shadow.getView()); auto shadowPipeline = _shapePlumber->pickPipeline(args, ShapeKey()); auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, ShapeKey::Builder().withSkinned()); - args->_pipeline = shadowPipeline; - batch.setPipeline(shadowPipeline->pipeline); std::vector skinnedShapeKeys{}; + + // Iterate through all inShapes and render the unskinned + args->_pipeline = shadowPipeline; + batch.setPipeline(shadowPipeline->pipeline); for (auto items : inShapes) { if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); @@ -71,6 +73,7 @@ void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const } } + // Reiterate to render the skinned args->_pipeline = shadowSkinnedPipeline; batch.setPipeline(shadowSkinnedPipeline->pipeline); for (const auto& key : skinnedShapeKeys) { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 43a20057cd..7b86b9b660 100755 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -87,7 +87,8 @@ float evalShadowAttenuationPCF(vec4 position, vec4 shadowTexcoord) { float evalShadowAttenuation(vec4 position) { vec4 shadowTexcoord = evalShadowTexcoord(position); if (shadowTexcoord.x < 0.0 || shadowTexcoord.x > 1.0 || - shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0) { + shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0 || + shadowTexcoord.z < 0.0 || shadowTexcoord.z > 1.0) { // If a point is not in the map, do not attenuate return 1.0; } From 917fe2ae6a0154ac16a1b3b8fcd6d5ea947218f0 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 18 May 2016 19:09:25 -0700 Subject: [PATCH 154/264] Give AC audio searcher invalid avatar URL --- .../audioExamples/acAudioSearching/ACAudioSearchAndInject.js | 1 + 1 file changed, 1 insertion(+) diff --git a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js index de7baba267..381a7ee902 100644 --- a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js +++ b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js @@ -40,6 +40,7 @@ var DEFAULT_SOUND_DATA = { Script.include("../../libraries/utils.js"); Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList. +Avatar.skeletonModelURL = "http://invalid-url"; function ignore() {} function debug() { // Display the arguments not just [Object object]. //print.apply(null, [].map.call(arguments, JSON.stringify)); From 585467094c58e177c59e9da30b18e5b77bcf8d7b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 19 May 2016 14:16:00 +1200 Subject: [PATCH 155/264] Sort files case-insensitively, directories first --- .../resources/qml/dialogs/FileDialog.qml | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index fbe8df79c0..e7d96cedf9 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -238,6 +238,8 @@ ModalWindow { // drive information when viewing at the computer level. property var folder + property int sortOrder: Qt.AscendingOrder + property int sortColumn: 0 onFolderChanged: folderListModel.folder = folder; @@ -249,16 +251,61 @@ ModalWindow { } function update() { - var i; + var dataFields = ["fileName", "fileModified", "fileSize"], + sortFields = ["fileNameSort", "fileModified", "fileSize"], + dataField = dataFields[sortColumn], + sortField = sortFields[sortColumn], + sortValue, + fileName, + fileIsDir, + comparisonFunction, + lower, + middle, + upper, + rows = 0, + i; clear(); + + comparisonFunction = sortOrder === Qt.AscendingOrder + ? function(a, b) { return a < b; } + : function(a, b) { return a > b; } + for (i = 0; i < folderListModel.count; i++) { - append({ - fileName: folderListModel.get(i, "fileName"), - fileModified: folderListModel.get(i, "fileModified"), + fileName = folderListModel.get(i, "fileName"); + fileIsDir = folderListModel.get(i, "fileIsDir"); + + sortValue = folderListModel.get(i, dataField); + + if (dataField === "fileName") { + // Directories first by prefixing a "*". + // Case-insensitive. + sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase(); + } + + lower = 0; + upper = rows; + while (lower < upper) { + middle = Math.floor((lower + upper) / 2); + var lessThan; + if (comparisonFunction(sortValue, get(middle)[sortField])) { + lessThan = true; + upper = middle; + } else { + lessThan = false; + lower = middle + 1; + } + } + + insert(lower, { + fileName: fileName, + fileModified: (fileIsDir ? new Date(0) : folderListModel.get(i, "fileModified")), fileSize: folderListModel.get(i, "fileSize"), filePath: folderListModel.get(i, "filePath"), - fileIsDir: folderListModel.get(i, "fileIsDir") + fileIsDir: fileIsDir, + fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() }); + + rows++; } } } @@ -287,10 +334,9 @@ ModalWindow { model: fileTableModel function updateSort() { - model.sortReversed = sortIndicatorColumn == 0 - ? (sortIndicatorOrder == Qt.DescendingOrder) - : (sortIndicatorOrder == Qt.AscendingOrder); // Date and size fields have opposite sense - model.sortField = sortIndicatorColumn + 1; + model.sortOrder = sortIndicatorOrder; + model.sortColumn = sortIndicatorColumn; + model.update(); } onSortIndicatorColumnChanged: { updateSort(); } From 41311bc3df3859be96291d7a06fb44605f98146b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 19 May 2016 17:13:07 +1200 Subject: [PATCH 156/264] Finish decoupling FolderListModel --- .../resources/qml/dialogs/FileDialog.qml | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index e7d96cedf9..5674123b4d 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -43,7 +43,7 @@ ModalWindow { // Set from OffscreenUi::getOpenFile() property alias caption: root.title; // Set from OffscreenUi::getOpenFile() - property alias dir: folderListModel.folder; + property alias dir: fileTableModel.folder; // Set from OffscreenUi::getOpenFile() property alias filter: selectionType.filtersString; // Set from OffscreenUi::getOpenFile() @@ -75,8 +75,9 @@ ModalWindow { // HACK: The following two lines force the model to initialize properly such that: // - Selecting a different drive at the initial screen updates the path displayed. // - The go-up button works properly from the initial screen. + var initialFolder = currentDirectory.lastValidFolder; root.dir = helper.pathToUrl(drivesSelector.currentText); - root.dir = helper.pathToUrl(currentDirectory.lastValidFolder); + root.dir = helper.pathToUrl(initialFolder); iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; } @@ -110,7 +111,7 @@ ModalWindow { glyph: hifi.glyphs.levelUp width: height size: 30 - enabled: folderListModel.parentFolder && folderListModel.parentFolder !== "" + enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" onClicked: d.navigateUp(); } @@ -135,7 +136,7 @@ ModalWindow { TextField { id: currentDirectory - property var lastValidFolder: helper.urlToPath(folderListModel.folder) + property var lastValidFolder: helper.urlToPath(fileTableModel.folder) height: homeButton.height anchors { top: parent.top @@ -161,7 +162,7 @@ ModalWindow { text = lastValidFolder; return } - folderListModel.folder = helper.pathToUrl(text); + fileTableModel.folder = helper.pathToUrl(text); } } @@ -172,15 +173,13 @@ ModalWindow { property bool currentSelectionIsFolder; property var backStack: [] property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } - property var modelConnection: Connections { target: model; onFolderChanged: d.update(); } // DJRTODO + property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } property var homeDestination: helper.home(); - Component.onCompleted: update(); function update() { var row = fileTableView.currentRow; - if (row === -1 && root.selectDirectory) { - currentSelectionUrl = fileTableView.model.folder; - currentSelectionIsFolder = true; + + if (row === -1) { return; } @@ -189,19 +188,21 @@ ModalWindow { if (root.selectDirectory || !currentSelectionIsFolder) { currentSelection.text = helper.urlToPath(currentSelectionUrl); } else { - currentSelection.text = "" + currentSelection.text = ""; } } function navigateUp() { - if (folderListModel.parentFolder && folderListModel.parentFolder !== "") { - folderListModel.folder = folderListModel.parentFolder + if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { + fileTableModel.folder = fileTableModel.parentFolder return true; + } else if (true) { + } } function navigateHome() { - folderListModel.folder = homeDestination; + fileTableModel.folder = homeDestination; return true; } } @@ -215,15 +216,11 @@ ModalWindow { // For some reason, declaring these bindings directly in the targets doesn't // work for setting the initial state Component.onCompleted: { - currentDirectory.lastValidFolder = Qt.binding(function() { return helper.urlToPath(folder); }); - upButton.enabled = Qt.binding(function() { return (parentFolder && parentFolder != "") ? true : false; }); + currentDirectory.lastValidFolder = helper.urlToPath(folder); showFiles = !root.selectDirectory } onFolderChanged: { - fileTableModel.update(); - fileTableView.selection.clear(); - fileTableView.selection.select(0); - fileTableView.currentRow = 0; + fileTableModel.update(); // Update once the data from the folder change is available. } } @@ -240,8 +237,12 @@ ModalWindow { property var folder property int sortOrder: Qt.AscendingOrder property int sortColumn: 0 + property string parentFolder: folderListModel.parentFolder - onFolderChanged: folderListModel.folder = folder; + onFolderChanged: { + folderListModel.folder = folder; + currentDirectory.lastValidFolder = helper.urlToPath(folder); + } function isFolder(row) { if (row === -1) { From 7efcad38d2a0816fdac09fdccfd97b601b877097 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 May 2016 22:29:07 -0700 Subject: [PATCH 157/264] PR feedback --- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 8 ++-- libraries/gpu-gl/src/gpu/gl/GLQuery.cpp | 12 ------ libraries/gpu-gl/src/gpu/gl/GLShared.h | 8 ---- libraries/gpu-gl/src/gpu/gl/GLTexture.cpp | 2 +- libraries/gpu-gl/src/gpu/gl/GLTexture.h | 7 ++++ .../gpu-gl/src/gpu/gl/GLTextureTransfer.cpp | 1 + .../gpu-gl/src/gpu/gl/GLTextureTransfer.h | 1 - .../gl41/{GLBackend.cpp => GL41Backend.cpp} | 14 +++---- .../gpu/gl41/{GLBackend.h => GL41Backend.h} | 18 ++++----- ...ackendBuffer.cpp => GL41BackendBuffer.cpp} | 14 +++---- ...LBackendInput.cpp => GL41BackendInput.cpp} | 10 ++--- ...ackendOutput.cpp => GL41BackendOutput.cpp} | 22 +++++------ ...LBackendQuery.cpp => GL41BackendQuery.cpp} | 16 ++++---- ...kendTexture.cpp => GL41BackendTexture.cpp} | 39 +++++++++---------- ...Transform.cpp => GL41BackendTransform.cpp} | 8 ++-- libraries/gpu/src/gpu/Forward.h | 4 +- 16 files changed, 84 insertions(+), 100 deletions(-) delete mode 100644 libraries/gpu-gl/src/gpu/gl/GLQuery.cpp rename libraries/gpu-gl/src/gpu/gl41/{GLBackend.cpp => GL41Backend.cpp} (93%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackend.h => GL41Backend.h} (86%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendBuffer.cpp => GL41BackendBuffer.cpp} (81%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendInput.cpp => GL41BackendInput.cpp} (96%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendOutput.cpp => GL41BackendOutput.cpp} (86%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendQuery.cpp => GL41BackendQuery.cpp} (62%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendTexture.cpp => GL41BackendTexture.cpp} (86%) rename libraries/gpu-gl/src/gpu/gl41/{GLBackendTransform.cpp => GL41BackendTransform.cpp} (95%) diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index e5460c6641..4d264995ae 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -16,7 +16,7 @@ #include #include -#include "../gl41/GLBackend.h" +#include "../gl41/GL41Backend.h" #if defined(NSIGHT_FOUND) #include "nvToolsExt.h" @@ -36,11 +36,11 @@ static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); static bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); Backend* GLBackend::createBackend() { - auto version = QOpenGLContextWrapper::currentContextVersion(); +#if 0 // FIXME provide a mechanism to override the backend for testing // Where the gpuContext is initialized and where the TRUE Backend is created and assigned -#if 0 + auto version = QOpenGLContextWrapper::currentContextVersion(); GLBackend* result; if (enableOpenGL45 && version >= 0x0405) { result = new gpu::gl45::GLBackend; @@ -48,7 +48,7 @@ Backend* GLBackend::createBackend() { result = new gpu::gl41::GLBackend; } #else - GLBackend* result = new gpu::gl41::GLBackend; + GLBackend* result = new gpu::gl41::GL41Backend; #endif result->initInput(); result->initTransform(); diff --git a/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp b/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp deleted file mode 100644 index b358db40c9..0000000000 --- a/libraries/gpu-gl/src/gpu/gl/GLQuery.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/05/15 -// Copyright 2013-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 -// - -#include "GLQuery.h" - -using namespace gpu; -using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index 5d90badc1c..3220eafef4 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -128,13 +128,6 @@ public: virtual ~GLObject() { } - // Used by derived classes and helpers to ensure the actual GL object exceeds the lifetime of `this` - GLuint takeOwnership() { - GLuint result = _id; - const_cast(_id) = 0; - return result; - } - const GPUType& _gpuObject; const GLuint _id; }; @@ -146,7 +139,6 @@ class GLQuery; class GLState; class GLShader; class GLTexture; -class GLTextureTransferHelper; } } // namespace gpu::gl diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp index 47fc583d77..ed931437b7 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp @@ -182,7 +182,7 @@ GLTexture::~GLTexture() { if (0 == numTexturesForMipCount) { _textureCountByMips.erase(mipCount); if (mipCount == _currentMaxMipCount) { - _currentMaxMipCount = _textureCountByMips.rbegin()->first; + _currentMaxMipCount = (_textureCountByMips.empty() ? 0 : _textureCountByMips.rbegin()->first); } } } diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.h b/libraries/gpu-gl/src/gpu/gl/GLTexture.h index fa09fb49f2..df2d38e2f3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexture.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.h @@ -102,6 +102,13 @@ public: return result; } + // Used by derived classes and helpers to ensure the actual GL object exceeds the lifetime of `this` + GLuint takeOwnership() { + GLuint result = _id; + const_cast(_id) = 0; + return result; + } + ~GLTexture(); const GLuint& _texture { _id }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp index ca2e7061f5..7acb736063 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.cpp @@ -13,6 +13,7 @@ #endif #include "GLShared.h" +#include "GLTexture.h" using namespace gpu; using namespace gpu::gl; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h index deb470c572..078ab40ee3 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h +++ b/libraries/gpu-gl/src/gpu/gl/GLTextureTransfer.h @@ -14,7 +14,6 @@ #include #include "GLShared.h" -#include "GLTexture.h" #ifdef Q_OS_WIN #define THREADED_TEXTURE_TRANSFER diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp similarity index 93% rename from libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp index e46c739593..93d87ee6e4 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.cpp @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" #include #include @@ -18,7 +18,7 @@ Q_LOGGING_CATEGORY(gpugl41logging, "hifi.gpu.gl41") using namespace gpu; using namespace gpu::gl41; -void GLBackend::do_draw(Batch& batch, size_t paramOffset) { +void GL41Backend::do_draw(Batch& batch, size_t paramOffset) { Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; uint32 numVertices = batch._params[paramOffset + 1]._uint; @@ -43,7 +43,7 @@ void GLBackend::do_draw(Batch& batch, size_t paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { +void GL41Backend::do_drawIndexed(Batch& batch, size_t paramOffset) { Primitive primitiveType = (Primitive)batch._params[paramOffset + 2]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; uint32 numIndices = batch._params[paramOffset + 1]._uint; @@ -72,7 +72,7 @@ void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { (void) CHECK_GL_ERROR(); } -void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { +void GL41Backend::do_drawInstanced(Batch& batch, size_t paramOffset) { GLint numInstances = batch._params[paramOffset + 4]._uint; Primitive primitiveType = (Primitive)batch._params[paramOffset + 3]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[primitiveType]; @@ -108,7 +108,7 @@ void glbackend_glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsize #endif } -void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { +void GL41Backend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { GLint numInstances = batch._params[paramOffset + 4]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 3]._uint]; uint32 numIndices = batch._params[paramOffset + 2]._uint; @@ -143,7 +143,7 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { } -void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { +void GL41Backend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { #if (GPU_INPUT_PROFILE == GPU_CORE_43) uint commandCount = batch._params[paramOffset + 0]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; @@ -159,7 +159,7 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { } -void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { +void GL41Backend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { #if (GPU_INPUT_PROFILE == GPU_CORE_43) uint commandCount = batch._params[paramOffset + 0]._uint; GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h similarity index 86% rename from libraries/gpu-gl/src/gpu/gl41/GLBackend.h rename to libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 8a0f4bb912..5695ba080e 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -1,5 +1,5 @@ // -// GLBackend.h +// GL41Backend.h // libraries/gpu/src/gpu // // Created by Sam Gateau on 10/27/2014. @@ -8,8 +8,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_gpu_41_GLBackend_h -#define hifi_gpu_41_GLBackend_h +#ifndef hifi_gpu_41_GL41Backend_h +#define hifi_gpu_41_GL41Backend_h #include @@ -27,21 +27,21 @@ namespace gpu { namespace gl41 { -class GLBackend : public gl::GLBackend { +class GL41Backend : public gl::GLBackend { using Parent = gl::GLBackend; // Context Backend static interface required friend class Context; public: - explicit GLBackend(bool syncCache) : Parent(syncCache) {} - GLBackend() : Parent() {} + explicit GL41Backend(bool syncCache) : Parent(syncCache) {} + GL41Backend() : Parent() {} - class GLTexture : public gpu::gl::GLTexture { + class GL41Texture : public gpu::gl::GLTexture { using Parent = gpu::gl::GLTexture; GLuint allocate(); public: - GLTexture(const Texture& buffer, bool transferrable); - GLTexture(const Texture& buffer, GLTexture* original); + GL41Texture(const Texture& buffer, bool transferrable); + GL41Texture(const Texture& buffer, GL41Texture* original); protected: void transferMip(uint16_t mipLevel, uint8_t face = 0) const; diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp similarity index 81% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index 9c6b8b8124..e56d671891 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -5,13 +5,13 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" #include "../gl/GLBuffer.h" using namespace gpu; using namespace gpu::gl41; -class GLBuffer : public gl::GLBuffer { +class GL41Buffer : public gl::GLBuffer { using Parent = gpu::gl::GLBuffer; static GLuint allocate() { GLuint result; @@ -20,7 +20,7 @@ class GLBuffer : public gl::GLBuffer { } public: - GLBuffer(const Buffer& buffer, GLBuffer* original) : Parent(buffer, allocate()) { + GL41Buffer(const Buffer& buffer, GL41Buffer* original) : Parent(buffer, allocate()) { glBindBuffer(GL_ARRAY_BUFFER, _buffer); glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -53,10 +53,10 @@ public: } }; -GLuint GLBackend::getBufferID(const Buffer& buffer) { - return GLBuffer::getId(buffer); +GLuint GL41Backend::getBufferID(const Buffer& buffer) { + return GL41Buffer::getId(buffer); } -gl::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { - return GLBuffer::sync(buffer); +gl::GLBuffer* GL41Backend::syncGPUObject(const Buffer& buffer) { + return GL41Buffer::sync(buffer); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp similarity index 96% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 0486d1cfd2..1b0caa7345 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -1,5 +1,5 @@ // -// GLBackendInput.cpp +// GL41BackendInput.cpp // libraries/gpu/src/gpu // // Created by Sam Gateau on 3/8/2015. @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" using namespace gpu; using namespace gpu::gl41; @@ -22,7 +22,7 @@ using namespace gpu::gl41; #define SUPPORT_VERTEX_ATTRIB_FORMAT #endif -void GLBackend::updateInput() { +void GL41Backend::updateInput() { #if defined(SUPPORT_VERTEX_ATTRIB_FORMAT) if (_input._invalidFormat) { @@ -36,7 +36,7 @@ void GLBackend::updateInput() { GLuint slot = attrib._slot; GLuint count = attrib._element.getLocationScalarCount(); uint8_t locationCount = attrib._element.getLocationCount(); - GLenum type = _elementTypeToGLType[attrib._element.getType()]; + GLenum type = _elementTypeToGL41Type[attrib._element.getType()]; GLuint offset = attrib._offset;; GLboolean isNormalized = attrib._element.isNormalized(); @@ -142,7 +142,7 @@ void GLBackend::updateInput() { int bufferNum = (channelIt).first; if (_input._invalidBuffers.test(bufferNum) || _input._invalidFormat) { - // GLuint vbo = gpu::GLBackend::getBufferID((*buffers[bufferNum])); + // GLuint vbo = gpu::GL41Backend::getBufferID((*buffers[bufferNum])); GLuint vbo = _input._bufferVBOs[bufferNum]; if (boundVBO != vbo) { glBindBuffer(GL_ARRAY_BUFFER, vbo); diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp similarity index 86% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp index a34a0aaebd..53c2c75394 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -1,5 +1,5 @@ // -// GLBackendTexture.cpp +// GL41BackendTexture.cpp // libraries/gpu/src/gpu // // Created by Sam Gateau on 1/19/2015. @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" #include @@ -17,7 +17,7 @@ namespace gpu { namespace gl41 { -class GLFramebuffer : public gl::GLFramebuffer { +class GL41Framebuffer : public gl::GLFramebuffer { using Parent = gl::GLFramebuffer; static GLuint allocate() { GLuint result; @@ -56,7 +56,7 @@ public: for (auto& b : _gpuObject.getRenderBuffers()) { surface = b._texture; if (surface) { - gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer } else { gltexture = nullptr; } @@ -83,7 +83,7 @@ public: if (_gpuObject.getDepthStamp() != _depthStamp) { auto surface = _gpuObject.getDepthStencilBuffer(); if (_gpuObject.hasDepthStencil() && surface) { - gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer + gltexture = gl::GLTexture::sync(surface, false); // Grab the gltexture and don't transfer } if (gltexture) { @@ -115,19 +115,19 @@ public: public: - GLFramebuffer(const gpu::Framebuffer& framebuffer) + GL41Framebuffer(const gpu::Framebuffer& framebuffer) : Parent(framebuffer, allocate()) { } }; -gl::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { - return GLFramebuffer::sync(framebuffer); +gl::GLFramebuffer* GL41Backend::syncGPUObject(const Framebuffer& framebuffer) { + return GL41Framebuffer::sync(framebuffer); } -GLuint GLBackend::getFramebufferID(const FramebufferPointer& framebuffer) { - return framebuffer ? GLFramebuffer::getId(*framebuffer) : 0; +GLuint GL41Backend::getFramebufferID(const FramebufferPointer& framebuffer) { + return framebuffer ? GL41Framebuffer::getId(*framebuffer) : 0; } -void GLBackend::do_blit(Batch& batch, size_t paramOffset) { +void GL41Backend::do_blit(Batch& batch, size_t paramOffset) { auto srcframebuffer = batch._framebuffers.get(batch._params[paramOffset]._uint); Vec4i srcvp; for (auto i = 0; i < 4; ++i) { diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp similarity index 62% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp index 4915adbc1d..3c6109bbdf 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendQuery.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendQuery.cpp @@ -1,5 +1,5 @@ // -// GLBackendQuery.cpp +// GL41BackendQuery.cpp // libraries/gpu/src/gpu // // Created by Sam Gateau on 7/7/2015. @@ -8,14 +8,14 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" #include "../gl/GLQuery.h" using namespace gpu; using namespace gpu::gl41; -class GLQuery : public gpu::gl::GLQuery { +class GL41Query : public gpu::gl::GLQuery { using Parent = gpu::gl::GLBuffer; public: static GLuint allocateQuery() { @@ -24,14 +24,14 @@ public: return result; } - GLQuery(const Query& query) + GL41Query(const Query& query) : gl::GLQuery(query, allocateQuery()) { } }; -gl::GLQuery* GLBackend::syncGPUObject(const Query& query) { - return GLQuery::sync(query); +gl::GLQuery* GL41Backend::syncGPUObject(const Query& query) { + return GL41Query::sync(query); } -GLuint GLBackend::getQueryID(const QueryPointer& query) { - return GLQuery::getId(query); +GLuint GL41Backend::getQueryID(const QueryPointer& query) { + return GL41Query::getId(query); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp similarity index 86% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index f5e8531ddc..326a63c01a 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -1,5 +1,5 @@ // -// GLBackendTexture.cpp +// GL41BackendTexture.cpp // libraries/gpu/src/gpu // // Created by Sam Gateau on 1/19/2015. @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" #include #include @@ -19,29 +19,29 @@ using namespace gpu; using namespace gpu::gl41; -using GLTexelFormat = gl::GLTexelFormat; -using GLTexture = GLBackend::GLTexture; +using GL41TexelFormat = gl::GLTexelFormat; +using GL41Texture = GL41Backend::GL41Texture; -GLuint GLTexture::allocate() { +GLuint GL41Texture::allocate() { Backend::incrementTextureGPUCount(); GLuint result; glGenTextures(1, &result); return result; } -GLuint GLBackend::getTextureID(const TexturePointer& texture, bool transfer) { - return GLTexture::getId(texture, transfer); +GLuint GL41Backend::getTextureID(const TexturePointer& texture, bool transfer) { + return GL41Texture::getId(texture, transfer); } -gl::GLTexture* GLBackend::syncGPUObject(const TexturePointer& texture, bool transfer) { - return GLTexture::sync(texture, transfer); +gl::GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transfer) { + return GL41Texture::sync(texture, transfer); } -GLTexture::GLTexture(const Texture& texture, bool transferrable) : gl::GLTexture(texture, allocate(), transferrable) {} +GL41Texture::GL41Texture(const Texture& texture, bool transferrable) : gl::GLTexture(texture, allocate(), transferrable) {} -GLTexture::GLTexture(const Texture& texture, GLTexture* original) : gl::GLTexture(texture, allocate(), original) {} +GL41Texture::GL41Texture(const Texture& texture, GL41Texture* original) : gl::GLTexture(texture, allocate(), original) {} -void GLBackend::GLTexture::withPreservedTexture(std::function f) const { +void GL41Backend::GL41Texture::withPreservedTexture(std::function f) const { GLint boundTex = -1; switch (_target) { case GL_TEXTURE_2D: @@ -63,14 +63,14 @@ void GLBackend::GLTexture::withPreservedTexture(std::function f) const (void)CHECK_GL_ERROR(); } -void GLBackend::GLTexture::generateMips() const { +void GL41Backend::GL41Texture::generateMips() const { withPreservedTexture([&] { glGenerateMipmap(_target); }); (void)CHECK_GL_ERROR(); } -void GLBackend::GLTexture::allocateStorage() const { +void GL41Backend::GL41Texture::allocateStorage() const { gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, 0); (void)CHECK_GL_ERROR(); @@ -93,7 +93,7 @@ void GLBackend::GLTexture::allocateStorage() const { } } -void GLBackend::GLTexture::updateSize() const { +void GL41Backend::GL41Texture::updateSize() const { setSize(_virtualSize); if (!_id) { return; @@ -129,7 +129,7 @@ void GLBackend::GLTexture::updateSize() const { } // Move content bits from the CPU to the GPU for a given mip / face -void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { +void GL41Backend::GL41Texture::transferMip(uint16_t mipLevel, uint8_t face) const { auto mip = _gpuObject.accessStoredMipFace(mipLevel, face); gl::GLTexelFormat texelFormat = gl::GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat(), mip->getFormat()); //GLenum target = getFaceTargets()[face]; @@ -141,7 +141,7 @@ void GLBackend::GLTexture::transferMip(uint16_t mipLevel, uint8_t face) const { // This should never happen on the main thread // Move content bits from the CPU to the GPU -void GLBackend::GLTexture::transfer() const { +void GL41Backend::GL41Texture::transfer() const { PROFILE_RANGE(__FUNCTION__); //qDebug() << "Transferring texture: " << _privateTexture; // Need to update the content of the GPU object from the source sysmem of the texture @@ -208,11 +208,8 @@ void GLBackend::GLTexture::transfer() const { } } -void GLBackend::GLTexture::syncSampler() const { +void GL41Backend::GL41Texture::syncSampler() const { const Sampler& sampler = _gpuObject.getSampler(); - Texture::Type type = _gpuObject.getType(); - auto object = this; - const auto& fm = FILTER_MODES[sampler.getFilter()]; glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, fm.minFilter); glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); diff --git a/libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp similarity index 95% rename from libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp rename to libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index 6601eef86e..89b5db34c0 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GLBackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -1,5 +1,5 @@ // -// GLBackendTransform.cpp +// GL41BackendTransform.cpp // libraries/gpu/src/gpu // // Created by Sam Gateau on 3/8/2015. @@ -8,12 +8,12 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "GLBackend.h" +#include "GL41Backend.h" using namespace gpu; using namespace gpu::gl41; -void GLBackend::initTransform() { +void GL41Backend::initTransform() { glGenBuffers(1, &_transform._objectBuffer); glGenBuffers(1, &_transform._cameraBuffer); glGenBuffers(1, &_transform._drawCallInfoBuffer); @@ -24,7 +24,7 @@ void GLBackend::initTransform() { } } -void GLBackend::transferTransformState(const Batch& batch) const { +void GL41Backend::transferTransformState(const Batch& batch) const { // FIXME not thread safe static std::vector bufferData; if (!_transform._cameras.empty()) { diff --git a/libraries/gpu/src/gpu/Forward.h b/libraries/gpu/src/gpu/Forward.h index 83645f73e3..7fe6739ff7 100644 --- a/libraries/gpu/src/gpu/Forward.h +++ b/libraries/gpu/src/gpu/Forward.h @@ -87,11 +87,11 @@ namespace gpu { } namespace gl41 { - class GLBackend; + class GL41Backend; } namespace gl45 { - class GLBackend; + class GL45Backend; } } From 53956c8210b1f93363e5c3f12deee002bb4234e7 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 19 May 2016 07:50:06 -0700 Subject: [PATCH 158/264] reduce log spam --- scripts/system/controllers/handControllerPointer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 921c55b7b2..78b7c4eb84 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -186,7 +186,9 @@ function updateSeeking() { averageMouseVelocity = lastIntegration = 0; var lookAt2D = HMD.getHUDLookAtPosition2D(); if (!lookAt2D) { - print('Cannot seek without lookAt position'); + // FIXME - determine if this message is useful but make it so it doesn't spam the + // log in the case that it is happening + //print('Cannot seek without lookAt position'); return; } // E.g., if parallel to location in HUD var copy = Reticle.position; @@ -420,7 +422,9 @@ function update() { var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { - print('Controller is parallel to HUD'); + // FIXME - determine if this message is useful but make it so it doesn't spam the + // log in the case that it is happening + //print('Controller is parallel to HUD'); return turnOffVisualization(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); From 2d7118dacc87260faca4f232727823ca7b8985d2 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 19 May 2016 10:21:00 -0700 Subject: [PATCH 159/264] clening up the code --- libraries/render-utils/src/DeferredLightingEffect.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index fd7826070d..0a562de3ba 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -468,11 +468,6 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo auto& part = mesh->getPartBuffer().get(0); batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); } - // keep for debug - /* { - auto& part = mesh->getPartBuffer().get(1); - batch.drawIndexed(model::Mesh::topologyToPrimitive(part._topology), part._numIndices, part._startIndex); - }*/ } } } @@ -555,7 +550,6 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo state->setDepthTest(true, false, gpu::LESS_EQUAL); // TODO: We should use DepthClamp and avoid changing geometry for inside /outside cases - //state->setDepthClampEnable(true); // additive blending state->setBlendFunction(true, gpu::State::ONE, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -665,8 +659,7 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { std::vector parts; parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::TRIANGLES)); - //DEBUG: - parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::LINE_STRIP)); + parts.push_back(model::Mesh::Part(0, indices, 0, model::Mesh::LINE_STRIP)); // outline version _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part), (gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL)); From 7ea7bd3e1dacd21702452421cc0737a89fd1aea7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 19 May 2016 10:21:04 -0700 Subject: [PATCH 160/264] set created-time for imported entities to 'now' --- libraries/entities/src/EntityTree.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 30c37d46b1..97d4e91acd 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1376,6 +1376,10 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra item->globalizeProperties(properties, "Cannot find %3 parent of %2 %1", args->root); } } + + // set creation time to "now" for imported entities + properties.setCreated(usecTimestampNow()); + properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity // queue the packet to send to the server From 9a4b30a50bb0c3178de83b6e53fbbf2d090cb4b5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 19 May 2016 12:52:32 -0700 Subject: [PATCH 161/264] Fix AvatarInputs phantom movement --- interface/src/Application.cpp | 16 ++++++++-------- interface/src/Application.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9e3bd0c69c..cf14d01771 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1510,17 +1510,17 @@ void Application::paintGL() { renderArgs._context->syncCache(); } - if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + auto inputs = AvatarInputs::getInstance(); + if (inputs->mirrorVisible()) { PerformanceTimer perfTimer("Mirror"); auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; renderArgs._blitFramebuffer = DependencyManager::get()->getSelfieFramebuffer(); - auto inputs = AvatarInputs::getInstance(); _mirrorViewRect.moveTo(inputs->x(), inputs->y()); - renderRearViewMirror(&renderArgs, _mirrorViewRect); + renderRearViewMirror(&renderArgs, _mirrorViewRect, inputs->mirrorZoomed()); renderArgs._blitFramebuffer.reset(); renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; @@ -1839,6 +1839,9 @@ bool Application::event(QEvent* event) { idle(); return true; } else if ((int)event->type() == (int)Paint) { + // NOTE: This must be updated as close to painting as possible, + // or AvatarInputs will mysteriously move to the bottom-right + AvatarInputs::getInstance()->update(); justPresented = true; paintGL(); return true; @@ -2670,9 +2673,6 @@ void Application::idle() { firstIdle = false; connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); - } else { - // FIXME: AvatarInputs are positioned incorrectly if instantiated before the first paint - AvatarInputs::getInstance()->update(); } PROFILE_RANGE(__FUNCTION__); @@ -4101,7 +4101,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se activeRenderingThread = nullptr; } -void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region) { +void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) { auto originalViewport = renderArgs->_viewport; // Grab current viewport to reset it at the end @@ -4111,7 +4111,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi auto myAvatar = getMyAvatar(); // bool eyeRelativeCamera = false; - if (!AvatarInputs::getInstance()->mirrorZoomed()) { + if (!isZoomed) { _mirrorCamera.setPosition(myAvatar->getChestPosition() + myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5616722c92..28dbcead47 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -342,7 +342,7 @@ private: glm::vec3 getSunDirection() const; - void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region); + void renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed); int sendNackPackets(); From 8381e74fb3478750362de176aa0710b369469c0f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 19 May 2016 13:56:40 -0700 Subject: [PATCH 162/264] OpenVRDispalyPlugin: fix one-frame lag in resetSensors. resetSensors() should take place immediately, so that the getHeadPose() after the reset should be identity. (or close to it) --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 38719fdca5..1f6b11f862 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -122,7 +122,19 @@ void OpenVrDisplayPlugin::customizeContext() { void OpenVrDisplayPlugin::resetSensors() { Lock lock(_poseMutex); glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + + glm::mat4 oldSensorResetMat = _sensorResetMat; _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + + glm::mat4 undoRedoMat = _sensorResetMat * glm::inverse(oldSensorResetMat); + + // update the device poses, by undoing the previous sensorResetMatrix then applying the new one. + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + _trackedDevicePoseMat4[i] = undoRedoMat * _trackedDevicePoseMat4[i]; + _trackedDeviceLinearVelocities[i] = transformVectorFast(undoRedoMat, _trackedDeviceLinearVelocities[i]); + _trackedDeviceAngularVelocities[i] = transformVectorFast(undoRedoMat, _trackedDeviceAngularVelocities[i]); + } + _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; } From 4e862941cb9307ebbcfb88c1a14e463c62a854c4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 19 May 2016 14:51:56 -0700 Subject: [PATCH 163/264] fix a race when restarting scripts -- avoid the old not-yet-stopped script from being considered the restart script --- libraries/script-engine/src/ScriptEngine.h | 3 +++ libraries/script-engine/src/ScriptEngines.cpp | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 80978e4527..b576e2a37f 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -146,6 +146,8 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + bool isStopping() const { return _isStopping; } + void flagAsStopping() { _isStopping = true; } bool isDebuggable() const { return _debuggable; } @@ -189,6 +191,7 @@ protected: QString _parentURL; std::atomic _isFinished { false }; std::atomic _isRunning { false }; + std::atomic _isStopping { false }; int _evaluatesPending { 0 }; bool _isInitialized { false }; QHash _timerFunctionMap; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 70eb055d22..5a2beb901d 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -351,6 +351,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } + it.value()->flagAsStopping(); QMetaObject::invokeMethod(it.value(), "stop"); //it.value()->stop(); qCDebug(scriptengine) << "stopping script..." << it.key(); @@ -369,6 +370,7 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { if (_scriptEnginesHash.contains(scriptURL)) { ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURL]; if (restart) { + scriptEngine->flagAsStopping(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) { @@ -454,7 +456,7 @@ ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { QReadLocker lock(&_scriptEnginesHashLock); const QUrl scriptURL = normalizeScriptURL(rawScriptURL); auto it = _scriptEnginesHash.find(scriptURL); - if (it != _scriptEnginesHash.end()) { + if (it != _scriptEnginesHash.end() && !it.value()->isStopping()) { result = it.value(); } } From 7f131e2c4fe06eb2d2faf3901ebcece4ec3c14ec Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 19 May 2016 15:00:08 -0700 Subject: [PATCH 164/264] Reset avatar as well as displayPlugin when "driving" --- interface/src/avatar/MyAvatar.cpp | 6 ++++-- interface/src/avatar/MyAvatar.h | 2 +- interface/src/ui/OverlayConductor.cpp | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a3cf1f9f4f..183a24f252 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -234,7 +234,7 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { return AvatarData::toByteArray(cullSmallChanges, sendAll); } -void MyAvatar::reset(bool andRecenter) { +void MyAvatar::reset(bool andRecenter, bool andReload) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter)); @@ -244,7 +244,9 @@ void MyAvatar::reset(bool andRecenter) { // Reset dynamic state. _wasPushing = _isPushing = _isBraking = false; _follow.deactivate(); - _skeletonModel->reset(); + if (andReload) { + _skeletonModel->reset(); + } getHead()->reset(); _targetVelocity = glm::vec3(0.0f); setThrust(glm::vec3(0.0f)); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 66e6af340e..d7af6b5683 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -94,7 +94,7 @@ public: AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } - Q_INVOKABLE void reset(bool andRecenter = false); + Q_INVOKABLE void reset(bool andRecenter = false, bool andReload = true); void update(float deltaTime); virtual void postUpdate(float deltaTime) override; void preDisplaySide(RenderArgs* renderArgs); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index b44ea91a60..1ceceb741e 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -86,6 +86,7 @@ void OverlayConductor::updateMode() { _mode = FLAT; // Seems appropriate to let things reset, below, after the following. // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. qApp->getActiveDisplayPlugin()->resetSensors(); + myAvatar->reset(true, false); } if (_wantsOverlays) { qDebug() << "flipping" << !nowDriving; From dac043a52d5c7f47255373e10cad89446d3297be Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 20 May 2016 10:49:38 +1200 Subject: [PATCH 165/264] Provide navigation up to the PC level to display drives in table --- .../resources/qml/dialogs/FileDialog.qml | 87 ++++++++++++++++--- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 5674123b4d..0e936ff0a3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -194,10 +194,8 @@ ModalWindow { function navigateUp() { if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { - fileTableModel.folder = fileTableModel.parentFolder + fileTableModel.folder = fileTableModel.parentFolder; return true; - } else if (true) { - } } @@ -222,6 +220,42 @@ ModalWindow { onFolderChanged: { fileTableModel.update(); // Update once the data from the folder change is available. } + + function getItem(index, field) { + return get(index, field); + } + } + + ListModel { + // Emulates FolderListModel but contains drive data. + id: driveListModel + + property int count: 1 + + Component.onCompleted: initialize(); + + function initialize() { + var drive, + i; + + count = drives.length; + + for (i = 0; i < count; i++) { + drive = drives[i].slice(0, -1); // Remove trailing "/". + append({ + fileName: drive, + fileModified: new Date(0), + fileSize: 0, + filePath: drive + "/", + fileIsDir: true, + fileNameSort: drive.toLowerCase() + }); + } + } + + function getItem(index, field) { + return get(index)[field]; + } } ListModel { @@ -237,10 +271,37 @@ ModalWindow { property var folder property int sortOrder: Qt.AscendingOrder property int sortColumn: 0 - property string parentFolder: folderListModel.parentFolder + property var model: folderListModel + property string parentFolder: calculateParentFolder(); + + readonly property string rootFolder: "file:///" + + function calculateParentFolder() { + if (model === folderListModel) { + if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) { + return rootFolder; + } else { + return folderListModel.parentFolder; + } + } else { + return ""; + } + } onFolderChanged: { - folderListModel.folder = folder; + if (folder === rootFolder) { + model = driveListModel; + update(); + } else { + var needsUpdate = model === driveListModel && folder === folderListModel.folder; + + model = folderListModel; + folderListModel.folder = folder; + + if (needsUpdate) { + update(); + } + } currentDirectory.lastValidFolder = helper.urlToPath(folder); } @@ -265,18 +326,18 @@ ModalWindow { upper, rows = 0, i; + clear(); comparisonFunction = sortOrder === Qt.AscendingOrder ? function(a, b) { return a < b; } : function(a, b) { return a > b; } - for (i = 0; i < folderListModel.count; i++) { - fileName = folderListModel.get(i, "fileName"); - fileIsDir = folderListModel.get(i, "fileIsDir"); - - sortValue = folderListModel.get(i, dataField); + for (i = 0; i < model.count; i++) { + fileName = model.getItem(i, "fileName"); + fileIsDir = model.getItem(i, "fileIsDir"); + sortValue = model.getItem(i, dataField); if (dataField === "fileName") { // Directories first by prefixing a "*". // Case-insensitive. @@ -299,9 +360,9 @@ ModalWindow { insert(lower, { fileName: fileName, - fileModified: (fileIsDir ? new Date(0) : folderListModel.get(i, "fileModified")), - fileSize: folderListModel.get(i, "fileSize"), - filePath: folderListModel.get(i, "filePath"), + fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), + fileSize: model.getItem(i, "fileSize"), + filePath: model.getItem(i, "filePath"), fileIsDir: fileIsDir, fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() }); From 9ad488ba7b1d1ce3709fbbba79ad8a2ef5e04821 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 19 May 2016 16:08:44 -0700 Subject: [PATCH 166/264] fix method name to match coding standard --- libraries/script-engine/src/ScriptEngine.h | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index b576e2a37f..512e79fb95 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -147,7 +147,7 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget bool isStopping() const { return _isStopping; } - void flagAsStopping() { _isStopping = true; } + void setIsStopping() { _isStopping = true; } bool isDebuggable() const { return _debuggable; } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 5a2beb901d..65e08938b3 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -351,7 +351,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } - it.value()->flagAsStopping(); + it.value()->setIsStopping(); QMetaObject::invokeMethod(it.value(), "stop"); //it.value()->stop(); qCDebug(scriptengine) << "stopping script..." << it.key(); @@ -370,7 +370,7 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { if (_scriptEnginesHash.contains(scriptURL)) { ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURL]; if (restart) { - scriptEngine->flagAsStopping(); + scriptEngine->setIsStopping(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) { From 3f5ed4bef8603eae9bb4a08e9912953057244c1d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 19 May 2016 16:15:33 -0700 Subject: [PATCH 167/264] set isStopping on other calls to stop --- libraries/script-engine/src/ScriptEngines.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 65e08938b3..8b57487db0 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -160,6 +160,7 @@ void ScriptEngines::shutdownScripting() { scriptEngine->disconnect(this); // Gracefully stop the engine's scripting thread + scriptEngine->setIsStopping(); scriptEngine->stop(); // We need to wait for the engine to be done running before we proceed, because we don't @@ -353,7 +354,6 @@ void ScriptEngines::stopAllScripts(bool restart) { } it.value()->setIsStopping(); QMetaObject::invokeMethod(it.value(), "stop"); - //it.value()->stop(); qCDebug(scriptengine) << "stopping script..." << it.key(); } } @@ -370,13 +370,13 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { if (_scriptEnginesHash.contains(scriptURL)) { ScriptEngine* scriptEngine = _scriptEnginesHash[scriptURL]; if (restart) { - scriptEngine->setIsStopping(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); connect(scriptEngine, &ScriptEngine::finished, this, [this](QString scriptName, ScriptEngine* engine) { reloadScript(scriptName); }); } + scriptEngine->setIsStopping(); scriptEngine->stop(); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; From 11aeaba114fc4970767d8640d1f7530116d6d19b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 20 May 2016 11:32:14 +1200 Subject: [PATCH 168/264] Capitalize drive letter of chosen file --- interface/resources/qml/dialogs/FileDialog.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 0e936ff0a3..56b7cb0505 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -176,6 +176,14 @@ ModalWindow { property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } property var homeDestination: helper.home(); + function capitalizeDrive(path) { + // Consistently capitalize drive letter for Windows. + if (/[a-zA-Z]:/.test(path)) { + return path.charAt(0).toUpperCase() + path.slice(1); + } + return path; + } + function update() { var row = fileTableView.currentRow; @@ -186,7 +194,7 @@ ModalWindow { currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); currentSelectionIsFolder = fileTableView.model.isFolder(row); if (root.selectDirectory || !currentSelectionIsFolder) { - currentSelection.text = helper.urlToPath(currentSelectionUrl); + currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); } else { currentSelection.text = ""; } From 13a057513a3e04c0659904f947834524bbba91e0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 19 May 2016 16:46:17 -0700 Subject: [PATCH 169/264] Removed jointIndices transmission experiment --- libraries/avatars/src/AvatarData.cpp | 58 +++------------------------- libraries/avatars/src/AvatarData.h | 8 ---- 2 files changed, 6 insertions(+), 60 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b70c021f61..ab7647b9fc 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -592,16 +592,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // joint rotations int numJoints = *sourceBuffer++; - // do not process any jointData until we've received a valid jointIndices hash from - // an earlier AvatarIdentity packet. Because if we do, we risk applying the joint data - // the wrong bones, resulting in a twisted avatar, An un-animated avatar is preferable to this. - bool skipJoints = false; -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - if (_networkJointIndexMap.empty()) { - skipJoints = true; - } -#endif - int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; if (minPossibleSize > maxAvailableSize) { @@ -653,13 +643,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; if (validRotations[i]) { - if (skipJoints) { - sourceBuffer += COMPRESSED_QUATERNION_SIZE; - } else { - sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); - _hasNewJointRotations = true; - data.rotationSet = true; - } + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + _hasNewJointRotations = true; + data.rotationSet = true; } } } // numJoints * 6 bytes @@ -705,13 +691,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { for (int i = 0; i < numJoints; i++) { JointData& data = _jointData[i]; if (validTranslations[i]) { - if (skipJoints) { - sourceBuffer += COMPRESSED_TRANSLATION_SIZE; - } else { - sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - _hasNewJointTranslations = true; - data.translationSet = true; - } + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + _hasNewJointTranslations = true; + data.translationSet = true; } } } // numJoints * 6 bytes @@ -971,32 +953,13 @@ void AvatarData::clearJointsData() { void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.jointIndices; -#else packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName; -#endif } bool AvatarData::processAvatarIdentity(const Identity& identity) { bool hasIdentityChanged = false; - #ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - if (!_jointIndices.empty() && _networkJointIndexMap.empty() && !identity.jointIndices.empty()) { - - // build networkJointIndexMap from _jointIndices and networkJointIndices. - _networkJointIndexMap.fill(-1, identity.jointIndices.size()); - for (auto iter = identity.jointIndices.cbegin(); iter != identity.jointIndices.end(); ++iter) { - int jointIndex = getJointIndex(iter.key()); - int networkJointIndex = iter.value(); - if (networkJointIndex >= 0 && networkJointIndex < identity.jointIndices.size()) { - _networkJointIndexMap[networkJointIndex - 1] = jointIndex; - } - } - } -#endif - if (_firstSkeletonCheck || (identity.skeletonModelURL != _skeletonModelURL)) { setSkeletonModelURL(identity.skeletonModelURL); hasIdentityChanged = true; @@ -1021,12 +984,7 @@ QByteArray AvatarData::identityByteArray() { QDataStream identityStream(&identityData, QIODevice::Append); QUrl emptyURL(""); const QUrl& urlToSend = _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; - -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << _jointIndices; -#else identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName; -#endif return identityData; } @@ -1205,10 +1163,6 @@ void AvatarData::updateJointMappings() { _jointIndices.clear(); _jointNames.clear(); -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - _networkJointIndexMap.clear(); -#endif - if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 1a1a07410d..817d8aef09 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -128,8 +128,6 @@ enum KeyState { DELETE_KEY_DOWN }; -#define TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - class QDataStream; class AttachmentData; @@ -287,9 +285,6 @@ public: QUrl skeletonModelURL; QVector attachmentData; QString displayName; -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - QHash jointIndices; -#endif }; static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); @@ -382,9 +377,6 @@ protected: float _displayNameAlpha; QHash _jointIndices; ///< 1-based, since zero is returned for missing keys -#ifdef TRANSMIT_JOINT_INDICES_IN_IDENTITY_PACKET - QVector _networkJointIndexMap; // maps network joint indices to local model joint indices. -#endif QStringList _jointNames; ///< in order of depth-first traversal quint64 _errorLogExpiry; ///< time in future when to log an error From 6ecbdc6b9a5ec1e67701f00464d729403de11a80 Mon Sep 17 00:00:00 2001 From: Geenz Date: Thu, 19 May 2016 20:03:05 -0400 Subject: [PATCH 170/264] Modify evalLightAttenuation to "fade" edges of lights. This provides a more attractive light falloff around the bounds of a light - removing harsh edges. This also adds a new parameter to evalLightAttenuation - the unnormalized light vector. --- libraries/model/src/model/Light.slh | 9 ++++++--- libraries/render-utils/src/point_light.slf | 4 ++-- libraries/render-utils/src/spot_light.slf | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 7cc4691d63..51c78b9a4d 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -98,10 +98,13 @@ float getLightShowContour(Light l) { return l._control.w; } -float evalLightAttenuation(Light l, float d) { - float radius = getLightRadius(l); +float evalLightAttenuation(Light l, float d, vec3 ulightvec) { + float radius = getLightSquareRadius(l); float denom = d / radius + 1.0; - float attenuation = min(1.0, 1.0 / (denom * denom)); + float attenuation = 1.0 / (denom * denom); + // "Fade" the edges of light sources to make things look a bit more attractive. + // Note: this tends to look a bit odd at lower exponents. + attenuation *= clamp(0, 1, mix(0, 1, getLightCutoffSquareRadius(l) - dot(ulightvec, ulightvec))); return attenuation; } diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index fc72f094e7..581aa8c250 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -66,8 +66,8 @@ void main(void) { vec3 fragEyeDir = normalize(fragEyeVector.xyz); vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); - // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance); + // Eval attenuation + float radialAttenuation = evalLightAttenuation(light, fragLightDistance, fragLightVec); // Final Lighting color vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 4191ba3f63..1cb6ebb878 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -74,7 +74,7 @@ void main(void) { vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance); + float radialAttenuation = evalLightAttenuation(light, fragLightDistance, fragLightVec); float angularAttenuation = evalLightSpotAttenuation(light, cosSpotAngle); // Final Lighting color From a54e8206faeb7c689bae0e3731ca90a58d3e298c Mon Sep 17 00:00:00 2001 From: Geenz Date: Thu, 19 May 2016 20:10:49 -0400 Subject: [PATCH 171/264] Switch back to using the light's radius + spacing --- libraries/model/src/model/Light.slh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 51c78b9a4d..6211505090 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -99,12 +99,14 @@ float getLightShowContour(Light l) { } float evalLightAttenuation(Light l, float d, vec3 ulightvec) { - float radius = getLightSquareRadius(l); + float radius = getLightRadius(l); float denom = d / radius + 1.0; float attenuation = 1.0 / (denom * denom); + // "Fade" the edges of light sources to make things look a bit more attractive. // Note: this tends to look a bit odd at lower exponents. attenuation *= clamp(0, 1, mix(0, 1, getLightCutoffSquareRadius(l) - dot(ulightvec, ulightvec))); + return attenuation; } From 75532cda084c34c09b759eb120d00bfcc7d61ea5 Mon Sep 17 00:00:00 2001 From: Geenz Date: Thu, 19 May 2016 20:52:28 -0400 Subject: [PATCH 172/264] Document evalLightAttenuation's parameters. --- libraries/model/src/model/Light.slh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 6211505090..46eb352d16 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -98,6 +98,9 @@ float getLightShowContour(Light l) { return l._control.w; } +// Light is the light source its self, d is the light's distance, and ulightvec is the unnormalized light vector. +// Note that ulightvec should be calculated as light position - fragment position. +// Additionally, length(light position) != dot(light position, light position). float evalLightAttenuation(Light l, float d, vec3 ulightvec) { float radius = getLightRadius(l); float denom = d / radius + 1.0; From e8d7f9614aed57bad25f940c82282c25b91ed92e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 May 2016 15:51:17 -0700 Subject: [PATCH 173/264] refresh API info during re-connect - case 570 --- libraries/networking/src/AddressManager.cpp | 44 ++++++++++++++++++--- libraries/networking/src/AddressManager.h | 9 ++++- libraries/networking/src/DomainHandler.cpp | 18 +++------ libraries/networking/src/DomainHandler.h | 10 ++--- libraries/networking/src/NodeList.cpp | 14 +++++-- 5 files changed, 67 insertions(+), 28 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index a97f4df35d..35a9d9293d 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -144,12 +144,21 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // 4. domain network address (IP or dns resolvable hostname) // use our regex'ed helpers to figure out what we're supposed to do with this - if (!handleUsername(lookupUrl.authority())) { + if (handleUsername(lookupUrl.authority())) { + // handled a username for lookup + + // in case we're failing to connect to where we thought this user was + // store their username as previous lookup so we can refresh their location via API + _previousLookup = lookupUrl; + } else { // we're assuming this is either a network address or global place name // check if it is a network address first bool hostChanged; if (handleNetworkAddress(lookupUrl.host() - + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { + + // a network address lookup clears the previous lookup since we don't expect to re-attempt it + _previousLookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { @@ -165,10 +174,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ + // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info + _previousLookup = lookupUrl; + // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { + // store this place name as the previous lookup in case we fail to connect and want to refresh API info + _previousLookup = lookupUrl; + // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger); @@ -180,9 +195,13 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); + // a path lookup clears the previous lookup since we don't expect to re-attempt it + _previousLookup.clear(); + // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); + return true; } @@ -276,7 +295,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const qCDebug(networking) << "Possible domain change required to connect to" << domainHostname << "on" << domainPort; - emit possibleDomainChangeRequired(domainHostname, domainPort); + emit possibleDomainChangeRequired(domainHostname, domainPort, domainID); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -309,7 +328,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty()) { - handlePath(overridePath, trigger); + // make sure we don't re-handle an overriden path if this was a refresh of info from API + if (trigger != LookupTrigger::AttemptedRefresh) { + handlePath(overridePath, trigger); + } } else { // take the path that came back const QString PLACE_PATH_KEY = "path"; @@ -586,7 +608,7 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); - emit possibleDomainChangeRequired(hostname, port); + emit possibleDomainChangeRequired(hostname, port, QUuid()); return hostChanged; } @@ -606,6 +628,13 @@ void AddressManager::goToUser(const QString& username) { QByteArray(), nullptr, requestParams); } +void AddressManager::refreshPreviousLookup() { + // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) + if (!_previousLookup.isEmpty()) { + handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); + } +} + void AddressManager::copyAddress() { QApplication::clipboard()->setText(currentAddress().toString()); } @@ -617,7 +646,10 @@ void AddressManager::copyPath() { void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything - if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) { + if (trigger != LookupTrigger::StartupFromSettings + && trigger != LookupTrigger::DomainPathResponse + && trigger != LookupTrigger::AttemptedRefresh) { + if (trigger == LookupTrigger::Back) { // we're about to push to the forward stack // if it's currently empty emit our signal to say that going forward is now possible diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dd0dbd9f38..0780cfb2c2 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -48,7 +48,8 @@ public: Forward, StartupFromSettings, DomainPathResponse, - Internal + Internal, + AttemptedRefresh }; bool isConnected(); @@ -88,6 +89,8 @@ public slots: void goToUser(const QString& username); + void refreshPreviousLookup(); + void storeCurrentAddress(); void copyAddress(); @@ -98,7 +101,7 @@ signals: void lookupResultIsOffline(); void lookupResultIsNotFound(); - void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort); + void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID); void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); void locationChangeRequired(const glm::vec3& newPosition, @@ -150,6 +153,8 @@ private: quint64 _lastBackPush = 0; QString _newHostLookupPath; + + QUrl _previousLookup; }; #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 44ce63e6c6..b3c3a28829 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -28,16 +28,8 @@ DomainHandler::DomainHandler(QObject* parent) : QObject(parent), - _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), - _assignmentUUID(), - _connectionToken(), - _iceDomainID(), - _iceClientID(), - _iceServerSockAddr(), _icePeer(this), - _isConnected(false), - _settingsObject(), _settingsTimer(this) { _sockAddr.setObjectName("DomainServer"); @@ -105,7 +97,7 @@ void DomainHandler::hardReset() { softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; - _iceDomainID = QUuid(); + _pendingDomainID = QUuid(); _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); @@ -140,7 +132,7 @@ void DomainHandler::setUUID(const QUuid& uuid) { } } -void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { +void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) { if (hostname != _hostname || _sockAddr.getPort() != port) { // re-set the domain info so that auth information is reloaded @@ -169,6 +161,8 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { // grab the port by reading the string after the colon _sockAddr.setPort(port); } + + _pendingDomainID = domainID; } void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { @@ -179,7 +173,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); - _iceDomainID = id; + _pendingDomainID = id; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; replaceableSockAddr->~HifiSockAddr(); @@ -341,7 +335,7 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); - if (_icePeer.getUUID() != _iceDomainID) { + if (_icePeer.getUUID() != _pendingDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; _icePeer.reset(); } else { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 03141d8fef..c6269191d2 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -58,8 +58,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - - const QUuid& getICEDomainID() const { return _iceDomainID; } + + const QUuid& getPendingDomainID() const { return _pendingDomainID; } const QUuid& getICEClientID() const { return _iceClientID; } @@ -85,7 +85,7 @@ public: void softReset(); public slots: - void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); + void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid()); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); void processSettingsPacketList(QSharedPointer packetList); @@ -126,11 +126,11 @@ private: HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; - QUuid _iceDomainID; + QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; - bool _isConnected; + bool _isConnected { false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 482d0366fd..831f0a4995 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // handle domain change signals from AddressManager connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, - &_domainHandler, &DomainHandler::setHostnameAndPort); + &_domainHandler, &DomainHandler::setSocketAndID); connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, &_domainHandler, &DomainHandler::setIceServerHostnameAndID); @@ -344,6 +344,14 @@ void NodeList::sendDomainServerCheckIn() { // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; } + + if (!_publicSockAddr.isNull() && !_domainHandler.isConnected() && !_domainHandler.getPendingDomainID().isNull()) { + // if we aren't connected to the domain-server, and we have an ID + // (that we presume belongs to a domain in the HF Metaverse) + // we request connection information for the domain every so often to make sure what we have is up to date + + DependencyManager::get()->refreshPreviousLookup(); + } } void NodeList::handleDSPathQuery(const QString& newPath) { @@ -451,7 +459,7 @@ void NodeList::handleICEConnectionToDomainServer() { LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), - _domainHandler.getICEDomainID()); + _domainHandler.getPendingDomainID()); } } @@ -464,7 +472,7 @@ void NodeList::pingPunchForDomainServer() { if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + << uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID()); } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat From d3304ee65c52dd589cc04ed194768e17f30650df Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 19 May 2016 18:01:13 -0700 Subject: [PATCH 174/264] make some overriding methods as override --- assignment-client/src/entities/EntityServer.h | 4 ++-- tests/controllers/src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 1685f08e01..0486a97ede 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -60,8 +60,8 @@ public: virtual void trackViewerGone(const QUuid& sessionID) override; public slots: - virtual void nodeAdded(SharedNodePointer node); - virtual void nodeKilled(SharedNodePointer node); + virtual void nodeAdded(SharedNodePointer node) override; + virtual void nodeKilled(SharedNodePointer node) override; void pruneDeletedEntities(); protected: diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index e978dd9a38..3a5b4a4a4d 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -89,7 +89,7 @@ public: virtual GLWidget* getPrimaryWidget() override { return nullptr; } virtual MainWindow* getPrimaryWindow() override { return nullptr; } virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } - virtual ui::Menu* getPrimaryMenu() { return nullptr; } + virtual ui::Menu* getPrimaryMenu() override { return nullptr; } virtual bool isForeground() override { return true; } virtual const DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; From b95ba8141c59723e525fbc600ac8abcfa8872ce3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 19 May 2016 20:24:44 -0700 Subject: [PATCH 175/264] AvatarData packet overhaul, uses a structure instead of raw memcpy --- libraries/avatars/src/AvatarData.cpp | 454 +++++++++++---------------- libraries/avatars/src/AvatarData.h | 2 + libraries/shared/src/Packed.h | 12 + 3 files changed, 199 insertions(+), 269 deletions(-) create mode 100644 libraries/shared/src/Packed.h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ab7647b9fc..4379a0d72c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -48,6 +48,38 @@ const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f); const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; +namespace AvatarDataPacket { + + PACKED_BEGIN struct Header { + float position[3]; // skeletal model's position + float globalPosition[3]; // avatar's position + uint16_t localOrientation[3]; // avatar's local euler angles (degrees, compressed) relative to the thing it's attached to + uint16_t scale; // (compressed) 'ratio' encoding uses sign bit as flag. + float lookAtPosition[3]; // world space position that eyes are focusing on. + float audioLoudness; // current loundess of microphone + uint8_t flags; + } PACKED_END; + const size_t HEADER_SIZE = 49; + + PACKED_BEGIN struct ParentInfo { + uint8_t parentUUID[16]; // rfc 4122 encoded + uint16_t parentJointIndex; + } PACKED_END; + const size_t PARENT_INFO_SIZE = 18; + + PACKED_BEGIN struct FaceTrackerInfo { + float leftEyeBlink; + float rightEyeBlink; + float averageLoudness; + float browAudioLift; + uint8_t numBlendshapeCoefficients; + // float blendshapeCoefficients[numBlendshapeCoefficients]; + } PACKED_END; + const size_t FACE_TRACKER_INFO_SIZE = 17; +} + +#define ASSERT(COND) do { if (!(COND)) { int* bad = nullptr; *bad = 0xbad; } } while(0) + AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), _handPosition(0.0f), @@ -68,6 +100,10 @@ AvatarData::AvatarData() : setBodyPitch(0.0f); setBodyYaw(-90.0f); setBodyRoll(0.0f); + + ASSERT(sizeof AvatarDataPacket::Header == AvatarDataPacket::HEADER_SIZE); + ASSERT(sizeof AvatarDataPacket::ParentInfo == AvatarDataPacket::PARENT_INFO_SIZE); + ASSERT(sizeof AvatarDataPacket::FaceTrackerInfo == AvatarDataPacket::FACE_TRACKER_INFO_SIZE); } AvatarData::~AvatarData() { @@ -141,81 +177,67 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - const glm::vec3& position = getLocalPosition(); - memcpy(destinationBuffer, &position, sizeof(position)); - destinationBuffer += sizeof(position); + auto header = reinterpret_cast(destinationBuffer); + header->position[0] = getLocalPosition().x; + header->position[1] = getLocalPosition().y; + header->position[2] = getLocalPosition().z; + header->globalPosition[0] = _globalPosition.x; + header->globalPosition[1] = _globalPosition.y; + header->globalPosition[2] = _globalPosition.z; - memcpy(destinationBuffer, &_globalPosition, sizeof(_globalPosition)); - destinationBuffer += sizeof(_globalPosition); - - // Body rotation glm::vec3 bodyEulerAngles = glm::degrees(safeEulerAngles(getLocalOrientation())); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.y); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.x); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, bodyEulerAngles.z); + 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); + header->lookAtPosition[0] = _headData->_lookAtPosition.x; + header->lookAtPosition[1] = _headData->_lookAtPosition.y; + header->lookAtPosition[2] = _headData->_lookAtPosition.z; + header->audioLoudness = _headData->_audioLoudness; - // Body scale - destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); - - // Lookat Position - memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition)); - destinationBuffer += sizeof(_headData->_lookAtPosition); - - // Instantaneous audio loudness (used to drive facial animation) - memcpy(destinationBuffer, &_headData->_audioLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - // bitMask of less than byte wide items - unsigned char bitItems = 0; - - // key state - setSemiNibbleAt(bitItems,KEY_STATE_START_BIT,_keyState); + setSemiNibbleAt(header->flags, KEY_STATE_START_BIT, _keyState); // hand state bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; - setSemiNibbleAt(bitItems, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); + setSemiNibbleAt(header->flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); if (isFingerPointing) { - setAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT); + setAtBit(header->flags, HAND_STATE_FINGER_POINTING_BIT); } // faceshift state if (_headData->_isFaceTrackerConnected) { - setAtBit(bitItems, IS_FACESHIFT_CONNECTED); + setAtBit(header->flags, IS_FACESHIFT_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { - setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + setAtBit(header->flags, IS_EYE_TRACKER_CONNECTED); } // referential state QUuid parentID = getParentID(); if (!parentID.isNull()) { - setAtBit(bitItems, HAS_REFERENTIAL); + setAtBit(header->flags, HAS_REFERENTIAL); } - *destinationBuffer++ = bitItems; + destinationBuffer += sizeof(AvatarDataPacket::Header); if (!parentID.isNull()) { + auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); - memcpy(destinationBuffer, referentialAsBytes.data(), referentialAsBytes.size()); - destinationBuffer += referentialAsBytes.size(); - memcpy(destinationBuffer, &_parentJointIndex, sizeof(_parentJointIndex)); - destinationBuffer += sizeof(_parentJointIndex); + memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); + parentInfo->parentJointIndex = _parentJointIndex; + destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); } // If it is connected, pack up the data if (_headData->_isFaceTrackerConnected) { - memcpy(destinationBuffer, &_headData->_leftEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - memcpy(destinationBuffer, &_headData->_rightEyeBlink, sizeof(float)); - destinationBuffer += sizeof(float); + faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; + faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; + faceTrackerInfo->averageLoudness = _headData->_averageLoudness; + faceTrackerInfo->browAudioLift = _headData->_browAudioLift; + faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); + destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(destinationBuffer, &_headData->_averageLoudness, sizeof(float)); - destinationBuffer += sizeof(float); - - memcpy(destinationBuffer, &_headData->_browAudioLift, sizeof(float)); - destinationBuffer += sizeof(float); - - *destinationBuffer++ = _headData->_blendshapeCoefficients.size(); - memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), - _headData->_blendshapeCoefficients.size() * sizeof(float)); + // followed by a variable number of float coefficients + memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); } @@ -377,6 +399,16 @@ bool AvatarData::shouldLogError(const quint64& now) { return false; } +#define PACKET_READ_CHECK(ITEM_NAME, SIZE_TO_READ) \ + if ((endPosition - sourceBuffer) < (int)SIZE_TO_READ) { \ + if (shouldLogError(now)) { \ + qCWarning(avatars) << "AvatarData packet too small, attempting to read " << \ + #ITEM_NAME << ", only " << (endPosition - sourceBuffer) << \ + " bytes left, " << getSessionUUID(); \ + } \ + return buffer.size(); \ + } + // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { @@ -386,124 +418,76 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } const unsigned char* startPosition = reinterpret_cast(buffer.data()); + const unsigned char* endPosition = startPosition + buffer.size(); const unsigned char* sourceBuffer = startPosition; quint64 now = usecTimestampNow(); - // The absolute minimum size of the update data is as follows: - // 36 bytes of "plain old data" { - // position = 12 bytes - // bodyYaw = 2 (compressed float) - // bodyPitch = 2 (compressed float) - // bodyRoll = 2 (compressed float) - // targetScale = 2 (compressed float) - // lookAt = 12 - // audioLoudness = 4 - // } - // + 1 byte for varying data - // + 1 byte for numJoints (0) - // = 39 bytes - int minPossibleSize = 39; + PACKET_READ_CHECK(Header, sizeof(AvatarDataPacket::Header)); + auto header = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::Header); - int maxAvailableSize = buffer.size(); - if (minPossibleSize > maxAvailableSize) { + glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]); + _globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]); + if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet at the start; " - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; + qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID(); } - // this packet is malformed so we report all bytes as consumed - return maxAvailableSize; + return buffer.size(); + } + setLocalPosition(position); + + float pitch, yaw, roll; + unpackFloatAngleFromTwoByte(header->localOrientation + 0, &yaw); + unpackFloatAngleFromTwoByte(header->localOrientation + 1, &pitch); + unpackFloatAngleFromTwoByte(header->localOrientation + 2, &roll); + if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: localOriention is NaN, uuid " << getSessionUUID(); + } + return buffer.size(); } - { // Body world position, rotation, and scale - // position - glm::vec3 position; - memcpy(&position, sourceBuffer, sizeof(position)); - sourceBuffer += sizeof(position); + glm::quat currentOrientation = getLocalOrientation(); + glm::vec3 newEulerAngles(pitch, yaw, roll); + glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); + if (currentOrientation != newOrientation) { + _hasNewJointRotations = true; + setLocalOrientation(newOrientation); + } - memcpy(&_globalPosition, sourceBuffer, sizeof(_globalPosition)); - sourceBuffer += sizeof(_globalPosition); - - if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::position; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; + float scale; + unpackFloatRatioFromTwoByte((uint8_t*)&header->scale, scale); + if (glm::isnan(scale)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID(); } - setLocalPosition(position); + return buffer.size(); + } + _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - // rotation (NOTE: This needs to become a quaternion to save two bytes) - float yaw, pitch, roll; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll); - if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; + glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); + if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID(); } + return buffer.size(); + } + _headData->_lookAtPosition = lookAt; - // TODO is this safe? will the floats not exactly match? - // Andrew says: - // Yes, there is a possibility that the transmitted will not quite match the extracted despite being originally - // extracted from the exact same quaternion. I followed the code through and it appears the risk is that the - // avatar's SkeletonModel might fall into the CPU expensive part of Model::updateClusterMatrices() when otherwise it - // would not have required it. However, we know we can update many simultaneously animating avatars, and most - // avatars will be moving constantly anyway, so I don't think we need to worry. - glm::quat currentOrientation = getLocalOrientation(); - glm::vec3 newEulerAngles(pitch, yaw, roll); - glm::quat newOrientation = glm::quat(glm::radians(newEulerAngles)); - if (currentOrientation != newOrientation) { - _hasNewJointRotations = true; - setLocalOrientation(newOrientation); + float audioLoudness = header->audioLoudness; + if (glm::isnan(audioLoudness)) { + if (shouldLogError(now)) { + qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID(); } - - // scale - float scale; - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale); - if (glm::isnan(scale)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); - } // 20 bytes - - { // Lookat Position - glm::vec3 lookAt; - memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); - sourceBuffer += sizeof(lookAt); - if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_lookAtPosition = lookAt; - } // 12 bytes - - { // AudioLoudness - // Instantaneous audio loudness (used to drive facial animation) - float audioLoudness; - memcpy(&audioLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - if (glm::isnan(audioLoudness)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_audioLoudness = audioLoudness; - } // 4 bytes + return buffer.size(); + } + _headData->_audioLoudness = audioLoudness; { // bitFlags and face data - unsigned char bitItems = *sourceBuffer++; + uint8_t bitItems = header->flags; // key state, stored as a semi-nibble in the bitItems - _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); + _keyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT); // hand state, stored as a semi-nibble plus a bit in the bitItems // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split @@ -520,95 +504,48 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL); if (hasReferential) { - const int sizeOfPackedUuid = 16; - QByteArray referentialAsBytes((const char*)sourceBuffer, sizeOfPackedUuid); - _parentID = QUuid::fromRfc4122(referentialAsBytes); - sourceBuffer += sizeOfPackedUuid; - memcpy(&_parentJointIndex, sourceBuffer, sizeof(_parentJointIndex)); - sourceBuffer += sizeof(_parentJointIndex); + PACKET_READ_CHECK(ParentInfo, sizeof(AvatarDataPacket::ParentInfo)); + auto parentInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::ParentInfo); + + const size_t RFC_4122_SIZE = 16; + QByteArray byteArray((const char*)parentInfo->parentUUID, RFC_4122_SIZE); + _parentID = QUuid::fromRfc4122(byteArray); + _parentJointIndex = parentInfo->parentJointIndex; } else { _parentID = QUuid(); } if (_headData->_isFaceTrackerConnected) { - float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; - minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); - minPossibleSize++; // one byte for blendDataSize - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after BitItems;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - // unpack face data - memcpy(&leftEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo)); + auto faceTrackerInfo = reinterpret_cast(sourceBuffer); + sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - memcpy(&rightEyeBlink, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); + _headData->_leftEyeBlink = faceTrackerInfo->leftEyeBlink; + _headData->_rightEyeBlink = faceTrackerInfo->rightEyeBlink; + _headData->_averageLoudness = faceTrackerInfo->averageLoudness; + _headData->_browAudioLift = faceTrackerInfo->browAudioLift; - memcpy(&averageLoudness, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - memcpy(&browAudioLift, sourceBuffer, sizeof(float)); - sourceBuffer += sizeof(float); - - if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink) - || glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_leftEyeBlink = leftEyeBlink; - _headData->_rightEyeBlink = rightEyeBlink; - _headData->_averageLoudness = averageLoudness; - _headData->_browAudioLift = browAudioLift; - - int numCoefficients = (int)(*sourceBuffer++); - int blendDataSize = numCoefficients * sizeof(float); - minPossibleSize += blendDataSize; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after Blendshapes;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - - _headData->_blendshapeCoefficients.resize(numCoefficients); - memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize); - sourceBuffer += numCoefficients * sizeof(float); - - //bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize; + int numCoefficients = faceTrackerInfo->numBlendshapeCoefficients; + const int coefficientsSize = sizeof(float) * numCoefficients; + PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); + _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! + memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); + sourceBuffer += coefficientsSize; } - } // 1 + bitItemsDataSize bytes + } - // joint rotations + PACKET_READ_CHECK(NumJoints, sizeof(uint8_t)); int numJoints = *sourceBuffer++; - int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); - minPossibleSize += bytesOfValidity; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointValidityBits;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - int numValidJointRotations = 0; _jointData.resize(numJoints); + const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); + PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity); + + int numValidJointRotations = 0; QVector validRotations; validRotations.resize(numJoints); - { // rotation validity bits unsigned char validity = 0; int validityBit = 0; @@ -623,39 +560,26 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { validRotations[i] = valid; validityBit = (validityBit + 1) % BITS_IN_BYTE; } - } // 1 + bytesOfValidity bytes - - // each joint rotation is stored in 6 bytes. - - const size_t COMPRESSED_QUATERNION_SIZE = 6; - minPossibleSize += numValidJointRotations * COMPRESSED_QUATERNION_SIZE; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; } - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validRotations[i]) { - sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); - _hasNewJointRotations = true; - data.rotationSet = true; - } + // each joint rotation is stored in 6 bytes. + const int COMPRESSED_QUATERNION_SIZE = 6; + PACKET_READ_CHECK(JointRotations, numValidJointRotations * COMPRESSED_QUATERNION_SIZE); + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validRotations[i]) { + sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation); + _hasNewJointRotations = true; + data.rotationSet = true; } - } // numJoints * 6 bytes + } + + PACKET_READ_CHECK(JointTranslationValidityBits, bytesOfValidity); - // joint translations // get translation validity bits -- these indicate which translations were packed int numValidJointTranslations = 0; QVector validTranslations; validTranslations.resize(numJoints); - { // translation validity bits unsigned char validity = 0; int validityBit = 0; @@ -673,30 +597,18 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } // 1 + bytesOfValidity bytes // each joint translation component is stored in 6 bytes. - const size_t COMPRESSED_TRANSLATION_SIZE = 6; - minPossibleSize += numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE; - if (minPossibleSize > maxAvailableSize) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;" - << " displayName = '" << _displayName << "'" - << " minPossibleSize = " << minPossibleSize - << " maxAvailableSize = " << maxAvailableSize; - } - return maxAvailableSize; - } - + const int COMPRESSED_TRANSLATION_SIZE = 6; + PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_QUATERNION_SIZE); const int TRANSLATION_COMPRESSION_RADIX = 12; - { // joint data - for (int i = 0; i < numJoints; i++) { - JointData& data = _jointData[i]; - if (validTranslations[i]) { - sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - _hasNewJointTranslations = true; - data.translationSet = true; - } + for (int i = 0; i < numJoints; i++) { + JointData& data = _jointData[i]; + if (validTranslations[i]) { + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + _hasNewJointTranslations = true; + data.translationSet = true; } - } // numJoints * 6 bytes + } #ifdef WANT_DEBUG if (numValidJointRotations > 15) { @@ -707,6 +619,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { #endif int numBytesRead = sourceBuffer - startPosition; + + // AJT: Maybe make this a warning. + ASSERT(numBytesRead == buffer.size()); + _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 817d8aef09..dfb27411b8 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -53,6 +53,7 @@ typedef unsigned long long quint64; #include #include #include +#include #include "AABox.h" #include "HeadData.h" @@ -165,6 +166,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: + static const QString FRAME_NAME; static void fromFrame(const QByteArray& frameData, AvatarData& avatar); diff --git a/libraries/shared/src/Packed.h b/libraries/shared/src/Packed.h new file mode 100644 index 0000000000..3300634b96 --- /dev/null +++ b/libraries/shared/src/Packed.h @@ -0,0 +1,12 @@ +#ifndef hifi_Packed_h +#define hifi_Packed_h + +#if defined(_MSC_VER) +#define PACKED_BEGIN __pragma(pack(push, 1)) +#define PACKED_END __pragma(pack(pop)); +#else +#define PACKED_BEGIN +#define PACKED_END __attribute__((__packed__)); +#endif + +#endif From c73943ee19574a95bc9ecce380929dab71d4bffc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 19 May 2016 20:32:08 -0700 Subject: [PATCH 176/264] macosx warning fixes --- libraries/avatars/src/AvatarData.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4379a0d72c..7a20f24da8 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -101,9 +101,9 @@ AvatarData::AvatarData() : setBodyYaw(-90.0f); setBodyRoll(0.0f); - ASSERT(sizeof AvatarDataPacket::Header == AvatarDataPacket::HEADER_SIZE); - ASSERT(sizeof AvatarDataPacket::ParentInfo == AvatarDataPacket::PARENT_INFO_SIZE); - ASSERT(sizeof AvatarDataPacket::FaceTrackerInfo == AvatarDataPacket::FACE_TRACKER_INFO_SIZE); + ASSERT(sizeof(AvatarDataPacket::Header) == AvatarDataPacket::HEADER_SIZE); + ASSERT(sizeof(AvatarDataPacket::ParentInfo) == AvatarDataPacket::PARENT_INFO_SIZE); + ASSERT(sizeof(AvatarDataPacket::FaceTrackerInfo) == AvatarDataPacket::FACE_TRACKER_INFO_SIZE); } AvatarData::~AvatarData() { @@ -598,7 +598,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // each joint translation component is stored in 6 bytes. const int COMPRESSED_TRANSLATION_SIZE = 6; - PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_QUATERNION_SIZE); + PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE); const int TRANSLATION_COMPRESSION_RADIX = 12; for (int i = 0; i < numJoints; i++) { From 8be6bc6460e18ab1647c12dd07218f91bf32c101 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 19 May 2016 22:00:22 -0700 Subject: [PATCH 177/264] Add missing overrides --- assignment-client/src/entities/EntityServer.h | 4 ++-- tests/controllers/src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 1685f08e01..0486a97ede 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -60,8 +60,8 @@ public: virtual void trackViewerGone(const QUuid& sessionID) override; public slots: - virtual void nodeAdded(SharedNodePointer node); - virtual void nodeKilled(SharedNodePointer node); + virtual void nodeAdded(SharedNodePointer node) override; + virtual void nodeKilled(SharedNodePointer node) override; void pruneDeletedEntities(); protected: diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index e978dd9a38..3a5b4a4a4d 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -89,7 +89,7 @@ public: virtual GLWidget* getPrimaryWidget() override { return nullptr; } virtual MainWindow* getPrimaryWindow() override { return nullptr; } virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } - virtual ui::Menu* getPrimaryMenu() { return nullptr; } + virtual ui::Menu* getPrimaryMenu() override { return nullptr; } virtual bool isForeground() override { return true; } virtual const DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; From b2bbf72be2b10e0801cb281d9d96f52f2bf449bd Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 20 May 2016 18:48:51 +1200 Subject: [PATCH 178/264] Replace drive drop-down plus text field with a paths drop-down --- .../resources/qml/dialogs/FileDialog.qml | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 56b7cb0505..78d5943479 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -68,16 +68,12 @@ ModalWindow { Component.onCompleted: { console.log("Helper " + helper + " drives " + drives) - drivesSelector.onCurrentTextChanged.connect(function(){ - root.dir = helper.pathToUrl(drivesSelector.currentText); - }) - // HACK: The following two lines force the model to initialize properly such that: - // - Selecting a different drive at the initial screen updates the path displayed. - // - The go-up button works properly from the initial screen. - var initialFolder = currentDirectory.lastValidFolder; - root.dir = helper.pathToUrl(drivesSelector.currentText); - root.dir = helper.pathToUrl(initialFolder); + // HACK: The following lines force the model to initialize properly such that the go-up button + // works properly from the initial screen. + var initialFolder = folderListModel.folder; + fileTableModel.folder = helper.pathToUrl(drives[0]); + fileTableModel.folder = initialFolder; iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; } @@ -97,15 +93,6 @@ ModalWindow { } spacing: hifi.dimensions.contentSpacing.x - // FIXME implement back button - //VrControls.ButtonAwesome { - // id: backButton - // text: "\uf0a8" - // size: currentDirectory.height - // enabled: d.backStack.length != 0 - // MouseArea { anchors.fill: parent; onClicked: d.navigateBack() } - //} - GlyphButton { id: upButton glyph: hifi.glyphs.levelUp @@ -124,20 +111,10 @@ ModalWindow { enabled: d.homeDestination ? true : false onClicked: d.navigateHome(); } - - ComboBox { - id: drivesSelector - width: 62 - model: drives - visible: drives.length > 1 - currentIndex: 0 - } } - TextField { - id: currentDirectory - property var lastValidFolder: helper.urlToPath(fileTableModel.folder) - height: homeButton.height + ComboBox { + id: pathSelector anchors { top: parent.top topMargin: hifi.dimensions.contentMargin.y @@ -146,23 +123,54 @@ ModalWindow { right: parent.right } - function capitalizeDrive(path) { - // Consistently capitalize drive letter for Windows. - if (/[a-zA-Z]:/.test(path)) { - return path.charAt(0).toUpperCase() + path.slice(1); + property var lastValidFolder: helper.urlToPath(fileTableModel.folder) + + function calculatePathChoices(folder) { + var folders = folder.split("/"), + choices = [], + i, length; + + if (folders[folders.length - 1] === "") { + folders.pop(); + } + + if (folders[0] !== "") { + choices.push(folders[0]); + } + + for (i = 1, length = folders.length; i < length; i++) { + choices.push(choices[i - 1] + "/" + folders[i]); + } + choices.reverse(); + + if (drives && drives.length > 1) { + choices.push("This PC"); + } + + if (choices.length > 0) { + pathSelector.model = choices; } - return path; } - onLastValidFolderChanged: text = capitalizeDrive(lastValidFolder); + onLastValidFolderChanged: { + var folder = d.capitalizeDrive(lastValidFolder); + calculatePathChoices(folder); + } - // FIXME add support auto-completion - onAccepted: { - if (!helper.validFolder(text)) { - text = lastValidFolder; - return + onCurrentTextChanged: { + var folder = currentText; + + if (/^[a-zA-z]:$/.test(folder)) { + folder = "file:///" + folder + "/"; + } else if (folder === "This PC") { + folder = "file:///"; + } else { + folder = helper.pathToUrl(folder); + } + + if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { + fileTableModel.folder = folder; } - fileTableModel.folder = helper.pathToUrl(text); } } @@ -219,12 +227,10 @@ ModalWindow { showDirsFirst: true showDotAndDotDot: false showFiles: !root.selectDirectory - // For some reason, declaring these bindings directly in the targets doesn't - // work for setting the initial state Component.onCompleted: { - currentDirectory.lastValidFolder = helper.urlToPath(folder); showFiles = !root.selectDirectory } + onFolderChanged: { fileTableModel.update(); // Update once the data from the folder change is available. } @@ -310,7 +316,6 @@ ModalWindow { update(); } } - currentDirectory.lastValidFolder = helper.urlToPath(folder); } function isFolder(row) { From 2026226fe2b09a7664fd5b4a7f97246c7029584c Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 19 May 2016 13:18:02 -0700 Subject: [PATCH 179/264] Handle vsync without halving fps --- interface/src/Application.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cf14d01771..6190de5875 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1822,28 +1822,37 @@ bool Application::event(QEvent* event) { return false; } - static bool justPresented = false; + // Presentation/painting logic + // TODO: Decouple presentation and painting loops + static bool isPainting = false; if ((int)event->type() == (int)Present) { - if (justPresented) { - justPresented = false; - - // If presentation is hogging the main thread, repost as low priority to avoid hanging the GUI. + if (isPainting) { + // If painting (triggered by presentation) is hogging the main thread, + // repost as low priority to avoid hanging the GUI. // This has the effect of allowing presentation to exceed the paint budget by X times and - // only dropping every (1/X) frames, instead of every ceil(X) frames. + // only dropping every (1/X) frames, instead of every ceil(X) frames // (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS). removePostedEvents(this, Present); postEvent(this, new QEvent(static_cast(Present)), Qt::LowEventPriority); + isPainting = false; return true; } idle(); + + postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + isPainting = true; + return true; } else if ((int)event->type() == (int)Paint) { // NOTE: This must be updated as close to painting as possible, // or AvatarInputs will mysteriously move to the bottom-right AvatarInputs::getInstance()->update(); - justPresented = true; + paintGL(); + + isPainting = false; + return true; } @@ -2658,9 +2667,6 @@ void Application::idle() { // Sync up the _renderedFrameIndex _renderedFrameIndex = displayPlugin->presentCount(); - // Request a paint ASAP - postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority + 1); - // Update the deadlock watchdog updateHeartbeat(); @@ -2687,8 +2693,6 @@ void Application::idle() { _keyboardDeviceHasFocus = true; } - - // We're going to execute idle processing, so restart the last idle timer _lastTimeUpdated.start(); From fff260c2b9bcadd9d1a0d5597dd34b895edc491d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 20 May 2016 09:27:50 -0700 Subject: [PATCH 180/264] Guard against zero-sized gl buffer copy --- libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp index e56d671891..ac337550ca 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendBuffer.cpp @@ -25,7 +25,7 @@ public: glBufferData(GL_ARRAY_BUFFER, _size, nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); - if (original) { + if (original && original->_size) { glBindBuffer(GL_COPY_WRITE_BUFFER, _buffer); glBindBuffer(GL_COPY_READ_BUFFER, original->_buffer); glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, original->_size); From 7866fcf78c9bd44a2e0e9979136e05ec2c56070e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 18 May 2016 23:23:11 -0700 Subject: [PATCH 181/264] trim high-point convex hulls --- .../physics/src/PhysicalEntitySimulation.cpp | 7 +++++++ libraries/physics/src/ShapeFactory.cpp | 18 ++++++++++++++++++ libraries/shared/src/ShapeInfo.cpp | 12 ++++++++++++ libraries/shared/src/ShapeInfo.h | 1 + 4 files changed, 38 insertions(+) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3fbf8ffaf5..95ae45bbb2 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -217,6 +217,13 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re } else if (entity->isReadyToComputeShape()) { ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); + int numPoints = shapeInfo.getMaxNumPoints(); + const int MAX_HULL_POINTS = 42; + if (numPoints > MAX_HULL_POINTS) { + glm::vec3 p = entity->getPosition(); + qDebug() << "entity" << entity->getName() << "at <" << p.x << p.y << p.z << ">" + << "has convex hull with" << numPoints << "points"; + } btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); if (shape) { EntityMotionState* motionState = new EntityMotionState(shape, entity); diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index de3e9cc794..8641873540 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -10,6 +10,7 @@ // #include +#include #include // for MILLIMETERS_PER_METER @@ -64,6 +65,23 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin correctedPoint = (points[i] - center) * relativeScale + center; hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } + + const int MAX_HULL_POINTS = 42; + if (points.size() > MAX_HULL_POINTS) { + // create hull approximation + btShapeHull* shapeHull = new btShapeHull(hull); + shapeHull->buildHull(margin); + btConvexHullShape* newHull = new btConvexHullShape(); + const btVector3* newPoints = shapeHull->getVertexPointer(); + for (int i = 0; i < shapeHull->numVertices(); ++i) { + newHull->addPoint(newPoints[i], false); + } + delete hull; + delete shapeHull; + hull = newHull; + qDebug() << "reduced hull with" << points.size() << "points down to" << hull->getNumPoints(); // TODO: remove after testing + } + hull->recalcLocalAabb(); return hull; } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 1d0cd56b86..0974c88e73 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -99,6 +99,18 @@ uint32_t ShapeInfo::getNumSubShapes() const { } return 1; } + +int ShapeInfo::getMaxNumPoints() const { + int numPoints = 0; + for (int i = 0; i < _points.size(); ++i) { + int n = _points[i].size(); + if (n > numPoints) { + numPoints = n; + } + } + return numPoints; +} + float ShapeInfo::computeVolume() const { const float DEFAULT_VOLUME = 1.0f; float volume = DEFAULT_VOLUME; diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 79390b6680..71fa12d5b5 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -61,6 +61,7 @@ public: void clearPoints () { _points.clear(); } void appendToPoints (const QVector& newPoints) { _points << newPoints; } + int getMaxNumPoints() const; float computeVolume() const; From 9fae1a7edd7ea016257cda3f0239db1de900be25 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 May 2016 00:16:25 -0700 Subject: [PATCH 182/264] tweak log format --- libraries/physics/src/PhysicalEntitySimulation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 95ae45bbb2..15296e15ab 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -221,8 +221,10 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re const int MAX_HULL_POINTS = 42; if (numPoints > MAX_HULL_POINTS) { glm::vec3 p = entity->getPosition(); - qDebug() << "entity" << entity->getName() << "at <" << p.x << p.y << p.z << ">" - << "has convex hull with" << numPoints << "points"; + qWarning().nospace() << "convex hull with " << numPoints + << " points for entity " << entity->getName() + << " at <" << p.x << ", " << p.y << ", " << p.z << ">" + << " will be reduced"; } btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); if (shape) { From 5a826d696a8438f9ea93a772211ae45c66b4bd50 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 May 2016 09:14:48 -0700 Subject: [PATCH 183/264] cleanup after code review --- libraries/physics/src/PhysicalEntitySimulation.cpp | 1 - libraries/physics/src/ShapeFactory.cpp | 10 ++++------ libraries/shared/src/ShapeInfo.h | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 15296e15ab..3b271f6a81 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -218,7 +218,6 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re ShapeInfo shapeInfo; entity->computeShapeInfo(shapeInfo); int numPoints = shapeInfo.getMaxNumPoints(); - const int MAX_HULL_POINTS = 42; if (numPoints > MAX_HULL_POINTS) { glm::vec3 p = entity->getPosition(); qWarning().nospace() << "convex hull with " << numPoints diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 8641873540..8c1d44b273 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -66,18 +66,16 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin hull->addPoint(btVector3(correctedPoint[0], correctedPoint[1], correctedPoint[2]), false); } - const int MAX_HULL_POINTS = 42; if (points.size() > MAX_HULL_POINTS) { // create hull approximation - btShapeHull* shapeHull = new btShapeHull(hull); - shapeHull->buildHull(margin); + btShapeHull shapeHull(hull); + shapeHull.buildHull(margin); btConvexHullShape* newHull = new btConvexHullShape(); - const btVector3* newPoints = shapeHull->getVertexPointer(); - for (int i = 0; i < shapeHull->numVertices(); ++i) { + const btVector3* newPoints = shapeHull.getVertexPointer(); + for (int i = 0; i < shapeHull.numVertices(); ++i) { newHull->addPoint(newPoints[i], false); } delete hull; - delete shapeHull; hull = newHull; qDebug() << "reduced hull with" << points.size() << "points down to" << hull->getNumPoints(); // TODO: remove after testing } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 71fa12d5b5..7c76703b77 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -21,6 +21,7 @@ #include "DoubleHashKey.h" const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored +const int MAX_HULL_POINTS = 42; enum ShapeType { SHAPE_TYPE_NONE, From f3fc00bc3a2056798cd8ed3505cd18d831d03c30 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 May 2016 09:20:44 -0700 Subject: [PATCH 184/264] add comment for why MAX_HULL_POINTS is 42 --- libraries/shared/src/ShapeInfo.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 7c76703b77..1632d22450 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -21,6 +21,9 @@ #include "DoubleHashKey.h" const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored + +// Bullet has a mesh generation util for convex shapes that we used to +// trim convex hulls with many points down to only 42 points. const int MAX_HULL_POINTS = 42; enum ShapeType { From 83b8ef8131a6d44b40d8e35b73921c789f08c6e3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 May 2016 13:18:18 -0700 Subject: [PATCH 185/264] clean up stream formatting of vec3 and friends --- .../physics/src/PhysicalEntitySimulation.cpp | 8 ++-- libraries/shared/src/StreamUtils.cpp | 45 ++++++------------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 3b271f6a81..6806b3a398 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -219,11 +219,9 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re entity->computeShapeInfo(shapeInfo); int numPoints = shapeInfo.getMaxNumPoints(); if (numPoints > MAX_HULL_POINTS) { - glm::vec3 p = entity->getPosition(); - qWarning().nospace() << "convex hull with " << numPoints - << " points for entity " << entity->getName() - << " at <" << p.x << ", " << p.y << ", " << p.z << ">" - << " will be reduced"; + qWarning() << "convex hull with" << numPoints + << "points for entity" << entity->getName() + << "at" << entity->getPosition() << " will be reduced"; } btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); if (shape) { diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index d4378d82b3..876de2e698 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -23,7 +23,7 @@ void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { while (i < buffer.size()) { for(int j = 0; i < buffer.size() && j < row_size; ++j) { char byte = buffer[i]; - s << hex_digits[(byte >> 4) & 0x0f] << hex_digits[byte & 0x0f] << " "; + s << hex_digits[(byte >> 4) & 0x0f] << hex_digits[byte & 0x0f] << ' '; ++i; } s << "\n"; @@ -31,21 +31,21 @@ void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { } std::ostream& operator<<(std::ostream& s, const glm::vec3& v) { - s << "<" << v.x << " " << v.y << " " << v.z << ">"; + s << '(' << v.x << ' ' << v.y << ' ' << v.z << ')'; return s; } std::ostream& operator<<(std::ostream& s, const glm::quat& q) { - s << "<" << q.x << " " << q.y << " " << q.z << " " << q.w << ">"; + s << '(' << q.x << ' ' << q.y << ' ' << q.z << ' ' << q.w << ')'; return s; } std::ostream& operator<<(std::ostream& s, const glm::mat4& m) { - s << "["; + s << '['; for (int j = 0; j < 4; ++j) { - s << " " << m[0][j] << " " << m[1][j] << " " << m[2][j] << " " << m[3][j] << ";"; + s << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';'; } - s << " ]"; + s << " ]"; return s; } @@ -69,54 +69,37 @@ QDataStream& operator>>(QDataStream& in, glm::quat& quaternion) { #include QDebug& operator<<(QDebug& dbg, const glm::vec2& v) { - dbg.nospace() << "{type='glm::vec2'" - ", x=" << v.x << - ", y=" << v.y << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::vec3& v) { - dbg.nospace() << "{type='glm::vec3'" - ", x=" << v.x << - ", y=" << v.y << - ", z=" << v.z << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ", " << v.z << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::vec4& v) { - dbg.nospace() << "{type='glm::vec4'" - ", x=" << v.x << - ", y=" << v.y << - ", z=" << v.z << - ", w=" << v.w << - "}"; + dbg.nospace() << '(' << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::quat& q) { - dbg.nospace() << "{type='glm::quat'" - ", x=" << q.x << - ", y=" << q.y << - ", z=" << q.z << - ", w=" << q.w << - "}"; + dbg.nospace() << '(' << q.x << ", " << q.y << ", " << q.z << ", " << q.w << ')'; return dbg; } QDebug& operator<<(QDebug& dbg, const glm::mat4& m) { - dbg.nospace() << "{type='glm::mat4', ["; + dbg.nospace() << '['; for (int j = 0; j < 4; ++j) { dbg << ' ' << m[0][j] << ' ' << m[1][j] << ' ' << m[2][j] << ' ' << m[3][j] << ';'; } - return dbg << " ]}"; + return dbg << " ]"; } QDebug& operator<<(QDebug& dbg, const QVariantHash& v) { - dbg.nospace() << "["; + dbg.nospace() << "[ "; for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) { - dbg << it.key() << ":" << it.value(); + dbg << it.key() << ':' << it.value(); } return dbg << " ]"; } From aa58cad93e51ef078a3d6230c15d99b94cf5f86f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 09:54:54 -0700 Subject: [PATCH 186/264] code review --- libraries/script-engine/src/ScriptEngine.cpp | 7 +++++++ libraries/script-engine/src/ScriptEngine.h | 4 +++- libraries/script-engine/src/ScriptEngines.cpp | 5 +---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 67b87ae5e0..d8e0397347 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -938,6 +938,13 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { } void ScriptEngine::stop() { + _isStopping = true; // this can be done on any thread + + // marshal us over to the correct thread + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stop"); + return; + } if (!_isFinished) { _isFinished = true; emit runningStateChanged(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 512e79fb95..6af6aef338 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -84,7 +84,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(); + Q_INVOKABLE void stop(); // this can be called from any thread // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); @@ -146,6 +146,8 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget + + // these are used by code in ScriptEngines.cpp during the "reload all" operation bool isStopping() const { return _isStopping; } void setIsStopping() { _isStopping = true; } diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 8b57487db0..0d1f0de3c9 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -160,7 +160,6 @@ void ScriptEngines::shutdownScripting() { scriptEngine->disconnect(this); // Gracefully stop the engine's scripting thread - scriptEngine->setIsStopping(); scriptEngine->stop(); // We need to wait for the engine to be done running before we proceed, because we don't @@ -352,8 +351,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } - it.value()->setIsStopping(); - QMetaObject::invokeMethod(it.value(), "stop"); + it.value()->stop(); qCDebug(scriptengine) << "stopping script..." << it.key(); } } @@ -376,7 +374,6 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { reloadScript(scriptName); }); } - scriptEngine->setIsStopping(); scriptEngine->stop(); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; From 35065ab05ecb7384907f47a6360fda08c187cfd1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 10:12:38 -0700 Subject: [PATCH 187/264] remove unused setter --- libraries/script-engine/src/ScriptEngine.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 6af6aef338..2222503781 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -149,7 +149,6 @@ public: // these are used by code in ScriptEngines.cpp during the "reload all" operation bool isStopping() const { return _isStopping; } - void setIsStopping() { _isStopping = true; } bool isDebuggable() const { return _debuggable; } From 1ef0f8055bcd0f9b0a981bd24f16f6d6b8cb67fc Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 10:12:59 -0700 Subject: [PATCH 188/264] fix grammar in comment --- libraries/script-engine/src/ScriptEngine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 2222503781..ef7e075021 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -147,7 +147,7 @@ public: bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget bool isRunning() const { return _isRunning; } // used by ScriptWidget - // these are used by code in ScriptEngines.cpp during the "reload all" operation + // this is used by code in ScriptEngines.cpp during the "reload all" operation bool isStopping() const { return _isStopping; } bool isDebuggable() const { return _debuggable; } From 0353e562e4589e83322fd36a3e0f9c6c2f4e27a1 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 19 May 2016 16:55:13 -0700 Subject: [PATCH 189/264] entityProperties.html handles non object userData --- scripts/system/html/entityProperties.html | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index ae5684d6c8..e0bcf0efec 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -626,18 +626,19 @@ var parsedUserData = {} try { parsedUserData = JSON.parse(properties.userData); + + if ("grabbableKey" in parsedUserData) { + if ("grabbable" in parsedUserData["grabbableKey"]) { + elGrabbable.checked = parsedUserData["grabbableKey"].grabbable; + } + if ("wantsTrigger" in parsedUserData["grabbableKey"]) { + elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger; + } + if ("ignoreIK" in parsedUserData["grabbableKey"]) { + elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; + } + } } catch(e) {} - if ("grabbableKey" in parsedUserData) { - if ("grabbable" in parsedUserData["grabbableKey"]) { - elGrabbable.checked = parsedUserData["grabbableKey"].grabbable; - } - if ("wantsTrigger" in parsedUserData["grabbableKey"]) { - elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger; - } - if ("ignoreIK" in parsedUserData["grabbableKey"]) { - elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; - } - } elCollisionSoundURL.value = properties.collisionSoundURL; elLifetime.value = properties.lifetime; From 51c16739f2a68d7eaa33344b8b385b4919f2f5e9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 12:05:22 -0700 Subject: [PATCH 190/264] provide a getter for the place name in AddressManager --- libraries/networking/src/AddressManager.cpp | 5 +++++ libraries/networking/src/AddressManager.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index a97f4df35d..02962b636b 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -295,10 +295,13 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const // set our current root place name to the name that came back const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); + if (!placeName.isEmpty()) { if (setHost(placeName, trigger)) { trigger = LookupTrigger::Internal; } + + _placeName = placeName; } else { if (setHost(domainIDString, trigger)) { trigger = LookupTrigger::Internal; @@ -580,7 +583,9 @@ bool AddressManager::setHost(const QString& host, LookupTrigger trigger, quint16 bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { bool hostChanged = setHost(hostname, trigger, port); + // clear any current place information _rootPlaceID = QUuid(); + _placeName.clear(); qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dd0dbd9f38..643924ff5c 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -58,6 +58,7 @@ public: const QString currentPath(bool withOrientation = true) const; const QUuid& getRootPlaceID() const { return _rootPlaceID; } + const QString& getPlaceName() const { return _placeName; } const QString& getHost() const { return _host; } @@ -141,6 +142,7 @@ private: QString _host; quint16 _port; + QString _placeName; QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; From 7110fe98eba05dd7928aef5cf259eb23dbb6ba62 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 12:13:22 -0700 Subject: [PATCH 191/264] associate incoming place name with DomainServerNodeData --- domain-server/src/DomainGatekeeper.cpp | 1 + domain-server/src/DomainServerNodeData.h | 5 +++++ domain-server/src/NodeConnectionData.cpp | 1 + domain-server/src/NodeConnectionData.h | 1 + libraries/networking/src/NodeList.cpp | 3 +++ libraries/networking/src/udt/PacketHeaders.cpp | 3 +++ 6 files changed, 14 insertions(+) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 919ac37ee9..76671461d3 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -105,6 +105,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer(node->getLinkedData()); nodeData->setSendingSockAddr(message->getSenderSockAddr()); nodeData->setNodeInterestSet(nodeConnection.interestList.toSet()); + nodeData->setPlaceName(nodeConnection.placeName); // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index cd68aa3006..3ef5415cba 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -56,6 +56,9 @@ public: void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue); void removeOverrideForKey(const QString& key, const QString& value); + + const QString& getPlaceName() { return _placeName; } + void setPlaceName(const QString& placeName) { _placeName; } private: QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats); @@ -75,6 +78,8 @@ private: bool _isAuthenticated = true; NodeSet _nodeInterestSet; QString _nodeVersion; + + QString _placeName; }; #endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 80cb5950be..eabcfaacc6 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,6 +19,7 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; + dataStream >> newHeader.placeName; } dataStream >> newHeader.nodeType diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 6b3b8eb7c1..34119ffdab 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -27,6 +27,7 @@ public: HifiSockAddr localSockAddr; HifiSockAddr senderSockAddr; QList interestList; + QString placeName; }; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 482d0366fd..cee6404755 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -312,6 +312,9 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; + + // pack the hostname information (so the domain-server can see which place name we came in on) + packetStream << DependencyManager::get()->getPlaceName(); } // pack our data to send to the domain-server diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e4aab94090..f6ad6b2970 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -58,6 +58,9 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + case PacketType::DomainConnectRequest: + // addition of referring hostname information + return 18; default: return 17; } From 3b2a9b7b98995298d44a316671c08331995e25b2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 13:02:34 -0700 Subject: [PATCH 192/264] fix set of place name on DomainServerNodeData --- domain-server/src/DomainServerNodeData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 3ef5415cba..a14d7ff768 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -58,7 +58,7 @@ public: void removeOverrideForKey(const QString& key, const QString& value); const QString& getPlaceName() { return _placeName; } - void setPlaceName(const QString& placeName) { _placeName; } + void setPlaceName(const QString& placeName) { _placeName = placeName; } private: QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats); From 5884e1eadd03e62d49c92a6dd7f7a222760dda56 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 13:03:18 -0700 Subject: [PATCH 193/264] rename metaverse heartbeat methods --- domain-server/src/DomainServer.cpp | 10 +++++----- domain-server/src/DomainServer.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0aab6b7e31..e8199317f0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -462,7 +462,7 @@ void DomainServer::setupAutomaticNetworking() { nodeList->startSTUNPublicSocketUpdate(); } else { // send our heartbeat to data server so it knows what our network settings are - sendHeartbeatToDataServer(); + sendHeartbeatToMetaverse(); } } else { qDebug() << "Cannot enable domain-server automatic networking without a domain ID." @@ -471,7 +471,7 @@ void DomainServer::setupAutomaticNetworking() { return; } } else { - sendHeartbeatToDataServer(); + sendHeartbeatToMetaverse(); } qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; @@ -480,7 +480,7 @@ void DomainServer::setupAutomaticNetworking() { const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000; QTimer* dataHeartbeatTimer = new QTimer(this); - connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer())); + connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse())); dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS); } @@ -1029,11 +1029,11 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking"; void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) { - sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString()); + sendHeartbeatToMetaverse(newPublicSockAddr.getAddress().toString()); } -void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { +void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { const QString DOMAIN_UPDATE = "/api/v1/domains/%1"; auto nodeList = DependencyManager::get(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 93bb5de494..fef3221b7d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -71,7 +71,7 @@ private slots: void sendPendingTransactionsToServer(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); - void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } + void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); } void sendHeartbeatToIceServer(); void handleConnectedNode(SharedNodePointer newNode); @@ -103,7 +103,7 @@ private: void setupAutomaticNetworking(); void setupICEHeartbeatForFullNetworking(); - void sendHeartbeatToDataServer(const QString& networkAddress); + void sendHeartbeatToMetaverse(const QString& networkAddress); void randomizeICEServerAddress(bool shouldTriggerHostLookup); From 7d2d60f2005e5f931bbcae12a1ab253360e80d8e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 13:08:24 -0700 Subject: [PATCH 194/264] split assigned and un-assigned nodes --- domain-server/src/DomainGatekeeper.cpp | 3 ++- domain-server/src/DomainServerNodeData.h | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 76671461d3..e974ab26f4 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -151,6 +151,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID()); nodeData->setWalletUUID(it->second.getWalletUUID()); nodeData->setNodeVersion(it->second.getNodeVersion()); + nodeData->setWasAssigned(true); // cleanup the PendingAssignedNodeData for this assignment now that it's connecting _pendingAssignedNodes.erase(it); @@ -283,7 +284,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // set the edit rights for this user newNode->setIsAllowedEditor(isAllowedEditor); newNode->setCanRez(canRez); - + // grab the linked data for our new node so we can set the username DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index a14d7ff768..f95403c779 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -59,6 +59,9 @@ public: const QString& getPlaceName() { return _placeName; } void setPlaceName(const QString& placeName) { _placeName = placeName; } + + bool wasAssigned() const { return _wasAssigned; }; + void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; } private: QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats); @@ -80,6 +83,8 @@ private: QString _nodeVersion; QString _placeName; + + bool _wasAssigned { false }; }; #endif // hifi_DomainServerNodeData_h From 962066c7d10fd9f5df126002f849b298477f0c06 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 13:19:38 -0700 Subject: [PATCH 195/264] send user hostname breakdown with heartbeat --- domain-server/src/DomainServer.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e8199317f0..75f1c9f6b6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1056,20 +1056,34 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { domainObject[RESTRICTED_ACCESS_FLAG] = _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool(); - // add the number of currently connected agent users - int numConnectedAuthedUsers = 0; + // figure out the breakdown of currently connected interface clients + int numConnectedUnassigned = 0; + QJsonObject userHostnames; - nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){ - if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) { - ++numConnectedAuthedUsers; + static const QString DEFAULT_HOSTNAME = "*"; + + nodeList->eachNode([&numConnectedUnassigned, &userHostnames](const SharedNodePointer& node) { + if (node->getLinkedData()) { + auto nodeData = static_cast(node->getLinkedData()); + + if (!nodeData->wasAssigned()) { + ++numConnectedUnassigned; + + // increment the count for this hostname (or the default if we don't have one) + auto hostname = nodeData->getPlaceName().isEmpty() ? DEFAULT_HOSTNAME : nodeData->getPlaceName(); + userHostnames[hostname] = userHostnames[hostname].toInt() + 1; + } } }); - const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; - const QString HEARTBEAT_NUM_USERS_KEY = "num_users"; + static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat"; + static const QString HEARTBEAT_NUM_USERS_KEY = "num_users"; + static const QString HEARTBEAT_USER_HOSTNAMES_KEY = "user_hostnames"; QJsonObject heartbeatObject; - heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers; + heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedUnassigned; + heartbeatObject[HEARTBEAT_USER_HOSTNAMES_KEY] = userHostnames; + domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject; QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson())); From 5ab876114f86a715cc380c5214848a797dd9cf99 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 13:47:14 -0700 Subject: [PATCH 196/264] send hostname to DS with every DS packet to handle changes --- domain-server/src/DomainGatekeeper.cpp | 4 ++-- domain-server/src/DomainServer.cpp | 3 +++ domain-server/src/NodeConnectionData.cpp | 3 +-- libraries/networking/src/NodeList.cpp | 9 ++++----- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index e974ab26f4..61cc775e08 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -55,9 +55,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } - + QDataStream packetStream(message->getMessage()); - + // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr()); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 75f1c9f6b6..cfec72a24b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -678,6 +678,9 @@ void DomainServer::processListRequestPacket(QSharedPointer mess DomainServerNodeData* nodeData = reinterpret_cast(sendingNode->getLinkedData()); nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet()); + // update the connecting hostname in case it has changed + nodeData->setPlaceName(nodeRequestData.placeName); + sendDomainListToNode(sendingNode, message->getSenderSockAddr()); } diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index eabcfaacc6..28f769298c 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,12 +19,11 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; - dataStream >> newHeader.placeName; } dataStream >> newHeader.nodeType >> newHeader.publicSockAddr >> newHeader.localSockAddr - >> newHeader.interestList; + >> newHeader.interestList >> newHeader.placeName; newHeader.senderSockAddr = senderSockAddr; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index cee6404755..c295ffc700 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -312,13 +312,12 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; - - // pack the hostname information (so the domain-server can see which place name we came in on) - packetStream << DependencyManager::get()->getPlaceName(); } - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + // pack our data to send to the domain-server including + // the hostname information (so the domain-server can see which place name we came in on) + packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList() + << DependencyManager::get()->getPlaceName(); if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); From 33379824c8d0ad122d0e5435a9fa7c88ae59042b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 10 May 2016 15:15:22 -0700 Subject: [PATCH 197/264] clear place name if switching to a domain ID string --- libraries/networking/src/AddressManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 02962b636b..36d9e621a9 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -306,6 +306,9 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const if (setHost(domainIDString, trigger)) { trigger = LookupTrigger::Internal; } + + // this isn't a place, so clear the place name + _placeName.clear(); } // check if we had a path to override the path returned From 76f4a25694f1d3adf82e92f0683a5ca759747878 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 11:40:46 -0700 Subject: [PATCH 198/264] move code that clears old scale and registration to after model is loaded --- .../src/RenderableModelEntityItem.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a537ecd0f3..7b51283bac 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -48,13 +48,6 @@ RenderableModelEntityItem::~RenderableModelEntityItem() { void RenderableModelEntityItem::setModelURL(const QString& url) { auto& currentURL = getParsedModelURL(); - if (_model && (currentURL != url)) { - // The machinery for updateModelBounds will give existing models the opportunity to fix their translation/rotation/scale/registration. - // The first two are straightforward, but the latter two have guards to make sure they don't happen after they've already been set. - // Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in. - _model->setScaleToFit(false, getDimensions()); - _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); - } ModelEntityItem::setModelURL(url); if (currentURL != getParsedModelURL() || !_model) { @@ -163,6 +156,14 @@ void RenderableModelEntityItem::remapTextures() { } void RenderableModelEntityItem::doInitialModelSimulation() { + // The machinery for updateModelBounds will give existing models the opportunity to fix their + // translation/rotation/scale/registration. The first two are straightforward, but the latter two have guards to + // make sure they don't happen after they've already been set. Here we reset those guards. This doesn't cause the + // entity values to change -- it just allows the model to match once it comes in. + _model->setScaleToFit(false, getDimensions()); + _model->setSnapModelToRegistrationPoint(false, getRegistrationPoint()); + + // now recalculate the bounds and registration _model->setScaleToFit(true, getDimensions()); _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); _model->setRotation(getRotation()); From c299aef8f214b39b5dd51acb831292be129d7f82 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 20 May 2016 12:15:30 -0700 Subject: [PATCH 199/264] fix 588, 558 --- scripts/system/controllers/handControllerPointer.js | 8 ++++++-- scripts/system/edit.js | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 78b7c4eb84..f4e4492a88 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -178,7 +178,10 @@ var NON_LINEAR_DIVISOR = 2; var MINIMUM_SEEK_DISTANCE = 0.01; function updateSeeking() { if (!Reticle.visible || isShakingMouse()) { - isSeeking = true; + if (!isSeeking) { + print('Start seeking mouse.'); + isSeeking = true; + } } // e.g., if we're about to turn it on with first movement. if (!isSeeking) { return; @@ -203,6 +206,7 @@ function updateSeeking() { } var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit. if (okX && okY) { + print('Finished seeking mouse'); isSeeking = false; } else { Reticle.setPosition(copy); // Not setReticlePosition @@ -444,7 +448,7 @@ function update() { updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); } -var UPDATE_INTERVAL = 20; // milliseconds. Script.update is too frequent. +var UPDATE_INTERVAL = 50; // milliseconds. Script.update is too frequent. var updater = Script.setInterval(update, UPDATE_INTERVAL); Script.scriptEnding.connect(function () { Script.clearInterval(updater); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e84cdf7971..afbc679ec4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -352,7 +352,9 @@ var toolBar = (function() { gridTool.setVisible(true); grid.setEnabled(true); propertiesTool.setVisible(true); - Window.setFocus(); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); } that.showTools(isActive); } From 770fab956fac0502b0a95d68f624d20d50bce533 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 20 May 2016 12:23:06 -0700 Subject: [PATCH 200/264] remove dead code --- interface/src/Application.cpp | 4 ---- interface/src/Menu.cpp | 3 --- interface/src/Menu.h | 1 - libraries/entities/src/EntityEditPacketSender.cpp | 4 +--- libraries/entities/src/EntityEditPacketSender.h | 4 ---- 5 files changed, 1 insertion(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6190de5875..5ce869e5c9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3594,10 +3594,6 @@ void Application::update(float deltaTime) { int Application::sendNackPackets() { - if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { - return 0; - } - // iterates through all nodes in NodeList auto nodeList = DependencyManager::get(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8b69bb8022..883c30a3b8 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -531,9 +531,6 @@ Menu::Menu() { // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); - addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, - qApp->getEntityEditPacketSender(), - SLOT(toggleNackPackets())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableActivityLogger, 0, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 484be9f346..a58a103504 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -84,7 +84,6 @@ namespace MenuOption { const QString DisableActivityLogger = "Disable Activity Logger"; const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment"; const QString DisableLightEntities = "Disable Light Entities"; - const QString DisableNackPackets = "Disable Entity NACK Packets"; const QString DiskCacheEditor = "Disk Cache Editor"; const QString DisplayCrashOptions = "Display Crash Options"; const QString DisplayHandTargets = "Show Hand Targets"; diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 1e38c32964..ad936cc890 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -24,9 +24,7 @@ EntityEditPacketSender::EntityEditPacketSender() { } void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode) { - if (_shouldProcessNack) { - processNackPacket(*message, sendingNode); - } + processNackPacket(*message, sendingNode); } void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) { diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 26e4dd83ff..0492fc66f4 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -36,9 +36,5 @@ public: public slots: void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); - void toggleNackPackets() { _shouldProcessNack = !_shouldProcessNack; } - -private: - bool _shouldProcessNack = true; }; #endif // hifi_EntityEditPacketSender_h From 00bd3b2e8a60577f25d081078f4bfe1d98ea7dee Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 20 May 2016 13:43:08 -0700 Subject: [PATCH 201/264] remove more dead code --- interface/resources/qml/hifi/MenuOption.qml | 165 -------------------- 1 file changed, 165 deletions(-) delete mode 100644 interface/resources/qml/hifi/MenuOption.qml diff --git a/interface/resources/qml/hifi/MenuOption.qml b/interface/resources/qml/hifi/MenuOption.qml deleted file mode 100644 index 46cf5d9662..0000000000 --- a/interface/resources/qml/hifi/MenuOption.qml +++ /dev/null @@ -1,165 +0,0 @@ -import QtQuick 2.5 - -QtObject { - readonly property string aboutApp: "About Interface"; - readonly property string addRemoveFriends: "Add/Remove Friends..."; - readonly property string addressBar: "Show Address Bar"; - readonly property string animations: "Animations..."; - readonly property string animDebugDrawAnimPose: "Debug Draw Animation"; - readonly property string animDebugDrawDefaultPose: "Debug Draw Default Pose"; - readonly property string animDebugDrawPosition: "Debug Draw Position"; - readonly property string antialiasing: "Antialiasing"; - readonly property string assetMigration: "ATP Asset Migration"; - readonly property string assetServer: "Asset Server"; - readonly property string atmosphere: "Atmosphere"; - readonly property string attachments: "Attachments..."; - readonly property string audioNetworkStats: "Audio Network Stats"; - readonly property string audioNoiseReduction: "Audio Noise Reduction"; - readonly property string audioScope: "Show Scope"; - readonly property string audioScopeFiftyFrames: "Fifty"; - readonly property string audioScopeFiveFrames: "Five"; - readonly property string audioScopeFrames: "Display Frames"; - readonly property string audioScopePause: "Pause Scope"; - readonly property string audioScopeTwentyFrames: "Twenty"; - readonly property string audioStatsShowInjectedStreams: "Audio Stats Show Injected Streams"; - readonly property string audioTools: "Show Level Meter"; - readonly property string autoMuteAudio: "Auto Mute Microphone"; - readonly property string avatarReceiveStats: "Show Receive Stats"; - readonly property string back: "Back"; - readonly property string bandwidthDetails: "Bandwidth Details"; - readonly property string binaryEyelidControl: "Binary Eyelid Control"; - readonly property string bookmarkLocation: "Bookmark Location"; - readonly property string bookmarks: "Bookmarks"; - readonly property string cachesSize: "RAM Caches Size"; - readonly property string calibrateCamera: "Calibrate Camera"; - readonly property string cameraEntityMode: "Entity Mode"; - readonly property string centerPlayerInView: "Center Player In View"; - readonly property string chat: "Chat..."; - readonly property string collisions: "Collisions"; - readonly property string connexion: "Activate 3D Connexion Devices"; - readonly property string console_: "Console..."; - readonly property string controlWithSpeech: "Control With Speech"; - readonly property string copyAddress: "Copy Address to Clipboard"; - readonly property string copyPath: "Copy Path to Clipboard"; - readonly property string coupleEyelids: "Couple Eyelids"; - readonly property string crashInterface: "Crash Interface"; - readonly property string debugAmbientOcclusion: "Debug Ambient Occlusion"; - readonly property string decreaseAvatarSize: "Decrease Avatar Size"; - readonly property string deleteBookmark: "Delete Bookmark..."; - readonly property string disableActivityLogger: "Disable Activity Logger"; - readonly property string disableEyelidAdjustment: "Disable Eyelid Adjustment"; - readonly property string disableLightEntities: "Disable Light Entities"; - readonly property string disableNackPackets: "Disable Entity NACK Packets"; - readonly property string diskCacheEditor: "Disk Cache Editor"; - readonly property string displayCrashOptions: "Display Crash Options"; - readonly property string displayHandTargets: "Show Hand Targets"; - readonly property string displayModelBounds: "Display Model Bounds"; - readonly property string displayModelTriangles: "Display Model Triangles"; - readonly property string displayModelElementChildProxies: "Display Model Element Children"; - readonly property string displayModelElementProxy: "Display Model Element Bounds"; - readonly property string displayDebugTimingDetails: "Display Timing Details"; - readonly property string dontDoPrecisionPicking: "Don't Do Precision Picking"; - readonly property string dontRenderEntitiesAsScene: "Don't Render Entities as Scene"; - readonly property string echoLocalAudio: "Echo Local Audio"; - readonly property string echoServerAudio: "Echo Server Audio"; - readonly property string enable3DTVMode: "Enable 3DTV Mode"; - readonly property string enableCharacterController: "Enable avatar collisions"; - readonly property string expandMyAvatarSimulateTiming: "Expand /myAvatar/simulation"; - readonly property string expandMyAvatarTiming: "Expand /myAvatar"; - readonly property string expandOtherAvatarTiming: "Expand /otherAvatar"; - readonly property string expandPaintGLTiming: "Expand /paintGL"; - readonly property string expandUpdateTiming: "Expand /update"; - readonly property string faceshift: "Faceshift"; - readonly property string firstPerson: "First Person"; - readonly property string fivePointCalibration: "5 Point Calibration"; - readonly property string fixGaze: "Fix Gaze (no saccade)"; - readonly property string forward: "Forward"; - readonly property string frameTimer: "Show Timer"; - readonly property string fullscreenMirror: "Mirror"; - readonly property string help: "Help..."; - readonly property string increaseAvatarSize: "Increase Avatar Size"; - readonly property string independentMode: "Independent Mode"; - readonly property string inputMenu: "Avatar>Input Devices"; - readonly property string keyboardMotorControl: "Enable Keyboard Motor Control"; - readonly property string leapMotionOnHMD: "Leap Motion on HMD"; - readonly property string loadScript: "Open and Run Script File..."; - readonly property string loadScriptURL: "Open and Run Script from URL..."; - readonly property string lodTools: "LOD Tools"; - readonly property string login: "Login"; - readonly property string log: "Log"; - readonly property string logExtraTimings: "Log Extra Timing Details"; - readonly property string lowVelocityFilter: "Low Velocity Filter"; - readonly property string meshVisible: "Draw Mesh"; - readonly property string miniMirror: "Mini Mirror"; - readonly property string muteAudio: "Mute Microphone"; - readonly property string muteEnvironment: "Mute Environment"; - readonly property string muteFaceTracking: "Mute Face Tracking"; - readonly property string namesAboveHeads: "Names Above Heads"; - readonly property string noFaceTracking: "None"; - readonly property string octreeStats: "Entity Statistics"; - readonly property string onePointCalibration: "1 Point Calibration"; - readonly property string onlyDisplayTopTen: "Only Display Top Ten"; - readonly property string outputMenu: "Display"; - readonly property string packageModel: "Package Model..."; - readonly property string pair: "Pair"; - readonly property string physicsShowOwned: "Highlight Simulation Ownership"; - readonly property string physicsShowHulls: "Draw Collision Hulls"; - readonly property string pipelineWarnings: "Log Render Pipeline Warnings"; - readonly property string preferences: "General..."; - readonly property string quit: "Quit"; - readonly property string reloadAllScripts: "Reload All Scripts"; - readonly property string reloadContent: "Reload Content (Clears all caches)"; - readonly property string renderBoundingCollisionShapes: "Show Bounding Collision Shapes"; - readonly property string renderFocusIndicator: "Show Eye Focus"; - readonly property string renderLookAtTargets: "Show Look-at Targets"; - readonly property string renderLookAtVectors: "Show Look-at Vectors"; - readonly property string renderResolution: "Scale Resolution"; - readonly property string renderResolutionOne: "1"; - readonly property string renderResolutionTwoThird: "2/3"; - readonly property string renderResolutionHalf: "1/2"; - readonly property string renderResolutionThird: "1/3"; - readonly property string renderResolutionQuarter: "1/4"; - readonly property string renderAmbientLight: "Ambient Light"; - readonly property string renderAmbientLightGlobal: "Global"; - readonly property string renderAmbientLight0: "OLD_TOWN_SQUARE"; - readonly property string renderAmbientLight1: "GRACE_CATHEDRAL"; - readonly property string renderAmbientLight2: "EUCALYPTUS_GROVE"; - readonly property string renderAmbientLight3: "ST_PETERS_BASILICA"; - readonly property string renderAmbientLight4: "UFFIZI_GALLERY"; - readonly property string renderAmbientLight5: "GALILEOS_TOMB"; - readonly property string renderAmbientLight6: "VINE_STREET_KITCHEN"; - readonly property string renderAmbientLight7: "BREEZEWAY"; - readonly property string renderAmbientLight8: "CAMPUS_SUNSET"; - readonly property string renderAmbientLight9: "FUNSTON_BEACH_SUNSET"; - readonly property string resetAvatarSize: "Reset Avatar Size"; - readonly property string resetSensors: "Reset Sensors"; - readonly property string runningScripts: "Running Scripts..."; - readonly property string runTimingTests: "Run Timing Tests"; - readonly property string scriptEditor: "Script Editor..."; - readonly property string scriptedMotorControl: "Enable Scripted Motor Control"; - readonly property string showDSConnectTable: "Show Domain Connection Timing"; - readonly property string showBordersEntityNodes: "Show Entity Nodes"; - readonly property string showRealtimeEntityStats: "Show Realtime Entity Stats"; - readonly property string showWhosLookingAtMe: "Show Who's Looking at Me"; - readonly property string standingHMDSensorMode: "Standing HMD Sensor Mode"; - readonly property string simulateEyeTracking: "Simulate"; - readonly property string sMIEyeTracking: "SMI Eye Tracking"; - readonly property string stars: "Stars"; - readonly property string stats: "Stats"; - readonly property string stopAllScripts: "Stop All Scripts"; - readonly property string suppressShortTimings: "Suppress Timings Less than 10ms"; - readonly property string thirdPerson: "Third Person"; - readonly property string threePointCalibration: "3 Point Calibration"; - readonly property string throttleFPSIfNotFocus: "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp - readonly property string toolWindow: "Tool Window"; - readonly property string transmitterDrive: "Transmitter Drive"; - readonly property string turnWithHead: "Turn using Head"; - readonly property string useAudioForMouth: "Use Audio for Mouth"; - readonly property string useCamera: "Use Camera"; - readonly property string velocityFilter: "Velocity Filter"; - readonly property string visibleToEveryone: "Everyone"; - readonly property string visibleToFriends: "Friends"; - readonly property string visibleToNoOne: "No one"; - readonly property string worldAxes: "World Axes"; -} - From 07562f72af18341b42069a12aa251c64e4e45e8e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 11 May 2016 17:23:24 -0700 Subject: [PATCH 202/264] Doing a pass over the input plugins and controller code --- interface/CMakeLists.txt | 4 - .../resources/controllers/spacemouse.json | 4 +- interface/src/Application.cpp | 55 ++--- interface/src/Menu.cpp | 13 +- interface/src/Menu.h | 2 +- .../controllers/src/controllers/Actions.cpp | 8 - .../controllers/src/controllers/Actions.h | 10 +- .../controllers/src/controllers/InputDevice.h | 4 +- .../src/controllers/StandardController.cpp | 6 - .../src/controllers/StandardController.h | 2 - .../src/controllers/StateController.h | 5 +- .../src/input-plugins/KeyboardMouseDevice.cpp | 7 +- .../src/input-plugins/KeyboardMouseDevice.h | 11 +- libraries/plugins/src/plugins/InputPlugin.h | 4 +- plugins/hifiNeuron/CMakeLists.txt | 11 +- plugins/hifiNeuron/src/NeuronPlugin.cpp | 118 ++------- plugins/hifiNeuron/src/NeuronPlugin.h | 5 +- plugins/hifiSdl2/src/Joystick.cpp | 11 +- plugins/hifiSdl2/src/Joystick.h | 15 +- plugins/hifiSdl2/src/SDL2Manager.cpp | 34 +-- plugins/hifiSdl2/src/SDL2Manager.h | 25 +- plugins/hifiSixense/src/SixenseManager.cpp | 18 +- plugins/hifiSixense/src/SixenseManager.h | 5 +- plugins/hifiSpacemouse/CMakeLists.txt | 18 ++ .../hifiSpacemouse/src}/SpacemouseManager.cpp | 230 +++++++++--------- .../hifiSpacemouse/src}/SpacemouseManager.h | 63 ++--- .../hifiSpacemouse/src/SpacemouseProvider.cpp | 45 ++++ plugins/hifiSpacemouse/src/plugin.json | 1 + .../oculus/src/OculusControllerManager.cpp | 30 ++- plugins/oculus/src/OculusControllerManager.h | 7 +- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 7 +- plugins/openvr/src/OpenVrHelpers.cpp | 7 + plugins/openvr/src/OpenVrHelpers.h | 3 +- plugins/openvr/src/ViveControllerManager.cpp | 17 +- plugins/openvr/src/ViveControllerManager.h | 20 +- tests/controllers/src/main.cpp | 4 +- 36 files changed, 333 insertions(+), 496 deletions(-) create mode 100644 plugins/hifiSpacemouse/CMakeLists.txt rename {libraries/input-plugins/src/input-plugins => plugins/hifiSpacemouse/src}/SpacemouseManager.cpp (95%) rename {libraries/input-plugins/src/input-plugins => plugins/hifiSpacemouse/src}/SpacemouseManager.h (77%) create mode 100644 plugins/hifiSpacemouse/src/SpacemouseProvider.cpp create mode 100644 plugins/hifiSpacemouse/src/plugin.json diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4f58cf73db..4381f3321b 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -4,10 +4,6 @@ project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") -if(WIN32) - list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") -endif() - foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) diff --git a/interface/resources/controllers/spacemouse.json b/interface/resources/controllers/spacemouse.json index adcfef71a6..1480e1957d 100644 --- a/interface/resources/controllers/spacemouse.json +++ b/interface/resources/controllers/spacemouse.json @@ -2,11 +2,11 @@ "name": "Spacemouse to Standard", "channels": [ - { "from": "Spacemouse.TranslateX", "to": "Standard.RX" }, + { "from": "Spacemouse.TranslateX", "to": "Standard.LX" }, { "from": "Spacemouse.TranslateY", "to": "Standard.LY" }, { "from": "Spacemouse.TranslateZ", "to": "Standard.RY" }, - { "from": "Spacemouse.RotateZ", "to": "Standard.LX" }, + { "from": "Spacemouse.RotateZ", "to": "Standard.RX" }, { "from": "Spacemouse.LeftButton", "to": "Standard.LB" }, { "from": "Spacemouse.RightButton", "to": "Standard.RB" } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6190de5875..8ebb9a1f9d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -108,7 +108,6 @@ #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "CrashHandler.h" -#include "input-plugins/SpacemouseManager.h" #include "devices/DdeFaceTracker.h" #include "devices/EyeTracker.h" #include "devices/Faceshift.h" @@ -199,6 +198,7 @@ static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check f static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +static const QString INPUT_DEVICE_MENU_PREFIX = "Device: "; Setting::Handle maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); const QHash Application::_acceptedExtensions { @@ -997,7 +997,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); if (webEntity) { webEntity->setProxyWindow(_window->windowHandle()); - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->pluginFocusOutEvent(); } _keyboardFocusedItem = entityItemID; @@ -1121,7 +1121,7 @@ void Application::aboutToQuit() { emit beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = inputPlugin->getName(); + QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); QAction* action = Menu::getInstance()->getActionForOption(name); if (action->isChecked()) { inputPlugin->deactivate(); @@ -1440,8 +1440,7 @@ void Application::initializeUi() { // This will set up the input plugins UI _activeInputPlugins.clear(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = inputPlugin->getName(); - if (name == KeyboardMouseDevice::NAME) { + if (KeyboardMouseDevice::NAME == inputPlugin->getName()) { _keyboardMouseDevice = std::dynamic_pointer_cast(inputPlugin); } } @@ -1985,7 +1984,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->keyPressEvent(event); } @@ -2319,7 +2318,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->keyReleaseEvent(event); } @@ -2351,7 +2350,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { void Application::focusOutEvent(QFocusEvent* event) { auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); + QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); QAction* action = Menu::getInstance()->getActionForOption(name); if (action && action->isChecked()) { inputPlugin->pluginFocusOutEvent(); @@ -2438,7 +2437,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->mouseMoveEvent(event); } @@ -2475,7 +2474,7 @@ void Application::mousePressEvent(QMouseEvent* event) { if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->mousePressEvent(event); } @@ -2520,7 +2519,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->mouseReleaseEvent(event); } @@ -2547,7 +2546,7 @@ void Application::touchUpdateEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->touchUpdateEvent(event); } } @@ -2565,7 +2564,7 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->touchBeginEvent(event); } @@ -2582,7 +2581,7 @@ void Application::touchEndEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->touchEndEvent(event); } @@ -2598,7 +2597,7 @@ void Application::wheelEvent(QWheelEvent* event) const { return; } - if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { _keyboardMouseDevice->wheelEvent(event); } } @@ -2730,7 +2729,7 @@ void Application::idle() { getActiveDisplayPlugin()->idle(); auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); + QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); QAction* action = Menu::getInstance()->getActionForOption(name); if (action && action->isChecked()) { inputPlugin->idle(); @@ -3373,22 +3372,18 @@ void Application::update(float deltaTime) { }; InputPluginPointer keyboardMousePlugin; - bool jointsCaptured = false; for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->getName() == KeyboardMouseDevice::NAME) { keyboardMousePlugin = inputPlugin; } else if (inputPlugin->isActive()) { - inputPlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured); - if (inputPlugin->isJointController()) { - jointsCaptured = true; - } + inputPlugin->pluginUpdate(deltaTime, calibrationData); } } userInputMapper->update(deltaTime); if (keyboardMousePlugin && keyboardMousePlugin->isActive()) { - keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData, jointsCaptured); + keyboardMousePlugin->pluginUpdate(deltaTime, calibrationData); } _controllerScriptingInterface->updateInputControllers(); @@ -5150,21 +5145,23 @@ void Application::updateDisplayMode() { Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } -static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) { +static void addInputPluginToMenu(InputPluginPointer inputPlugin) { auto menu = Menu::getInstance(); - QString name = inputPlugin->getName(); + QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); Q_ASSERT(!menu->menuItemExists(MenuOption::InputMenu, name)); static QActionGroup* inputPluginGroup = nullptr; if (!inputPluginGroup) { inputPluginGroup = new QActionGroup(menu); + inputPluginGroup->setExclusive(false); } + auto parent = menu->getMenu(MenuOption::InputMenu); auto action = menu->addCheckableActionToQMenuAndActionHash(parent, - name, 0, active, qApp, + name, 0, true, qApp, SLOT(updateInputModes())); + inputPluginGroup->addAction(action); - inputPluginGroup->setExclusive(false); Q_ASSERT(menu->menuItemExists(MenuOption::InputMenu, name)); } @@ -5174,10 +5171,8 @@ void Application::updateInputModes() { auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); static std::once_flag once; std::call_once(once, [&] { - bool first = true; foreach(auto inputPlugin, inputPlugins) { - addInputPluginToMenu(inputPlugin, first); - first = false; + addInputPluginToMenu(inputPlugin); } }); auto offscreenUi = DependencyManager::get(); @@ -5185,7 +5180,7 @@ void Application::updateInputModes() { InputPluginList newInputPlugins; InputPluginList removedInputPlugins; foreach(auto inputPlugin, inputPlugins) { - QString name = inputPlugin->getName(); + QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); QAction* action = menu->getActionForOption(name); auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8b69bb8022..f946553b55 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,7 +34,6 @@ #include "avatar/AvatarManager.h" #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" -#include "input-plugins/SpacemouseManager.h" #include "MainWindow.h" #include "render/DrawStatus.h" #include "scripting/MenuScriptingInterface.h" @@ -327,12 +326,6 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - // Settings > Input Devices - MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); - QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); - inputModeGroup->setExclusive(false); - - // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); @@ -410,6 +403,12 @@ Menu::Menu() { // Developer > Avatar >>> MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); + // Settings > Input Devices + MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); + QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); + inputModeGroup->setExclusive(false); + + // Developer > Avatar > Face Tracking MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 484be9f346..7230ac2157 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -114,7 +114,7 @@ namespace MenuOption { const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; - const QString InputMenu = "Avatar>Input Devices"; + const QString InputMenu = "Developer>Avatar>Input Devices"; const QString ActionMotorControl = "Enable Default Motor Control"; const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index dba856cbaa..79ff4ecbf8 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -121,16 +121,8 @@ namespace controller { return availableInputs; } - void ActionsDevice::update(float deltaTime, const InputCalibrationData& inpuCalibrationData, bool jointsCaptured) { - } - - void ActionsDevice::focusOutEvent() { - } - ActionsDevice::ActionsDevice() : InputDevice("Actions") { _deviceID = UserInputMapper::ACTIONS_DEVICE; } - ActionsDevice::~ActionsDevice() {} - } diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index efdc45cb3d..724d17d951 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -109,13 +109,11 @@ class ActionsDevice : public QObject, public InputDevice { Q_PROPERTY(QString name READ getName) public: - virtual EndpointPointer createEndpoint(const Input& input) const override; - virtual Input::NamedVector getAvailableInputs() const override; - virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; - virtual void focusOutEvent() override; - ActionsDevice(); - virtual ~ActionsDevice(); + + EndpointPointer createEndpoint(const Input& input) const override; + Input::NamedVector getAvailableInputs() const override; + }; } diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 93247965bc..afb1f7d1f7 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -57,9 +57,9 @@ public: // Update call MUST be called once per simulation loop // It takes care of updating the action states and deltas - virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) = 0; + virtual void update(float deltaTime, const InputCalibrationData& inputCalibrationData) {}; - virtual void focusOutEvent() = 0; + virtual void focusOutEvent() {}; int getDeviceID() { return _deviceID; } void setDeviceID(int deviceID) { _deviceID = deviceID; } diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index 5996cad5df..a9d317d407 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -22,12 +22,6 @@ StandardController::StandardController() : InputDevice("Standard") { _deviceID = UserInputMapper::STANDARD_DEVICE; } -StandardController::~StandardController() { -} - -void StandardController::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { -} - void StandardController::focusOutEvent() { _axisStateMap.clear(); _buttonPressedMap.clear(); diff --git a/libraries/controllers/src/controllers/StandardController.h b/libraries/controllers/src/controllers/StandardController.h index fee608f822..58c3ecaa30 100644 --- a/libraries/controllers/src/controllers/StandardController.h +++ b/libraries/controllers/src/controllers/StandardController.h @@ -28,11 +28,9 @@ public: virtual EndpointPointer createEndpoint(const Input& input) const override; virtual Input::NamedVector getAvailableInputs() const override; virtual QStringList getDefaultMappingConfigs() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; virtual void focusOutEvent() override; StandardController(); - virtual ~StandardController(); }; } diff --git a/libraries/controllers/src/controllers/StateController.h b/libraries/controllers/src/controllers/StateController.h index 57414c3ae8..c18c9df27c 100644 --- a/libraries/controllers/src/controllers/StateController.h +++ b/libraries/controllers/src/controllers/StateController.h @@ -35,10 +35,7 @@ public: const QString& getName() const { return _name; } // Device functions - virtual Input::NamedVector getAvailableInputs() const override; - - void update(float deltaTime, const InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {} - void focusOutEvent() override {} + Input::NamedVector getAvailableInputs() const override; void setInputVariant(const QString& name, ReadLambda lambda); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 4c0240eaf7..915ec1db87 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -20,11 +20,10 @@ const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; -void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { - +void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); // For touch event, we need to check that the last event is not too long ago @@ -40,7 +39,7 @@ void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputC } } -void KeyboardMouseDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void KeyboardMouseDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _axisStateMap.clear(); } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 67d9ccb1b2..a66cc7060b 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -65,12 +65,11 @@ public: }; // Plugin functions - virtual bool isSupported() const override { return true; } - virtual bool isJointController() const override { return false; } - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override { return true; } + const QString& getName() const override { return NAME; } - virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -97,7 +96,7 @@ protected: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; // Let's make it easy for Qt because we assume we love Qt forever diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index b45fa862c1..02ae5f58d5 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -18,10 +18,8 @@ namespace controller { class InputPlugin : public Plugin { public: - virtual bool isJointController() const = 0; - virtual void pluginFocusOutEvent() = 0; - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) = 0; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) = 0; }; diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt index 1cab2359a9..a9ed8cca6e 100644 --- a/plugins/hifiNeuron/CMakeLists.txt +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -6,8 +6,11 @@ # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # -set(TARGET_NAME hifiNeuron) -setup_hifi_plugin(Script Qml Widgets) -link_hifi_libraries(shared controllers ui plugins input-plugins) -target_neuron() +if (APPLE OR WIN32) + + set(TARGET_NAME hifiNeuron) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared controllers ui plugins input-plugins) + target_neuron() +endif() diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index 6e2f744173..41c0eb0d4e 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -25,9 +25,7 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") #define __OS_XUN__ 1 #define BOOL int -#ifdef HAVE_NEURON #include -#endif const QString NeuronPlugin::NAME = "Neuron"; const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; @@ -166,69 +164,6 @@ static controller::StandardPoseChannel neuronJointIndexToPoseIndexMap[NeuronJoin static glm::vec3 rightHandThumb1DefaultAbsTranslation(-2.155500650405884, -0.7610001564025879, 2.685631036758423); static glm::vec3 leftHandThumb1DefaultAbsTranslation(2.1555817127227783, -0.7603635787963867, 2.6856393814086914); -// default translations (cm) -static glm::vec3 neuronJointTranslations[NeuronJointIndex::Size] = { - {131.901, 95.6602, -27.9815}, - {-9.55907, -1.58772, 0.0760284}, - {0.0144232, -41.4683, -0.105322}, - {1.59348, -41.5875, -0.557237}, - {9.72077, -1.68926, -0.280643}, - {0.0886684, -43.1586, -0.0111596}, - {-2.98473, -44.0517, 0.0694456}, - {0.110967, 16.3959, 0.140463}, - {0.0500451, 10.0238, 0.0731921}, - {0.061568, 10.4352, 0.0583075}, - {0.0500606, 10.0217, 0.0711083}, - {0.0317731, 10.7176, 0.0779325}, - {-0.0204253, 9.71067, 0.131734}, - {-3.24245, 7.13584, 0.185638}, - {-13.0885, -0.0877601, 0.176065}, - {-27.2674, 0.0688724, 0.0272146}, - {-26.7673, 0.0301916, 0.0102847}, - {-2.56017, 0.195537, 3.20968}, - {-3.78796, 0, 0}, - {-2.63141, 0, 0}, - {-3.31579, 0.522947, 2.03495}, - {-5.36589, -0.0939789, 1.02771}, - {-3.72278, 0, 0}, - {-2.11074, 0, 0}, - {-3.47874, 0.532042, 0.778358}, - {-5.32194, -0.0864, 0.322863}, - {-4.06232, 0, 0}, - {-2.54653, 0, 0}, - {-3.46131, 0.553263, -0.132632}, - {-4.76716, -0.0227368, -0.492632}, - {-3.54073, 0, 0}, - {-2.45634, 0, 0}, - {-3.25137, 0.482779, -1.23613}, - {-4.25937, -0.0227368, -1.12168}, - {-2.83528, 0, 0}, - {-1.79166, 0, 0}, - {3.25624, 7.13148, -0.131575}, - {13.149, -0.052598, -0.125076}, - {27.2903, 0.00282644, -0.0181535}, - {26.6602, 0.000969969, -0.0487599}, - {2.56017, 0.195537, 3.20968}, - {3.78796, 0, 0}, - {2.63141, 0, 0}, - {3.31579, 0.522947, 2.03495}, - {5.36589, -0.0939789, 1.02771}, - {3.72278, 0, 0}, - {2.11074, 0, 0}, - {3.47874, 0.532042, 0.778358}, - {5.32194, -0.0864, 0.322863}, - {4.06232, 0, 0}, - {2.54653, 0, 0}, - {3.46131, 0.553263, -0.132632}, - {4.76716, -0.0227368, -0.492632}, - {3.54073, 0, 0}, - {2.45634, 0, 0}, - {3.25137, 0.482779, -1.23613}, - {4.25937, -0.0227368, -1.12168}, - {2.83528, 0, 0}, - {1.79166, 0, 0} -}; - static controller::StandardPoseChannel neuronJointIndexToPoseIndex(NeuronJointIndex i) { assert(i >= 0 && i < NeuronJointIndex::Size); if (i >= 0 && i < NeuronJointIndex::Size) { @@ -307,16 +242,13 @@ static const char* controllerJointName(controller::StandardPoseChannel i) { // convert between YXZ neuron euler angles in degrees to quaternion // this is the default setting in the Axis Neuron server. -static quat eulerToQuat(vec3 euler) { +static quat eulerToQuat(const vec3& e) { // euler.x and euler.y are swaped, WTF. - glm::vec3 e = glm::vec3(euler.y, euler.x, euler.z) * RADIANS_PER_DEGREE; - return (glm::angleAxis(e.y, Vectors::UNIT_Y) * - glm::angleAxis(e.x, Vectors::UNIT_X) * - glm::angleAxis(e.z, Vectors::UNIT_Z)); + return (glm::angleAxis(e.x * RADIANS_PER_DEGREE, Vectors::UNIT_Y) * + glm::angleAxis(e.y * RADIANS_PER_DEGREE, Vectors::UNIT_X) * + glm::angleAxis(e.z * RADIANS_PER_DEGREE, Vectors::UNIT_Z)); } -#ifdef HAVE_NEURON - // // neuronDataReader SDK callback functions // @@ -355,21 +287,6 @@ void FrameDataReceivedCallback(void* context, SOCKET_REF sender, BvhDataHeaderEx // copy the data memcpy(&(neuronPlugin->_joints[0]), data, sizeof(NeuronPlugin::NeuronJoint) * NUM_JOINTS); - - } else { - qCWarning(inputplugins) << "NeuronPlugin: unsuported binary format, please enable displacements"; - - // enter mutex - std::lock_guard guard(neuronPlugin->_jointsMutex); - - if (neuronPlugin->_joints.size() != NeuronJointIndex::Size) { - neuronPlugin->_joints.resize(NeuronJointIndex::Size, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); - } - - for (int i = 0; i < NeuronJointIndex::Size; i++) { - neuronPlugin->_joints[i].euler = glm::vec3(); - neuronPlugin->_joints[i].pos = neuronJointTranslations[i]; - } } } else { static bool ONCE = false; @@ -435,26 +352,19 @@ static void SocketStatusChangedCallback(void* context, SOCKET_REF sender, Socket qCDebug(inputplugins) << "NeuronPlugin: socket status = " << message; } -#endif // #ifdef HAVE_NEURON - // // NeuronPlugin // bool NeuronPlugin::isSupported() const { -#ifdef HAVE_NEURON // Because it's a client/server network architecture, we can't tell // if the neuron is actually connected until we connect to the server. return true; -#else - return false; -#endif } bool NeuronPlugin::activate() { InputPlugin::activate(); -#ifdef HAVE_NEURON // register with userInputMapper auto userInputMapper = DependencyManager::get(); userInputMapper->registerDevice(_inputDevice); @@ -480,13 +390,9 @@ bool NeuronPlugin::activate() { BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); return true; } -#else - return false; -#endif } void NeuronPlugin::deactivate() { -#ifdef HAVE_NEURON // unregister from userInputMapper if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) { auto userInputMapper = DependencyManager::get(); @@ -499,10 +405,9 @@ void NeuronPlugin::deactivate() { } InputPlugin::deactivate(); -#endif } -void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void NeuronPlugin::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { std::vector joints; { // lock and copy @@ -548,16 +453,23 @@ QString NeuronPlugin::InputDevice::getDefaultMappingConfig() const { void NeuronPlugin::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const std::vector& joints, const std::vector& prevJoints) { for (size_t i = 0; i < joints.size(); i++) { + int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i); glm::vec3 linearVel, angularVel; - glm::vec3 pos = joints[i].pos; - glm::quat rot = eulerToQuat(joints[i].euler); + const glm::vec3& pos = joints[i].pos; + const glm::vec3& rotEuler = joints[i].euler; + + if ((Vectors::ZERO == pos && Vectors::ZERO == rotEuler)) { + _poseStateMap[poseIndex] = controller::Pose(); + continue; + } + + glm::quat rot = eulerToQuat(rotEuler); if (i < prevJoints.size()) { linearVel = (pos - (prevJoints[i].pos * METERS_PER_CENTIMETER)) / deltaTime; // m/s // quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation. glm::quat d = glm::log(rot * glm::inverse(eulerToQuat(prevJoints[i].euler))); angularVel = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s } - int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i); _poseStateMap[poseIndex] = controller::Pose(pos, rot, linearVel, angularVel); } diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h index 99859dcacb..9ddd79c013 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.h +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -27,7 +27,6 @@ public: // Plugin functions virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } virtual const QString& getName() const override { return NAME; } const QString& getID() const override { return NEURON_ID_STRING; } @@ -35,7 +34,7 @@ public: virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void saveSettings() const override; virtual void loadSettings() override; @@ -56,7 +55,7 @@ protected: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override {}; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override {}; virtual void focusOutEvent() override {}; void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const std::vector& joints, const std::vector& prevJoints); diff --git a/plugins/hifiSdl2/src/Joystick.cpp b/plugins/hifiSdl2/src/Joystick.cpp index 9d195fd606..a109656489 100644 --- a/plugins/hifiSdl2/src/Joystick.cpp +++ b/plugins/hifiSdl2/src/Joystick.cpp @@ -15,7 +15,6 @@ const float CONTROLLER_THRESHOLD = 0.3f; -#ifdef HAVE_SDL2 const float MAX_AXIS = 32768.0f; Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController) : @@ -27,19 +26,15 @@ Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameControl } -#endif - Joystick::~Joystick() { closeJoystick(); } void Joystick::closeJoystick() { -#ifdef HAVE_SDL2 SDL_GameControllerClose(_sdlGameController); -#endif } -void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { for (auto axisState : _axisStateMap) { if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { _axisStateMap[axisState.first] = 0.0f; @@ -52,8 +47,6 @@ void Joystick::focusOutEvent() { _buttonPressedMap.clear(); }; -#ifdef HAVE_SDL2 - void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; @@ -69,8 +62,6 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { } } -#endif - controller::Input::NamedVector Joystick::getAvailableInputs() const { using namespace controller; static const Input::NamedVector availableInputs{ diff --git a/plugins/hifiSdl2/src/Joystick.h b/plugins/hifiSdl2/src/Joystick.h index 08bf27b960..e2eaeaef8b 100644 --- a/plugins/hifiSdl2/src/Joystick.h +++ b/plugins/hifiSdl2/src/Joystick.h @@ -15,10 +15,8 @@ #include #include -#ifdef HAVE_SDL2 #include #undef main -#endif #include #include @@ -26,10 +24,7 @@ class Joystick : public QObject, public controller::InputDevice { Q_OBJECT Q_PROPERTY(QString name READ getName) - -#ifdef HAVE_SDL2 Q_PROPERTY(int instanceId READ getInstanceId) -#endif public: using Pointer = std::shared_ptr; @@ -39,33 +34,25 @@ public: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; Joystick() : InputDevice("GamePad") {} ~Joystick(); -#ifdef HAVE_SDL2 Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController); -#endif void closeJoystick(); -#ifdef HAVE_SDL2 void handleAxisEvent(const SDL_ControllerAxisEvent& event); void handleButtonEvent(const SDL_ControllerButtonEvent& event); -#endif -#ifdef HAVE_SDL2 int getInstanceId() const { return _instanceId; } -#endif private: -#ifdef HAVE_SDL2 SDL_GameController* _sdlGameController; SDL_Joystick* _sdlJoystick; SDL_JoystickID _instanceId; -#endif }; #endif // hifi_Joystick_h diff --git a/plugins/hifiSdl2/src/SDL2Manager.cpp b/plugins/hifiSdl2/src/SDL2Manager.cpp index 7091b20d21..0bdb68f830 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.cpp +++ b/plugins/hifiSdl2/src/SDL2Manager.cpp @@ -16,7 +16,6 @@ #include "SDL2Manager.h" -#ifdef HAVE_SDL2 static_assert( (int)controller::A == (int)SDL_CONTROLLER_BUTTON_A && (int)controller::B == (int)SDL_CONTROLLER_BUTTON_B && @@ -40,28 +39,16 @@ static_assert( (int)controller::LT == (int)SDL_CONTROLLER_AXIS_TRIGGERLEFT && (int)controller::RT == (int)SDL_CONTROLLER_AXIS_TRIGGERRIGHT, "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h"); -#endif const QString SDL2Manager::NAME = "SDL2"; -#ifdef HAVE_SDL2 SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); return SDL_JoystickInstanceID(joystick); } -#endif - -SDL2Manager::SDL2Manager() : -#ifdef HAVE_SDL2 -_openJoysticks(), -#endif -_isInitialized(false) -{ -} void SDL2Manager::init() { -#ifdef HAVE_SDL2 bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0); if (initSuccess) { @@ -88,66 +75,50 @@ void SDL2Manager::init() { else { qDebug() << "Error initializing SDL2 Manager"; } -#endif } void SDL2Manager::deinit() { -#ifdef HAVE_SDL2 _openJoysticks.clear(); SDL_Quit(); -#endif } bool SDL2Manager::activate() { InputPlugin::activate(); -#ifdef HAVE_SDL2 auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->registerDevice(joystick); emit joystickAdded(joystick.get()); } return true; -#else - return false; -#endif } void SDL2Manager::deactivate() { -#ifdef HAVE_SDL2 auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { userInputMapper->removeDevice(joystick->getDeviceID()); emit joystickRemoved(joystick.get()); } -#endif InputPlugin::deactivate(); } bool SDL2Manager::isSupported() const { -#ifdef HAVE_SDL2 return true; -#else - return false; -#endif } void SDL2Manager::pluginFocusOutEvent() { -#ifdef HAVE_SDL2 for (auto joystick : _openJoysticks) { joystick->focusOutEvent(); } -#endif } -void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { -#ifdef HAVE_SDL2 +void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { if (_isInitialized) { auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { - joystick->update(deltaTime, inputCalibrationData, jointsCaptured); + joystick->update(deltaTime, inputCalibrationData); } PerformanceTimer perfTimer("SDL2Manager::update"); @@ -197,5 +168,4 @@ void SDL2Manager::pluginUpdate(float deltaTime, const controller::InputCalibrati } } } -#endif } diff --git a/plugins/hifiSdl2/src/SDL2Manager.h b/plugins/hifiSdl2/src/SDL2Manager.h index f69e23ee98..a597a87aee 100644 --- a/plugins/hifiSdl2/src/SDL2Manager.h +++ b/plugins/hifiSdl2/src/SDL2Manager.h @@ -12,9 +12,7 @@ #ifndef hifi__SDL2Manager_h #define hifi__SDL2Manager_h -#ifdef HAVE_SDL2 #include -#endif #include #include @@ -24,30 +22,26 @@ class SDL2Manager : public InputPlugin { Q_OBJECT public: - SDL2Manager(); - // Plugin functions - virtual bool isSupported() const override; - virtual bool isJointController() const override { return false; } - virtual const QString& getName() const override { return NAME; } + bool isSupported() const override; + const QString& getName() const override { return NAME; } - virtual void init() override; - virtual void deinit() override; + void init() override; + void deinit() override; /// Called when a plugin is being activated for use. May be called multiple times. - virtual bool activate() override; + bool activate() override; /// Called when a plugin is no longer being used. May be called multiple times. - virtual void deactivate() override; + void deactivate() override; - virtual void pluginFocusOutEvent() override; - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; signals: void joystickAdded(Joystick* joystick); void joystickRemoved(Joystick* joystick); private: -#ifdef HAVE_SDL2 SDL_JoystickID getInstanceId(SDL_GameController* controller); int axisInvalid() const { return SDL_CONTROLLER_AXIS_INVALID; } @@ -81,8 +75,7 @@ private: int buttonRelease() const { return SDL_RELEASED; } QMap _openJoysticks; -#endif - bool _isInitialized; + bool _isInitialized { false } ; static const QString NAME; }; diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index fdb7bb17fe..9ea79a8b96 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -134,12 +134,12 @@ void SixenseManager::setSixenseFilter(bool filter) { #endif } -void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); if (_inputDevice->_requestReset) { @@ -148,7 +148,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr } } -void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { BAIL_IF_NOT_LOADED #ifdef HAVE_SIXENSE _buttonPressedMap.clear(); @@ -208,14 +208,10 @@ void SixenseManager::InputDevice::update(float deltaTime, const controller::Inpu _axisStateMap[left ? LY : RY] = data->joystick_y; _axisStateMap[left ? LT : RT] = data->trigger; - if (!jointsCaptured) { - // Rotation of Palm - glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); - handlePoseEvent(deltaTime, inputCalibrationData, position, rotation, left); - rawPoses[i] = controller::Pose(position, rotation, Vectors::ZERO, Vectors::ZERO); - } else { - _poseStateMap.clear(); - } + // Rotation of Palm + glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); + handlePoseEvent(deltaTime, inputCalibrationData, position, rotation, left); + rawPoses[i] = controller::Pose(position, rotation, Vectors::ZERO, Vectors::ZERO); } else { auto hand = left ? controller::StandardPoseChannel::LEFT_HAND : controller::StandardPoseChannel::RIGHT_HAND; _poseStateMap[hand] = controller::Pose(); diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index a46614b17a..6aec9fd4ad 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -28,7 +28,6 @@ class SixenseManager : public InputPlugin { public: // Plugin functions virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } virtual const QString& getName() const override { return NAME; } virtual const QString& getID() const override { return HYDRA_ID_STRING; } @@ -36,7 +35,7 @@ public: virtual void deactivate() override; virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void saveSettings() const override; virtual void loadSettings() override; @@ -61,7 +60,7 @@ private: // Device functions virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; void handleButtonEvent(unsigned int buttons, bool left); diff --git a/plugins/hifiSpacemouse/CMakeLists.txt b/plugins/hifiSpacemouse/CMakeLists.txt new file mode 100644 index 0000000000..bcfb309a69 --- /dev/null +++ b/plugins/hifiSpacemouse/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Created by Bradley Austin Davis on 2016/05/11 +# Copyright 2013-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 +# + +if(WIN32) + set(TARGET_NAME hifiSpacemouse) + find_package(3DCONNEXIONCLIENT) + if (3DCONNEXIONCLIENT_FOUND) + setup_hifi_plugin(Script Qml Widgets) + link_hifi_libraries(shared networking controllers ui plugins input-plugins) + target_include_directories(${TARGET_NAME} PUBLIC ${3DCONNEXIONCLIENT_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${3DCONNEXIONCLIENT_LIBRARIES}) + endif() +endif() diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp similarity index 95% rename from libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp rename to plugins/hifiSpacemouse/src/SpacemouseManager.cpp index d946990319..8b584df366 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.cpp +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp @@ -17,13 +17,65 @@ #include #include -#include "../../../interface/src/Menu.h" +const QString SpacemouseManager::NAME { "Spacemouse" }; const float MAX_AXIS = 75.0f; // max forward = 2x speed +#define LOGITECH_VENDOR_ID 0x46d -static std::shared_ptr instance = std::make_shared(); +#ifndef RIDEV_DEVNOTIFY +#define RIDEV_DEVNOTIFY 0x00002000 +#endif -SpacemouseDevice::SpacemouseDevice() : InputDevice("Spacemouse") +const int TRACE_RIDI_DEVICENAME = 0; +const int TRACE_RIDI_DEVICEINFO = 0; + +#ifdef _WIN64 +typedef unsigned __int64 QWORD; +#endif + +bool Is3dmouseAttached(); + +std::shared_ptr instance; + +bool SpacemouseManager::isSupported() const { + return Is3dmouseAttached(); +} + +bool SpacemouseManager::activate() { + fLast3dmouseInputTime = 0; + + InitializeRawInput(GetActiveWindow()); + + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + + if (!instance) { + instance = std::make_shared(); + } + + if (instance->getDeviceID() == controller::Input::INVALID_DEVICE) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(instance); + UserActivityLogger::getInstance().connectedDevice("controller", NAME); + } + return true; +} + +void SpacemouseManager::deactivate() { + QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); + int deviceid = instance->getDeviceID(); + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(deviceid); +} + +void SpacemouseManager::pluginFocusOutEvent() { + instance->focusOutEvent(); +} + +void SpacemouseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + +} + +SpacemouseDevice::SpacemouseDevice() : InputDevice(SpacemouseManager::NAME) { } @@ -111,71 +163,80 @@ controller::Input::NamedPair SpacemouseDevice::makePair(SpacemouseDevice::Positi return controller::Input::NamedPair(makeInput(axis), name); } -void SpacemouseDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void SpacemouseDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { // the update is done in the SpacemouseManager class. // for windows in the nativeEventFilter the inputmapper is connected or registed or removed when an 3Dconnnexion device is attached or detached // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached } -void SpacemouseManager::ManagerFocusOutEvent() { - instance->focusOutEvent(); -} - -void SpacemouseManager::init() { -} - -#ifdef HAVE_3DCONNEXIONCLIENT - #ifdef Q_OS_WIN #include -void SpacemouseManager::toggleSpacemouse(bool shouldEnable) { - if (shouldEnable) { - init(); - } - if (!shouldEnable && instance->getDeviceID() != controller::Input::INVALID_DEVICE) { - destroy(); - } +bool SpacemouseManager::nativeEventFilter(const QByteArray& eventType, void* message, long* result) { + MSG* msg = static_cast< MSG * >(message); + return RawInputEventFilter(message, result); } -void SpacemouseManager::init() { - if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { - fLast3dmouseInputTime = 0; - InitializeRawInput(GetActiveWindow()); +//Get an initialized array of PRAWINPUTDEVICE for the 3D devices +//pNumDevices returns the number of devices to register. Currently this is always 1. +static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { + // Array of raw input devices to register + static RAWINPUTDEVICE sRawInputDevices[] = { + { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller + }; - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + if (pNumDevices) { + *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); + } - if (instance->getDeviceID() != controller::Input::INVALID_DEVICE) { - auto userInputMapper = DependencyManager::get(); - userInputMapper->registerDevice(instance); - UserActivityLogger::getInstance().connectedDevice("controller", "Spacemouse"); + return sRawInputDevices; +} + + +//Detect the 3D mouse +bool Is3dmouseAttached() { + unsigned int numDevicesOfInterest = 0; + PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); + + unsigned int nDevices = 0; + + if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { + return false; + } + + if (nDevices == 0) { + return false; + } + + std::vector rawInputDeviceList(nDevices); + if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { + return false; + } + + for (unsigned int i = 0; i < nDevices; ++i) { + RID_DEVICE_INFO rdi = { sizeof(rdi) }; + unsigned int cbSize = sizeof(rdi); + + if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { + //skip non HID and non logitec (3DConnexion) devices + if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { + continue; + } + + //check if devices matches Multi-axis Controller + for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { + if (devicesToRegister[j].usUsage == rdi.hid.usUsage + && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { + return true; + } + } } - } + return false; } -void SpacemouseManager::destroy() { - QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); - int deviceid = instance->getDeviceID(); - auto userInputMapper = DependencyManager::get(); - userInputMapper->removeDevice(deviceid); -} - -#define LOGITECH_VENDOR_ID 0x46d - -#ifndef RIDEV_DEVNOTIFY -#define RIDEV_DEVNOTIFY 0x00002000 -#endif - -const int TRACE_RIDI_DEVICENAME = 0; -const int TRACE_RIDI_DEVICEINFO = 0; - -#ifdef _WIN64 -typedef unsigned __int64 QWORD; -#endif - // object angular velocity per mouse tick 0.008 milliradians per second per count static const double k3dmouseAngularVelocity = 8.0e-6; // radians per second per count @@ -290,20 +351,9 @@ bool SpacemouseManager::RawInputEventFilter(void* msg, long* result) { return false; } -// Access the mouse parameters structure -I3dMouseParam& SpacemouseManager::MouseParams() { - return f3dMouseParams; -} - -// Access the mouse parameters structure -const I3dMouseParam& SpacemouseManager::MouseParams() const { - return f3dMouseParams; -} - //Called with the processed motion data when a 3D mouse event is received void SpacemouseManager::Move3d(HANDLE device, std::vector& motionData) { Q_UNUSED(device); - instance->cc_position = { motionData[0] * 1000, motionData[1] * 1000, motionData[2] * 1000 }; instance->cc_rotation = { motionData[3] * 1500, motionData[4] * 1500, motionData[5] * 1500 }; instance->handleAxisEvent(); @@ -321,62 +371,6 @@ void SpacemouseManager::On3dmouseKeyUp(HANDLE device, int virtualKeyCode) { instance->setButton(0); } -//Get an initialized array of PRAWINPUTDEVICE for the 3D devices -//pNumDevices returns the number of devices to register. Currently this is always 1. -static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { - // Array of raw input devices to register - static RAWINPUTDEVICE sRawInputDevices[] = { - { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller - }; - - if (pNumDevices) { - *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); - } - - return sRawInputDevices; -} - -//Detect the 3D mouse -bool SpacemouseManager::Is3dmouseAttached() { - unsigned int numDevicesOfInterest = 0; - PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); - - unsigned int nDevices = 0; - - if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { - return false; - } - - if (nDevices == 0) { - return false; - } - - std::vector rawInputDeviceList(nDevices); - if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { - return false; - } - - for (unsigned int i = 0; i < nDevices; ++i) { - RID_DEVICE_INFO rdi = { sizeof(rdi) }; - unsigned int cbSize = sizeof(rdi); - - if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { - //skip non HID and non logitec (3DConnexion) devices - if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { - continue; - } - - //check if devices matches Multi-axis Controller - for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { - if (devicesToRegister[j].usUsage == rdi.hid.usUsage - && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { - return true; - } - } - } - } - return false; -} // Initialize the window to recieve raw-input messages // This needs to be called initially so that Windows will send the messages from the 3D mouse to the window. @@ -942,5 +936,3 @@ void MessageHandler(unsigned int connection, unsigned int messageType, void *mes } #endif // __APPLE__ - -#endif diff --git a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h b/plugins/hifiSpacemouse/src/SpacemouseManager.h similarity index 77% rename from libraries/input-plugins/src/input-plugins/SpacemouseManager.h rename to plugins/hifiSpacemouse/src/SpacemouseManager.h index 82c4fa8fb6..a9933902e5 100644 --- a/libraries/input-plugins/src/input-plugins/SpacemouseManager.h +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.h @@ -17,22 +17,8 @@ #include #include -#include "InputPlugin.h" +#include -#ifndef HAVE_3DCONNEXIONCLIENT -class SpacemouseManager : public QObject { - Q_OBJECT -public: - void ManagerFocusOutEvent(); - void init(); - void destroy() {}; - bool Is3dmouseAttached() { return false; }; - public slots: - void toggleSpacemouse(bool shouldEnable) {}; -}; -#endif - -#ifdef HAVE_3DCONNEXIONCLIENT // the windows connexion rawinput #ifdef Q_OS_WIN @@ -85,42 +71,26 @@ private: Speed fSpeed; }; -class SpacemouseManager : public QObject, public QAbstractNativeEventFilter { +class SpacemouseManager : public InputPlugin, public QAbstractNativeEventFilter { Q_OBJECT public: - SpacemouseManager() {}; + bool isSupported() const override; + const QString& getName() const override { return NAME; } + const QString& getID() const override { return NAME; } - void init(); - void destroy(); - bool Is3dmouseAttached(); + bool activate() override; + void deactivate() override; - SpacemouseManager* client; + void pluginFocusOutEvent() override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; - void ManagerFocusOutEvent(); - - I3dMouseParam& MouseParams(); - const I3dMouseParam& MouseParams() const; - - virtual void Move3d(HANDLE device, std::vector& motionData); - virtual void On3dmouseKeyDown(HANDLE device, int virtualKeyCode); - virtual void On3dmouseKeyUp(HANDLE device, int virtualKeyCode); - - virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) Q_DECL_OVERRIDE - { - MSG* msg = static_cast< MSG * >(message); - return RawInputEventFilter(message, result); - } - - public slots: - void toggleSpacemouse(bool shouldEnable); - -signals: - void Move3d(std::vector& motionData); - void On3dmouseKeyDown(int virtualKeyCode); - void On3dmouseKeyUp(int virtualKeyCode); + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; private: + void Move3d(HANDLE device, std::vector& motionData); + void On3dmouseKeyDown(HANDLE device, int virtualKeyCode); + void On3dmouseKeyUp(HANDLE device, int virtualKeyCode); bool InitializeRawInput(HWND hwndTarget); bool RawInputEventFilter(void* msg, long* result); @@ -156,6 +126,9 @@ private: // use to calculate distance traveled since last event DWORD fLast3dmouseInputTime; + + static const QString NAME; + friend class SpacemouseDevice; }; // the osx connexion api @@ -176,8 +149,6 @@ public: #endif // __APPLE__ -#endif - // connnects to the userinputmapper class SpacemouseDevice : public QObject, public controller::InputDevice { @@ -214,7 +185,7 @@ public: virtual controller::Input::NamedVector getAvailableInputs() const override; virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; virtual void focusOutEvent() override; glm::vec3 cc_position; diff --git a/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp b/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp new file mode 100644 index 0000000000..c623f77d73 --- /dev/null +++ b/plugins/hifiSpacemouse/src/SpacemouseProvider.cpp @@ -0,0 +1,45 @@ +// +// Created by Bradley Austin Davis on 2015/10/25 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include +#include + +#include +#include + +#include "SpacemouseManager.h" + +class SpacemouseProvider : public QObject, public InputProvider +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json") + Q_INTERFACES(InputProvider) + +public: + SpacemouseProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~SpacemouseProvider() {} + + virtual InputPluginList getInputPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + InputPluginPointer plugin(new SpacemouseManager()); + if (plugin->isSupported()) { + _inputPlugins.push_back(plugin); + } + }); + return _inputPlugins; + } + +private: + InputPluginList _inputPlugins; +}; + +#include "SpacemouseProvider.moc" diff --git a/plugins/hifiSpacemouse/src/plugin.json b/plugins/hifiSpacemouse/src/plugin.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/plugins/hifiSpacemouse/src/plugin.json @@ -0,0 +1 @@ +{} diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 50ef6b09a1..09ab6ec159 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -76,12 +76,12 @@ void OculusControllerManager::deactivate() { } } -void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { PerformanceTimer perfTimer("OculusControllerManager::TouchDevice::update"); if (_touch) { if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Touch, &_inputState))) { - _touch->update(deltaTime, inputCalibrationData, jointsCaptured); + _touch->update(deltaTime, inputCalibrationData); } else { qCWarning(oculus) << "Unable to read Oculus touch input state"; } @@ -89,7 +89,7 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In if (_remote) { if (OVR_SUCCESS(ovr_GetInputState(_session, ovrControllerType_Remote, &_inputState))) { - _remote->update(deltaTime, inputCalibrationData, jointsCaptured); + _remote->update(deltaTime, inputCalibrationData); } else { qCWarning(oculus) << "Unable to read Oculus remote input state"; } @@ -158,7 +158,7 @@ QString OculusControllerManager::RemoteDevice::getDefaultMappingConfig() const { return MAPPING_JSON; } -void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); const auto& inputState = _parent._inputState; for (const auto& pair : BUTTON_MAP) { @@ -172,21 +172,19 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() { _buttonPressedMap.clear(); } -void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); - if (!jointsCaptured) { - int numTrackedControllers = 0; - static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; - auto tracking = ovr_GetTrackingState(_parent._session, 0, false); - ovr_for_each_hand([&](ovrHandType hand) { - ++numTrackedControllers; - if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { - handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); - } - }); - } + int numTrackedControllers = 0; + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + auto tracking = ovr_GetTrackingState(_parent._session, 0, false); + ovr_for_each_hand([&](ovrHandType hand) { + ++numTrackedControllers; + if (REQUIRED_HAND_STATUS == (tracking.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + handlePose(deltaTime, inputCalibrationData, hand, tracking.HandPoses[hand]); + } + }); using namespace controller; // Axes const auto& inputState = _parent._inputState; diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index 60969097f8..980e1286f8 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -24,14 +24,13 @@ class OculusControllerManager : public InputPlugin { public: // Plugin functions bool isSupported() const override; - bool isJointController() const override { return true; } const QString& getName() const override { return NAME; } bool activate() override; void deactivate() override; void pluginFocusOutEvent() override; - void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; private: class OculusInputDevice : public controller::InputDevice { @@ -49,7 +48,7 @@ private: controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; - void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; friend class OculusControllerManager; @@ -62,7 +61,7 @@ private: controller::Input::NamedVector getAvailableInputs() const override; QString getDefaultMappingConfig() const override; - void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void focusOutEvent() override; private: diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 38719fdca5..dba8fca208 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -9,7 +9,6 @@ #include -#include #include #include #include @@ -30,10 +29,6 @@ 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 -static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); -static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); - - static vr::IVRCompositor* _compositor{ nullptr }; vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; @@ -43,7 +38,7 @@ static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; bool OpenVrDisplayPlugin::isSupported() const { - return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); + return openVrSupported(); } bool OpenVrDisplayPlugin::internalActivate() { diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 8ddf028dd2..8536ffd5d9 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -44,6 +45,12 @@ bool isOculusPresent() { return result; } +bool openVrSupported() { + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); +} + vr::IVRSystem* acquireOpenVrSystem() { bool hmdPresent = vr::VR_IsHmdPresent(); if (hmdPresent) { diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 81896a2ce5..4b06ca0813 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -12,7 +12,8 @@ #include #include -bool isOculusPresent(); +bool openVrSupported(); + vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index fab383a955..5ca8f03ad7 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -50,11 +50,9 @@ static const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; static const QString RENDER_CONTROLLERS = "Render Hand Controllers"; const QString ViveControllerManager::NAME = "OpenVR"; -static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); -static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); bool ViveControllerManager::isSupported() const { - return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); + return openVrSupported(); } bool ViveControllerManager::activate() { @@ -214,12 +212,13 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& } -void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _inputDevice->update(deltaTime, inputCalibrationData); auto userInputMapper = DependencyManager::get(); // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { - _inputDevice->update(deltaTime, inputCalibrationData, jointsCaptured); + _inputDevice->update(deltaTime, inputCalibrationData); }); if (_inputDevice->_trackedControllers == 0 && _registeredWithInputMapper) { @@ -235,7 +234,7 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu } } -void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) { +void ViveControllerManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _poseStateMap.clear(); _buttonPressedMap.clear(); @@ -244,10 +243,8 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand); auto rightHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand); - if (!jointsCaptured) { - handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); - handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); - } + handleHandController(deltaTime, leftHandDeviceIndex, inputCalibrationData, true); + handleHandController(deltaTime, rightHandDeviceIndex, inputCalibrationData, false); int numTrackedControllers = 0; if (leftHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index d55d4e726c..672ad59cfe 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -31,17 +31,15 @@ namespace vr { class ViveControllerManager : public InputPlugin { Q_OBJECT public: - // Plugin functions - virtual bool isSupported() const override; - virtual bool isJointController() const override { return true; } + bool isSupported() const override; const QString& getName() const override { return NAME; } - virtual bool activate() override; - virtual void deactivate() override; + bool activate() override; + void deactivate() override; - virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } - virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; void updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges); @@ -53,10 +51,10 @@ private: InputDevice(vr::IVRSystem*& system) : controller::InputDevice("Vive"), _system(system) {} private: // Device functions - virtual controller::Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; - virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, bool jointsCaptured) override; - virtual void focusOutEvent() override; + controller::Input::NamedVector getAvailableInputs() const override; + QString getDefaultMappingConfig() const override; + void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + void focusOutEvent() override; void handleHandController(float deltaTime, uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData, bool isLeftHand); void handleButtonEvent(float deltaTime, uint32_t button, bool pressed, bool touched, bool isLeftHand); diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 3a5b4a4a4d..36ed566ea7 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -121,7 +121,7 @@ int main(int argc, char** argv) { }; foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - inputPlugin->pluginUpdate(delta, calibrationData, false); + inputPlugin->pluginUpdate(delta, calibrationData); } auto userInputMapper = DependencyManager::get(); @@ -144,7 +144,7 @@ int main(int argc, char** argv) { if (name == KeyboardMouseDevice::NAME) { userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); } - inputPlugin->pluginUpdate(0, calibrationData, false); + inputPlugin->pluginUpdate(0, calibrationData); } rootContext->setContextProperty("Controllers", new MyControllerScriptingInterface()); } From 73342b2758c1ae89188186a1b23b5e2b8b16664e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 May 2016 09:38:44 -0700 Subject: [PATCH 203/264] PR feedback --- plugins/hifiNeuron/src/NeuronPlugin.cpp | 2 +- plugins/hifiSpacemouse/src/SpacemouseManager.cpp | 6 ++++-- plugins/openvr/src/ViveControllerManager.cpp | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp index 41c0eb0d4e..0a4bc7f8d2 100644 --- a/plugins/hifiNeuron/src/NeuronPlugin.cpp +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -458,7 +458,7 @@ void NeuronPlugin::InputDevice::update(float deltaTime, const controller::InputC const glm::vec3& pos = joints[i].pos; const glm::vec3& rotEuler = joints[i].euler; - if ((Vectors::ZERO == pos && Vectors::ZERO == rotEuler)) { + if (Vectors::ZERO == pos && Vectors::ZERO == rotEuler) { _poseStateMap[poseIndex] = controller::Pose(); continue; } diff --git a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp index 8b584df366..3d9b93ff44 100644 --- a/plugins/hifiSpacemouse/src/SpacemouseManager.cpp +++ b/plugins/hifiSpacemouse/src/SpacemouseManager.cpp @@ -11,6 +11,10 @@ #include "SpacemouseManager.h" +#ifdef Q_OS_WIN +#include +#endif + #include #include @@ -171,8 +175,6 @@ void SpacemouseDevice::update(float deltaTime, const controller::InputCalibratio #ifdef Q_OS_WIN -#include - bool SpacemouseManager::nativeEventFilter(const QByteArray& eventType, void* message, long* result) { MSG* msg = static_cast< MSG * >(message); return RawInputEventFilter(message, result); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 5ca8f03ad7..12567b10d1 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -213,7 +213,6 @@ void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { - _inputDevice->update(deltaTime, inputCalibrationData); auto userInputMapper = DependencyManager::get(); // because update mutates the internal state we need to lock From d95d3ff3ac61c722a109fe50fe99859b31eee585 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 14:56:47 -0700 Subject: [PATCH 204/264] clean up debugging prints --- interface/src/avatar/Avatar.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index efaed5b83a..d12306a122 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -197,19 +197,16 @@ void Avatar::updateAvatarEntities() { QScriptEngine scriptEngine; entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); - qDebug() << "---------------"; for (auto entityID : avatarEntities.keys()) { // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties // and either add or update the entity. QByteArray jsonByteArray = avatarEntities.value(entityID); QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray); if (!jsonProperties.isObject()) { - qDebug() << "got bad avatarEntity json"; + qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex()); continue; } - qDebug() << jsonProperties.toJson(); - QVariant variantProperties = jsonProperties.toVariant(); QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); @@ -229,19 +226,14 @@ void Avatar::updateAvatarEntities() { EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - qDebug() << "avatar-entities existing entity, element =" << entity->getElement().get(); if (entityTree->updateEntity(entityID, properties)) { entity->updateLastEditedFromRemote(); - qDebug() << "avatar-entities after entityTree->updateEntity(), element =" << entity->getElement().get(); } else { - qDebug() << "AVATAR-ENTITIES -- updateEntity failed: " << properties.getType(); success = false; } } else { - qDebug() << "avatar-entities new entity"; entity = entityTree->addEntity(entityID, properties); if (!entity) { - qDebug() << "AVATAR-ENTITIES -- addEntity failed: " << properties.getType(); success = false; } } @@ -1194,7 +1186,7 @@ void Avatar::setParentID(const QUuid& parentID) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentID failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentID failed to reset avatar's location."; } } } @@ -1209,7 +1201,7 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) { if (success) { setTransform(beforeChangeTransform, success); if (!success) { - qDebug() << "Avatar::setParentJointIndex failed to reset avatar's location."; + qCDebug(interfaceapp) << "Avatar::setParentJointIndex failed to reset avatar's location."; } } } From 2e30f0916cc9a365b5a93171527499e22f5385cb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 20 May 2016 15:07:47 -0700 Subject: [PATCH 205/264] remove debug logging and add some comments --- libraries/physics/src/ShapeFactory.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index 8c1d44b273..71b919b7ee 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -70,14 +70,15 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector& poin // create hull approximation btShapeHull shapeHull(hull); shapeHull.buildHull(margin); + // we cannot copy Bullet shapes so we must create a new one... btConvexHullShape* newHull = new btConvexHullShape(); const btVector3* newPoints = shapeHull.getVertexPointer(); for (int i = 0; i < shapeHull.numVertices(); ++i) { newHull->addPoint(newPoints[i], false); } + // ...and delete the old one delete hull; hull = newHull; - qDebug() << "reduced hull with" << points.size() << "points down to" << hull->getNumPoints(); // TODO: remove after testing } hull->recalcLocalAabb(); From a24d63a39c3974b05839499524a05c07ca86d1b7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 15:45:00 -0700 Subject: [PATCH 206/264] make distance-grab work better when avatar is walking --- .../system/controllers/handControllerGrab.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ed4ac219c0..06549a38b5 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1112,8 +1112,8 @@ function MyController(hand) { Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); // transform it into world frame - var controllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + var controllerPositionVSAvatar = Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation); + var controllerPosition = Vec3.sum(MyAvatar.position, controllerPositionVSAvatar); var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); @@ -1161,7 +1161,7 @@ function MyController(hand) { this.turnOffVisualizations(); - this.previousControllerPosition = controllerPosition; + this.previousControllerPositionVSAvatar = controllerPositionVSAvatar; this.previousControllerRotation = controllerRotation; }; @@ -1179,8 +1179,8 @@ function MyController(hand) { Controller.Standard.RightHand : Controller.Standard.LeftHand); // transform it into world frame - var controllerPosition = Vec3.sum(MyAvatar.position, - Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation)); + var controllerPositionVSAvatar = Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation); + var controllerPosition = Vec3.sum(MyAvatar.position, controllerPositionVSAvatar); var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); @@ -1197,7 +1197,8 @@ function MyController(hand) { } // scale delta controller hand movement by radius. - var handMoved = Vec3.multiply(Vec3.subtract(controllerPosition, this.previousControllerPosition), radius); + var handMoved = Vec3.multiply(Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar), + radius); // double delta controller rotation var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, @@ -1218,7 +1219,7 @@ function MyController(hand) { var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); // Update radialVelocity - var lastVelocity = Vec3.subtract(controllerPosition, this.previousControllerPosition); + var lastVelocity = Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar); lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaTime); var newRadialVelocity = Vec3.dot(lastVelocity, Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition))); @@ -1266,7 +1267,9 @@ function MyController(hand) { var clampedVector; var targetPosition; if (constraintData.axisStart !== false) { - clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, constraintData.axisStart, constraintData.axisEnd); + clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, + constraintData.axisStart, + constraintData.axisEnd); targetPosition = clampedVector; } else { targetPosition = { @@ -1309,7 +1312,7 @@ function MyController(hand) { print("continueDistanceHolding -- updateAction failed"); } - this.previousControllerPosition = controllerPosition; + this.previousControllerPositionVSAvatar = controllerPositionVSAvatar; this.previousControllerRotation = controllerRotation; }; From 6568c0563de1c8b95f47be2b1a5dc0a7bdf7cc67 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 20 May 2016 16:43:55 -0700 Subject: [PATCH 207/264] Delay on entry/exit. --- interface/src/ui/OverlayConductor.cpp | 26 +++++++++++++++++++------- interface/src/ui/OverlayConductor.h | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 1ceceb741e..e9dc766e73 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -69,27 +69,39 @@ void OverlayConductor::update(float dt) { void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); float speed = glm::length(myAvatar->getVelocity()); - bool nowDriving = _driving; const float MIN_DRIVING = 0.2f; const float MAX_NOT_DRIVING = 0.01f; - if (speed > MIN_DRIVING) { + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; + int fixmeDiff; + bool nowDriving = _driving; // Assume current _driving mode unless... + if (speed > MIN_DRIVING) { // ... we're definitely moving... nowDriving = true; - } - else if (speed < MAX_NOT_DRIVING) { + } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. nowDriving = false; } + // Check that we're in this new mode for long enough to really trigger a transition. + if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. + _timeInPotentialMode = 0; + } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. + _timeInPotentialMode = usecTimestampNow(); + nowDriving = _driving; + } else if ((fixmeDiff = (usecTimestampNow() - _timeInPotentialMode)) < (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + nowDriving = _driving; // Haven't accumulated enough time in new mode, but keep timing. + } else { // a real transition + _timeInPotentialMode = 0; + } + // If we're really in a transition if (nowDriving != _driving) { if (nowDriving) { _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - } - else { // reset when coming out of driving + } else { // reset when coming out of driving _mode = FLAT; // Seems appropriate to let things reset, below, after the following. // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. qApp->getActiveDisplayPlugin()->resetSensors(); myAvatar->reset(true, false); } if (_wantsOverlays) { - qDebug() << "flipping" << !nowDriving; setEnabled(!nowDriving, false); } _driving = nowDriving; diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 02b2035b07..99f4b56584 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -32,6 +32,7 @@ private: Mode _mode { FLAT }; bool _enabled { false }; bool _driving { false }; + quint64 _timeInPotentialMode { 0 }; bool _wantsOverlays { true }; }; From 9a428947e4f9fa6c51e76d69d385dc2eec33b945 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 20 May 2016 16:50:17 -0700 Subject: [PATCH 208/264] Simplify code. --- interface/src/ui/OverlayConductor.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e9dc766e73..fa74989f4f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -73,7 +73,6 @@ void OverlayConductor::updateMode() { const float MAX_NOT_DRIVING = 0.01f; const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - int fixmeDiff; bool nowDriving = _driving; // Assume current _driving mode unless... if (speed > MIN_DRIVING) { // ... we're definitely moving... nowDriving = true; @@ -85,14 +84,8 @@ void OverlayConductor::updateMode() { _timeInPotentialMode = 0; } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. _timeInPotentialMode = usecTimestampNow(); - nowDriving = _driving; - } else if ((fixmeDiff = (usecTimestampNow() - _timeInPotentialMode)) < (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - nowDriving = _driving; // Haven't accumulated enough time in new mode, but keep timing. - } else { // a real transition - _timeInPotentialMode = 0; - } - // If we're really in a transition - if (nowDriving != _driving) { + } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + _timeInPotentialMode = 0; // a real transition if (nowDriving) { _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); } else { // reset when coming out of driving @@ -105,7 +98,7 @@ void OverlayConductor::updateMode() { setEnabled(!nowDriving, false); } _driving = nowDriving; - } + } // Else haven't accumulated enough time in new mode, but keep timing. Mode newMode; if (qApp->isHMDMode()) { From 5a27d656c52d6524940d38a84d55799259c56e4b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 21 May 2016 13:03:58 +1200 Subject: [PATCH 209/264] Potential OSX fix plus extra logging --- interface/resources/qml/dialogs/FileDialog.qml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 78d5943479..b00ee76b5e 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -130,17 +130,23 @@ ModalWindow { choices = [], i, length; + console.log("####### folder parts: " + JSON.stringify(folders)); + if (folders[folders.length - 1] === "") { folders.pop(); } - if (folders[0] !== "") { - choices.push(folders[0]); - } + choices.push(folders[0]); for (i = 1, length = folders.length; i < length; i++) { choices.push(choices[i - 1] + "/" + folders[i]); } + + if (folders[0] === "") { + // Special handling for OSX root dir. + choices[0] = "/"; + } + choices.reverse(); if (drives && drives.length > 1) { @@ -154,6 +160,9 @@ ModalWindow { onLastValidFolderChanged: { var folder = d.capitalizeDrive(lastValidFolder); + + console.log("####### lastValidFolder: " + folder); + calculatePathChoices(folder); } From 637735bbc3e6099ed6648d31812a027d866ffcfb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 20 May 2016 18:07:38 -0700 Subject: [PATCH 210/264] unmangle merge --- libraries/entities/src/EntityItemProperties.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 03dc4a0557..99285b4986 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -690,13 +690,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); -<<<<<<< HEAD - COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); - COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); -======= COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); ->>>>>>> e012630db23d8aba81fae0721f69322220b21be2 + + COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); + COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); _lastEdited = usecTimestampNow(); } @@ -1592,13 +1590,11 @@ void EntityItemProperties::markAllChanged() { _queryAACubeChanged = true; -<<<<<<< HEAD - _clientOnlyChanged = true; - _owningAvatarIDChanged = true; -======= _flyingAllowedChanged = true; _ghostingAllowedChanged = true; ->>>>>>> e012630db23d8aba81fae0721f69322220b21be2 + + _clientOnlyChanged = true; + _owningAvatarIDChanged = true; } // The minimum bounding box for the entity. From a294170c74271ed1cb4b40a6e809ed2067bb5ce9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 21 May 2016 15:50:06 +1200 Subject: [PATCH 211/264] Fix not being able to teleport to user when users window moved right --- scripts/system/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/users.js b/scripts/system/users.js index d935dd23ca..c010b7ea24 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -716,7 +716,7 @@ var usersWindow = (function () { if (clickedOverlay === windowPane) { - overlayX = event.x - WINDOW_MARGIN; + overlayX = event.x - windowPosition.x - WINDOW_MARGIN; overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight; numLinesBefore = Math.round(overlayY / windowLineHeight); From 40e862cf9e624b50f91f267c87148ceeb7fef505 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 21 May 2016 16:53:01 -0700 Subject: [PATCH 212/264] quiet gcc 5 warnings --- .../octree/OctreeInboundPacketProcessor.cpp | 6 ++--- interface/src/Camera.h | 4 ++++ .../AssetMappingsScriptingInterface.cpp | 2 +- libraries/animation/src/AnimationCache.cpp | 2 +- libraries/audio/src/SoundCache.cpp | 2 +- .../src/controllers/UserInputMapper.cpp | 8 +++---- libraries/entities/src/EntityTypes.h | 2 +- libraries/fbx/src/FBXReader.cpp | 8 +++---- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 4 ++-- libraries/gpu-gl/src/gpu/gl/GLBackend.cpp | 2 +- libraries/gpu/src/gpu/Texture.cpp | 2 +- libraries/networking/src/HifiSockAddr.cpp | 2 +- libraries/networking/src/Node.cpp | 6 ++--- libraries/networking/src/ReceivedMessage.cpp | 4 ++-- libraries/networking/src/udt/Packet.cpp | 2 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketList.cpp | 2 +- libraries/render-utils/src/Model.cpp | 6 ++--- libraries/script-engine/src/ScriptEngine.cpp | 2 +- libraries/shared/src/RegisteredMetaTypes.cpp | 22 +++++++++---------- libraries/shared/src/SimpleMovingAverage.h | 2 +- tests/render-utils/src/main.cpp | 4 ---- 22 files changed, 48 insertions(+), 48 deletions(-) diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index c5d010871c..32f3de2ff6 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -153,7 +153,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer qDebug() << " maxSize=" << maxSize; qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", - packetType, message->getRawMessage(), message->getSize(), editData, + (int)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition(), maxSize); } @@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", - packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); + (int)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); } // Make sure our Node and NodeList knows we've heard from this node. @@ -208,7 +208,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { - qDebug("unknown packet ignored... packetType=%hhu", packetType); + qDebug("unknown packet ignored... packetType=%hhu", (int)packetType); } } diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 017bd742a4..486b98c100 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -29,6 +29,10 @@ enum CameraMode }; Q_DECLARE_METATYPE(CameraMode); + +#if defined(__GNUC__) && !defined(__clang__) +__attribute__((unused)) +#endif static int cameraModeId = qRegisterMetaType(); class Camera : public QObject { diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 965b3a9e0c..f1198c9d5b 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -196,7 +196,7 @@ bool AssetMappingModel::isKnownFolder(QString path) const { return false; } -static int assetMappingModelMetatypeId = qRegisterMetaType("AssetMappingModel*"); +int assetMappingModelMetatypeId = qRegisterMetaType("AssetMappingModel*"); void AssetMappingModel::refresh() { qDebug() << "Refreshing asset mapping model"; diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 482c4211cb..7601fbc782 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -15,7 +15,7 @@ #include "AnimationCache.h" #include "AnimationLogging.h" -static int animationPointerMetaTypeId = qRegisterMetaType(); +int animationPointerMetaTypeId = qRegisterMetaType(); AnimationCache::AnimationCache(QObject* parent) : ResourceCache(parent) diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 96a2cee204..6b34c68959 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -14,7 +14,7 @@ #include "AudioLogging.h" #include "SoundCache.h" -static int soundPointerMetaTypeId = qRegisterMetaType(); +int soundPointerMetaTypeId = qRegisterMetaType(); SoundCache::SoundCache(QObject* parent) : ResourceCache(parent) diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index c0d3ff40c0..c1ee3ce36c 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -336,10 +336,10 @@ QVector UserInputMapper::getActionNames() const { return result; } -static int actionMetaTypeId = qRegisterMetaType(); -static int inputMetaTypeId = qRegisterMetaType(); -static int inputPairMetaTypeId = qRegisterMetaType(); -static int poseMetaTypeId = qRegisterMetaType("Pose"); +int actionMetaTypeId = qRegisterMetaType(); +int inputMetaTypeId = qRegisterMetaType(); +int inputPairMetaTypeId = qRegisterMetaType(); +int poseMetaTypeId = qRegisterMetaType("Pose"); QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input); void inputFromScriptValue(const QScriptValue& object, Input& input); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 3536327d18..fe37c9b5f7 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -69,7 +69,7 @@ private: /// named NameEntityItem and must of a static method called factory that takes an EnityItemID, and EntityItemProperties and return a newly /// constructed (heap allocated) instance of your type. e.g. The following prototype: // static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); -#define REGISTER_ENTITY_TYPE(x) static bool x##Registration = \ +#define REGISTER_ENTITY_TYPE(x) bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, x##EntityItem::factory); /// Macro for registering entity types with an overloaded factory. Like using the REGISTER_ENTITY_TYPE macro: Make sure to add diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 65cae5343a..12cd0b0a91 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -39,7 +39,7 @@ using namespace std; -static int FBXGeometryPointerMetaTypeId = qRegisterMetaType(); +int FBXGeometryPointerMetaTypeId = qRegisterMetaType(); QStringList FBXGeometry::getJointNames() const { QStringList names; @@ -130,9 +130,9 @@ QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { return QString(); } -static int fbxGeometryMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); -static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); +int fbxGeometryMetaTypeId = qRegisterMetaType(); +int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); +int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); glm::vec3 parseVec3(const QString& string) { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index e90ef9e782..388ca26482 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -716,9 +716,9 @@ QQmlContext* OffscreenQmlSurface::getRootContext() { } Q_DECLARE_METATYPE(std::function); -static auto VoidLambdaType = qRegisterMetaType>(); +auto VoidLambdaType = qRegisterMetaType>(); Q_DECLARE_METATYPE(std::function); -static auto VariantLambdaType = qRegisterMetaType>(); +auto VariantLambdaType = qRegisterMetaType>(); void OffscreenQmlSurface::executeOnUiThread(std::function function, bool blocking ) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 4d264995ae..ece1ee730c 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -33,7 +33,7 @@ using namespace gpu::gl; static const QString DEBUG_FLAG("HIFI_ENABLE_OPENGL_45"); -static bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); +bool enableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); Backend* GLBackend::createBackend() { diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 3c8d39a838..f9a8f3b26b 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -22,7 +22,7 @@ using namespace gpu; -static int TexturePointerMetaTypeId = qRegisterMetaType(); +int TexturePointerMetaTypeId = qRegisterMetaType(); std::atomic Texture::_textureCPUCount{ 0 }; std::atomic Texture::_textureCPUMemoryUsage{ 0 }; diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index bc4c1a1599..bde0aca7b7 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -16,7 +16,7 @@ #include "HifiSockAddr.h" #include "NetworkLogging.h" -static int hifiSockAddrMetaTypeId = qRegisterMetaType(); +int hifiSockAddrMetaTypeId = qRegisterMetaType(); HifiSockAddr::HifiSockAddr() : _address(), diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index e4fe292223..1e1cec2413 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -22,9 +22,9 @@ const QString UNKNOWN_NodeType_t_NAME = "Unknown"; -static int NodePtrMetaTypeId = qRegisterMetaType("Node*"); -static int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); -static int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); +int NodePtrMetaTypeId = qRegisterMetaType("Node*"); +int sharedPtrNodeMetaTypeId = qRegisterMetaType>("QSharedPointer"); +int sharedNodePtrMetaTypeId = qRegisterMetaType("SharedNodePointer"); namespace NodeType { QHash TypeNameHash; diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp index 6ef2b7c7d4..cd3eb03473 100644 --- a/libraries/networking/src/ReceivedMessage.cpp +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -14,8 +14,8 @@ #include "QSharedPointer" -static int receivedMessageMetaTypeId = qRegisterMetaType("ReceivedMessage*"); -static int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType>("QSharedPointer"); +int receivedMessageMetaTypeId = qRegisterMetaType("ReceivedMessage*"); +int sharedPtrReceivedMessageMetaTypeId = qRegisterMetaType>("QSharedPointer"); static const int HEAD_DATA_SIZE = 512; diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index d46cae2404..e852332317 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -19,7 +19,7 @@ using namespace udt; -static int packetMetaTypeId = qRegisterMetaType("Packet*"); +int packetMetaTypeId = qRegisterMetaType("Packet*"); using Key = uint64_t; static const std::array KEYS {{ diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b04d582d6d..915c2f44ba 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -18,7 +18,7 @@ Q_DECLARE_METATYPE(PacketType); -static int packetTypeMetaTypeId = qRegisterMetaType(); +int packetTypeMetaTypeId = qRegisterMetaType(); const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery diff --git a/libraries/networking/src/udt/PacketList.cpp b/libraries/networking/src/udt/PacketList.cpp index b12b7e6938..ea84548210 100644 --- a/libraries/networking/src/udt/PacketList.cpp +++ b/libraries/networking/src/udt/PacketList.cpp @@ -15,7 +15,7 @@ using namespace udt; -static int packetListMetaTypeId = qRegisterMetaType("PacketList*"); +int packetListMetaTypeId = qRegisterMetaType("PacketList*"); std::unique_ptr PacketList::create(PacketType packetType, QByteArray extendedHeader, bool isReliable, bool isOrdered) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2fe4427333..9ef16b2daa 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -29,9 +29,9 @@ using namespace std; -static int nakedModelPointerTypeId = qRegisterMetaType(); -static int weakNetworkGeometryPointerTypeId = qRegisterMetaType >(); -static int vec3VectorTypeId = qRegisterMetaType >(); +int nakedModelPointerTypeId = qRegisterMetaType(); +int weakNetworkGeometryPointerTypeId = qRegisterMetaType >(); +int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7c3aad877e..c9d5ca35b0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -65,7 +65,7 @@ static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) -static int functionSignatureMetaID = qRegisterMetaType(); +int functionSignatureMetaID = qRegisterMetaType(); static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ QString message = ""; diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 53fa8b30cf..8e6c1ef6ed 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -21,17 +21,17 @@ #include "RegisteredMetaTypes.h" -static int vec4MetaTypeId = qRegisterMetaType(); -static int vec3MetaTypeId = qRegisterMetaType(); -static int qVectorVec3MetaTypeId = qRegisterMetaType>(); -static int qVectorQuatMetaTypeId = qRegisterMetaType>(); -static int qVectorBoolMetaTypeId = qRegisterMetaType>(); -static int vec2MetaTypeId = qRegisterMetaType(); -static int quatMetaTypeId = qRegisterMetaType(); -static int xColorMetaTypeId = qRegisterMetaType(); -static int pickRayMetaTypeId = qRegisterMetaType(); -static int collisionMetaTypeId = qRegisterMetaType(); -static int qMapURLStringMetaTypeId = qRegisterMetaType>(); +int vec4MetaTypeId = qRegisterMetaType(); +int vec3MetaTypeId = qRegisterMetaType(); +int qVectorVec3MetaTypeId = qRegisterMetaType>(); +int qVectorQuatMetaTypeId = qRegisterMetaType>(); +int qVectorBoolMetaTypeId = qRegisterMetaType>(); +int vec2MetaTypeId = qRegisterMetaType(); +int quatMetaTypeId = qRegisterMetaType(); +int xColorMetaTypeId = qRegisterMetaType(); +int pickRayMetaTypeId = qRegisterMetaType(); +int collisionMetaTypeId = qRegisterMetaType(); +int qMapURLStringMetaTypeId = qRegisterMetaType>(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue); diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 296911ae3e..dd25705f7e 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -57,7 +57,7 @@ public: void addSample(T sample) { if (numSamples > 0) { - average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING); + average = ((float)sample * WEIGHTING) + ((float)average * ONE_MINUS_WEIGHTING); } else { average = sample; } diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index db6598c43d..ccb91590c3 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -157,10 +157,6 @@ protected: //static const wchar_t* EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y"; static const glm::uvec2 QUAD_OFFSET(10, 10); -static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, { - 1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } }; - - void testShaderBuild(const char* vs_src, const char * fs_src) { auto vs = gpu::Shader::createVertex(std::string(vs_src)); auto fs = gpu::Shader::createPixel(std::string(fs_src)); From d7142aee806e570ea07cef9c360ff89cc1c7ca89 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 21 May 2016 18:06:21 -0700 Subject: [PATCH 213/264] try to make osx clang happy, also --- .../src/octree/OctreeInboundPacketProcessor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 32f3de2ff6..d2fef4dfbd 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -153,7 +153,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer qDebug() << " maxSize=" << maxSize; qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", - (int)packetType, message->getRawMessage(), message->getSize(), editData, + (unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition(), maxSize); } @@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", - (int)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); + (unsigned char)packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition()); } // Make sure our Node and NodeList knows we've heard from this node. @@ -208,7 +208,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { - qDebug("unknown packet ignored... packetType=%hhu", (int)packetType); + qDebug("unknown packet ignored... packetType=%hhu", (unsigned char)packetType); } } From cd1e910844ca3d7e9f6d0e722a6df07473fd9672 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 10 May 2016 09:27:01 -0700 Subject: [PATCH 214/264] Add a generic shape primitive --- .../src/EntityTreeRenderer.cpp | 10 +- .../src/RenderableBoxEntityItem.cpp | 76 ------ .../src/RenderableBoxEntityItem.h | 34 --- .../src/RenderableShapeEntityItem.cpp | 113 +++++++++ .../src/RenderableShapeEntityItem.h | 36 +++ .../src/RenderableSphereEntityItem.cpp | 80 ------ .../src/RenderableSphereEntityItem.h | 35 --- libraries/entities/src/BoxEntityItem.cpp | 103 -------- libraries/entities/src/BoxEntityItem.h | 64 ----- .../entities/src/EntityItemProperties.cpp | 15 +- libraries/entities/src/EntityItemProperties.h | 4 +- libraries/entities/src/EntityPropertyFlags.h | 2 + libraries/entities/src/EntityTypes.cpp | 10 +- libraries/entities/src/EntityTypes.h | 18 +- libraries/entities/src/ShapeEntityItem.cpp | 230 ++++++++++++++++++ libraries/entities/src/ShapeEntityItem.h | 103 ++++++++ libraries/entities/src/SphereEntityItem.cpp | 132 ---------- libraries/entities/src/SphereEntityItem.h | 70 ------ libraries/render-utils/src/GeometryCache.cpp | 41 ++-- libraries/render-utils/src/GeometryCache.h | 7 + .../developer/tests/entityEditStressTest.js | 164 +------------ scripts/developer/tests/entitySpawnTool.js | 184 ++++++++++++++ scripts/developer/tests/primitivesTest.js | 24 ++ scripts/system/html/entityProperties.html | 51 +++- tests/entities/src/main.cpp | 4 +- 25 files changed, 819 insertions(+), 791 deletions(-) delete mode 100644 libraries/entities-renderer/src/RenderableBoxEntityItem.cpp delete mode 100644 libraries/entities-renderer/src/RenderableBoxEntityItem.h create mode 100644 libraries/entities-renderer/src/RenderableShapeEntityItem.cpp create mode 100644 libraries/entities-renderer/src/RenderableShapeEntityItem.h delete mode 100644 libraries/entities-renderer/src/RenderableSphereEntityItem.cpp delete mode 100644 libraries/entities-renderer/src/RenderableSphereEntityItem.h delete mode 100644 libraries/entities/src/BoxEntityItem.cpp delete mode 100644 libraries/entities/src/BoxEntityItem.h create mode 100644 libraries/entities/src/ShapeEntityItem.cpp create mode 100644 libraries/entities/src/ShapeEntityItem.h delete mode 100644 libraries/entities/src/SphereEntityItem.cpp delete mode 100644 libraries/entities/src/SphereEntityItem.h create mode 100644 scripts/developer/tests/entitySpawnTool.js create mode 100644 scripts/developer/tests/primitivesTest.js diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index bec2fa9b8d..56f6438e70 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -29,17 +29,16 @@ #include "RenderableEntityItem.h" -#include "RenderableBoxEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableModelEntityItem.h" #include "RenderableParticleEffectEntityItem.h" -#include "RenderableSphereEntityItem.h" #include "RenderableTextEntityItem.h" #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableLineEntityItem.h" #include "RenderablePolyVoxEntityItem.h" #include "RenderablePolyLineEntityItem.h" +#include "RenderableShapeEntityItem.h" #include "EntitiesRendererLogging.h" #include "AddressManager.h" #include @@ -56,8 +55,6 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _dontDoPrecisionPicking(false) { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) - REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory) - REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Text, RenderableTextEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, RenderableWebEntityItem::factory) @@ -66,7 +63,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyLine, RenderablePolyLineEntityItem::factory) - + REGISTER_ENTITY_TYPE_WITH_FACTORY(Shape, RenderableShapeEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableShapeEntityItem::boxFactory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableShapeEntityItem::sphereFactory) + _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; } diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp deleted file mode 100644 index e392450c08..0000000000 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// RenderableBoxEntityItem.cpp -// libraries/entities-renderer/src/ -// -// Created by Brad Hefta-Gaub on 8/6/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "RenderableBoxEntityItem.h" - -#include - -#include - -#include -#include -#include - -#include -#include - -EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity{ new RenderableBoxEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -void RenderableBoxEntityItem::setUserData(const QString& value) { - if (value != getUserData()) { - BoxEntityItem::setUserData(value); - if (_procedural) { - _procedural->parse(value); - } - } -} - -void RenderableBoxEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderableBoxEntityItem::render"); - Q_ASSERT(getType() == EntityTypes::Box); - Q_ASSERT(args->_batch); - - if (!_procedural) { - _procedural.reset(new Procedural(this->getUserData())); - _procedural->_vertexSource = simple_vert; - _procedural->_fragmentSource = simple_frag; - _procedural->_state->setCullMode(gpu::State::CULL_NONE); - _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); - _procedural->_state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - } - - gpu::Batch& batch = *args->_batch; - glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha()); - - bool success; - auto transToCenter = getTransformToCenter(success); - if (!success) { - return; - } - - batch.setModelTransform(transToCenter); // we want to include the scale as well - if (_procedural->ready()) { - _procedural->prepare(batch, getPosition(), getDimensions()); - auto color = _procedural->getColor(cubeColor); - batch._glColor4f(color.r, color.g, color.b, color.a); - DependencyManager::get()->renderCube(batch); - } else { - DependencyManager::get()->renderSolidCubeInstance(batch, cubeColor); - } - static const auto triCount = DependencyManager::get()->getCubeTriangleCount(); - args->_details._trianglesRendered += (int)triCount; -} diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h deleted file mode 100644 index 67f881dbd8..0000000000 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// RenderableBoxEntityItem.h -// libraries/entities-renderer/src/ -// -// Created by Brad Hefta-Gaub on 8/6/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_RenderableBoxEntityItem_h -#define hifi_RenderableBoxEntityItem_h - -#include -#include - -#include "RenderableEntityItem.h" - -class RenderableBoxEntityItem : public BoxEntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableBoxEntityItem(const EntityItemID& entityItemID) : BoxEntityItem(entityItemID) { } - - virtual void render(RenderArgs* args) override; - virtual void setUserData(const QString& value) override; - - SIMPLE_RENDERABLE() -private: - QSharedPointer _procedural; -}; - - -#endif // hifi_RenderableBoxEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp new file mode 100644 index 0000000000..7d30b7a47c --- /dev/null +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -0,0 +1,113 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "RenderableShapeEntityItem.h" + +#include + +#include + +#include +#include +#include + +#include +#include + +// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 +// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. +static const float SPHERE_ENTITY_SCALE = 0.5f; + +static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = { + GeometryCache::Triangle, + GeometryCache::Quad, + GeometryCache::Circle, + GeometryCache::Cube, + GeometryCache::Sphere, + GeometryCache::Tetrahedron, + GeometryCache::Octahetron, + GeometryCache::Dodecahedron, + GeometryCache::Icosahedron, + GeometryCache::Torus, + GeometryCache::Cone, + GeometryCache::Cylinder, +}; + + +RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity = std::make_shared(entityID); + entity->setProperties(properties); + return entity; +} + +EntityItemPointer RenderableShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return baseFactory(entityID, properties); +} + +EntityItemPointer RenderableShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Cube); + return result; +} + +EntityItemPointer RenderableShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Sphere); + return result; +} + +void RenderableShapeEntityItem::setUserData(const QString& value) { + if (value != getUserData()) { + ShapeEntityItem::setUserData(value); + if (_procedural) { + _procedural->parse(value); + } + } +} + +void RenderableShapeEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderableShapeEntityItem::render"); + //Q_ASSERT(getType() == EntityTypes::Shape); + Q_ASSERT(args->_batch); + + if (!_procedural) { + _procedural.reset(new Procedural(getUserData())); + _procedural->_vertexSource = simple_vert; + _procedural->_fragmentSource = simple_frag; + _procedural->_state->setCullMode(gpu::State::CULL_NONE); + _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); + _procedural->_state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + } + + gpu::Batch& batch = *args->_batch; + glm::vec4 color(toGlm(getXColor()), getLocalRenderAlpha()); + bool success; + Transform modelTransform = getTransformToCenter(success); + if (!success) { + return; + } + if (_shape != entity::Cube) { + modelTransform.postScale(SPHERE_ENTITY_SCALE); + } + batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation + if (_procedural->ready()) { + _procedural->prepare(batch, getPosition(), getDimensions()); + auto outColor = _procedural->getColor(color); + batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a); + DependencyManager::get()->renderShape(batch, MAPPING[_shape]); + } else { + // FIXME, support instanced multi-shape rendering using multidraw indirect + DependencyManager::get()->renderSolidShapeInstance(batch, MAPPING[_shape], color); + } + + + static const auto triCount = DependencyManager::get()->getShapeTriangleCount(MAPPING[_shape]); + args->_details._trianglesRendered += (int)triCount; +} diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h new file mode 100644 index 0000000000..b18370b13c --- /dev/null +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -0,0 +1,36 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderableShapeEntityItem_h +#define hifi_RenderableShapeEntityItem_h + +#include +#include + +#include "RenderableEntityItem.h" + +class RenderableShapeEntityItem : public ShapeEntityItem { + using Pointer = std::shared_ptr; + static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties); +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + RenderableShapeEntityItem(const EntityItemID& entityItemID) : ShapeEntityItem(entityItemID) {} + + void render(RenderArgs* args) override; + void setUserData(const QString& value) override; + + SIMPLE_RENDERABLE(); + +private: + QSharedPointer _procedural; +}; + + +#endif // hifi_RenderableShapeEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp deleted file mode 100644 index c3437b0e4a..0000000000 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// -// RenderableSphereEntityItem.cpp -// interface/src -// -// Created by Brad Hefta-Gaub on 8/6/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "RenderableSphereEntityItem.h" - -#include - -#include - -#include -#include -#include - -#include -#include - -// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 -// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. -static const float SPHERE_ENTITY_SCALE = 0.5f; - - -EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity{ new RenderableSphereEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -void RenderableSphereEntityItem::setUserData(const QString& value) { - if (value != getUserData()) { - SphereEntityItem::setUserData(value); - if (_procedural) { - _procedural->parse(value); - } - } -} - -void RenderableSphereEntityItem::render(RenderArgs* args) { - PerformanceTimer perfTimer("RenderableSphereEntityItem::render"); - Q_ASSERT(getType() == EntityTypes::Sphere); - Q_ASSERT(args->_batch); - - if (!_procedural) { - _procedural.reset(new Procedural(getUserData())); - _procedural->_vertexSource = simple_vert; - _procedural->_fragmentSource = simple_frag; - _procedural->_state->setCullMode(gpu::State::CULL_NONE); - _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); - _procedural->_state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - } - - gpu::Batch& batch = *args->_batch; - glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha()); - bool success; - Transform modelTransform = getTransformToCenter(success); - if (!success) { - return; - } - modelTransform.postScale(SPHERE_ENTITY_SCALE); - batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation - if (_procedural->ready()) { - _procedural->prepare(batch, getPosition(), getDimensions()); - auto color = _procedural->getColor(sphereColor); - batch._glColor4f(color.r, color.g, color.b, color.a); - DependencyManager::get()->renderSphere(batch); - } else { - DependencyManager::get()->renderSolidSphereInstance(batch, sphereColor); - } - static const auto triCount = DependencyManager::get()->getSphereTriangleCount(); - args->_details._trianglesRendered += (int)triCount; -} diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.h b/libraries/entities-renderer/src/RenderableSphereEntityItem.h deleted file mode 100644 index 5efe49854a..0000000000 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// RenderableSphereEntityItem.h -// interface/src/entities -// -// Created by Brad Hefta-Gaub on 8/6/14. -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_RenderableSphereEntityItem_h -#define hifi_RenderableSphereEntityItem_h - -#include -#include - -#include "RenderableEntityItem.h" - -class RenderableSphereEntityItem : public SphereEntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - RenderableSphereEntityItem(const EntityItemID& entityItemID) : SphereEntityItem(entityItemID) { } - - virtual void render(RenderArgs* args) override; - virtual void setUserData(const QString& value) override; - - SIMPLE_RENDERABLE(); - -private: - QSharedPointer _procedural; -}; - - -#endif // hifi_RenderableSphereEntityItem_h diff --git a/libraries/entities/src/BoxEntityItem.cpp b/libraries/entities/src/BoxEntityItem.cpp deleted file mode 100644 index bf02d383ab..0000000000 --- a/libraries/entities/src/BoxEntityItem.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// -// BoxEntityItem.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include - -#include - -#include "BoxEntityItem.h" -#include "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" - -EntityItemPointer BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity { new BoxEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Box; -} - -EntityItemProperties BoxEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - - properties._color = getXColor(); - properties._colorChanged = false; - - return properties; -} - -bool BoxEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "BoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - } - return somethingChanged; -} - -int BoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); - - return bytesRead; -} - - -// TODO: eventually only include properties changed since the params.lastViewFrustumSent time -EntityPropertyFlags BoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; - return requestedProperties; -} - -void BoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); -} - -void BoxEntityItem::debugDump() const { - quint64 now = usecTimestampNow(); - qCDebug(entities) << " BOX EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; - qCDebug(entities) << " position:" << debugTreeVector(getPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); -} - diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h deleted file mode 100644 index 6196346b9a..0000000000 --- a/libraries/entities/src/BoxEntityItem.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// BoxEntityItem.h -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_BoxEntityItem_h -#define hifi_BoxEntityItem_h - -#include "EntityItem.h" - -class BoxEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - BoxEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; - virtual bool setProperties(const EntityItemProperties& properties); - - // TODO: eventually only include properties changed since the params.lastViewFrustumSent time - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); - - const rgbColor& getColor() const { return _color; } - xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } - - void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } - void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; - } - - virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } - virtual bool shouldBePhysical() const { return !isDead(); } - - virtual void debugDump() const; - -protected: - rgbColor _color; -}; - -#endif // hifi_BoxEntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 99285b4986..f273507d0d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -689,6 +689,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(shape, QString, setShape); COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); @@ -846,6 +847,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector); + ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); @@ -1141,7 +1144,9 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); } - + if (properties.getType() == EntityTypes::Shape) { + APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); + } APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); @@ -1429,6 +1434,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_WIDTHS, QVector, setStrokeWidths); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); } + if (properties.getType() == EntityTypes::Shape) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); + } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); @@ -1915,6 +1923,7 @@ QList EntityItemProperties::listChangedProperties() { if (queryAACubeChanged()) { out += "queryAACube"; } + if (clientOnlyChanged()) { out += "clientOnly"; } @@ -1929,6 +1938,10 @@ QList EntityItemProperties::listChangedProperties() { out += "ghostingAllowed"; } + if (shapeChanged()) { + out += "shape"; + } + getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); getSkybox().listChangedProperties(out); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 59749cfef5..6018ba793f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -50,7 +50,7 @@ const quint64 UNKNOWN_CREATED_TIME = 0; /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues -/// all units for position, dimensions, etc are in meter units +/// all units for SI units (meter, second, radian, etc) class EntityItemProperties { friend class EntityItem; // TODO: consider removing this friend relationship and use public methods friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods @@ -64,6 +64,7 @@ class EntityItemProperties { friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -195,6 +196,7 @@ public: DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); + DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); // these are used when bouncing location data into and out of scripts DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 20c451eaae..36bb37c8f3 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -175,6 +175,8 @@ enum EntityPropertyList { PROP_CLIENT_ONLY, // doesn't go over wire PROP_OWNING_AVATAR_ID, // doesn't go over wire + PROP_SHAPE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 52c2242629..7b1133c2aa 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -18,17 +18,16 @@ #include "EntityItemProperties.h" #include "EntityTypes.h" -#include "BoxEntityItem.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" #include "ParticleEffectEntityItem.h" -#include "SphereEntityItem.h" #include "TextEntityItem.h" #include "WebEntityItem.h" #include "ZoneEntityItem.h" #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" +#include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -39,16 +38,17 @@ const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; // Register Entity the default implementations of entity types here... REGISTER_ENTITY_TYPE(Model) -REGISTER_ENTITY_TYPE(Box) REGISTER_ENTITY_TYPE(Web) -REGISTER_ENTITY_TYPE(Sphere) REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Text) REGISTER_ENTITY_TYPE(ParticleEffect) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) -REGISTER_ENTITY_TYPE(PolyLine); +REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(Shape) +REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) +REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 3536327d18..be3be03713 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,8 @@ public: Line, PolyVox, PolyLine, - LAST = PolyLine + Shape, + LAST = Shape } EntityType; static const QString& getEntityTypeName(EntityType entityType); @@ -72,6 +73,15 @@ private: #define REGISTER_ENTITY_TYPE(x) static bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, x##EntityItem::factory); + +struct EntityRegistrationChecker { + EntityRegistrationChecker(bool result, const char* debugMessage) { + if (!result) { + qDebug() << debugMessage; + } + } +}; + /// Macro for registering entity types with an overloaded factory. Like using the REGISTER_ENTITY_TYPE macro: Make sure to add /// an element to the EntityType enum with your name. But unlike REGISTER_ENTITY_TYPE, your class can be named anything /// so long as you provide a static method passed to the macro, that takes an EnityItemID, and EntityItemProperties and @@ -79,9 +89,9 @@ private: // static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); #define REGISTER_ENTITY_TYPE_WITH_FACTORY(x,y) static bool x##Registration = \ EntityTypes::registerEntityType(EntityTypes::x, #x, y); \ - if (!x##Registration) { \ - qDebug() << "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"; \ - } + EntityRegistrationChecker x##RegistrationChecker( \ + x##Registration, \ + "UNEXPECTED: REGISTER_ENTITY_TYPE_WITH_FACTORY(" #x "," #y ") FAILED.!"); #endif // hifi_EntityTypes_h diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp new file mode 100644 index 0000000000..a24c7e1df5 --- /dev/null +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -0,0 +1,230 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include + +#include + +#include + +#include "EntitiesLogging.h" +#include "EntityItemProperties.h" +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "ShapeEntityItem.h" + +namespace entity { + static const std::vector shapeStrings { { + "Triangle", + "Quad", + "Circle", + "Cube", + "Sphere", + "Tetrahedron", + "Octahetron", + "Dodecahedron", + "Icosahedron", + "Torus", + "Cone", + "Cylinder" + } }; + + Shape shapeFromString(const ::QString& shapeString) { + for (size_t i = 0; i < shapeStrings.size(); ++i) { + if (shapeString.toLower() == shapeStrings[i].toLower()) { + return static_cast(i); + } + } + return Shape::Sphere; + } + + ::QString stringFromShape(Shape shape) { + return shapeStrings[shape]; + } +} + +ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity { new ShapeEntityItem(entityID) }; + entity->setProperties(properties); + return entity; +} + +EntityItemPointer ShapeEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return baseFactory(entityID, properties); +} + +EntityItemPointer ShapeEntityItem::boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Shape::Cube); + return result; +} + +EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { + auto result = baseFactory(entityID, properties); + result->setShape(entity::Shape::Sphere); + return result; +} + +// our non-pure virtual subclass for now... +ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Shape; + _volumeMultiplier *= PI / 6.0f; +} + +EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + properties.setColor(getXColor()); + properties.setShape(entity::stringFromShape(getShape())); + return properties; +} + +void ShapeEntityItem::setShape(const entity::Shape& shape) { + _shape = shape; + switch (_shape) { + case entity::Shape::Cube: + _type = EntityTypes::Box; + break; + case entity::Shape::Sphere: + _type = EntityTypes::Sphere; + break; + default: + _type = EntityTypes::Shape; + break; + } +} + +bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "ShapeEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); + READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_SHAPE; + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + return requestedProperties; +} + +void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getAlpha()); +} + +// This value specifes how the shape should be treated by physics calculations. +// For now, all polys will act as spheres +ShapeType ShapeEntityItem::getShapeType() const { + return SHAPE_TYPE_ELLIPSOID; +} + +void ShapeEntityItem::setColor(const rgbColor& value) { + memcpy(_color, value, sizeof(rgbColor)); +} + +xColor ShapeEntityItem::getXColor() const { + return xColor { _color[0], _color[1], _color[2] }; +} + +void ShapeEntityItem::setColor(const xColor& value) { + setColor(rgbColor { value.red, value.green, value.blue }); +} + +QColor ShapeEntityItem::getQColor() const { + auto& color = getColor(); + return QColor(color[0], color[1], color[2], (int)(getAlpha() * 255)); +} + +void ShapeEntityItem::setColor(const QColor& value) { + setColor(rgbColor { (uint8_t)value.red(), (uint8_t)value.green(), (uint8_t)value.blue() }); + setAlpha(value.alpha()); +} + +bool ShapeEntityItem::supportsDetailedRayIntersection() const { + return _shape == entity::Sphere; +} + +bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + void** intersectedObject, bool precisionPicking) const { + // determine the ray in the frame of the entity transformed from a unit sphere + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); + + float localDistance; + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { + // determine where on the unit sphere the hit point occured + glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); + // then translate back to work coordinates + glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); + distance = glm::distance(origin, hitAt); + bool success; + surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); + if (!success) { + return false; + } + return true; + } + return false; +} + +void ShapeEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " shape:" << stringFromShape(_shape); + qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; + qCDebug(entities) << " position:" << debugTreeVector(getPosition()); + qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); +} + diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h new file mode 100644 index 0000000000..f3cb2abd66 --- /dev/null +++ b/libraries/entities/src/ShapeEntityItem.h @@ -0,0 +1,103 @@ +// +// Created by Bradley Austin Davis on 2016/05/09 +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ShapeEntityItem_h +#define hifi_ShapeEntityItem_h + +#include "EntityItem.h" + +namespace entity { + enum Shape { + Triangle, + Quad, + Circle, + Cube, + Sphere, + Tetrahedron, + Octahetron, + Dodecahedron, + Icosahedron, + Torus, + Cone, + Cylinder, + NUM_SHAPES, + }; + + Shape shapeFromString(const ::QString& shapeString); + ::QString stringFromShape(Shape shape); +} + + +class ShapeEntityItem : public EntityItem { + using Pointer = std::shared_ptr; + static Pointer baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties); +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer sphereFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + static EntityItemPointer boxFactory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ShapeEntityItem(const EntityItemID& entityItemID); + + void pureVirtualFunctionPlaceHolder() override { }; + // Triggers warnings on OSX + //ALLOW_INSTANTIATION + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + entity::Shape getShape() const { return _shape; } + void setShape(const entity::Shape& shape); + void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } + + float getAlpha() const { return _alpha; }; + void setAlpha(float alpha) { _alpha = alpha; } + + const rgbColor& getColor() const { return _color; } + void setColor(const rgbColor& value); + + xColor getXColor() const; + void setColor(const xColor& value); + + QColor getQColor() const; + void setColor(const QColor& value); + + ShapeType getShapeType() const override; + bool shouldBePhysical() const override { return !isDead(); } + + bool supportsDetailedRayIntersection() const override; + bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + void** intersectedObject, bool precisionPicking) const override; + + void debugDump() const override; + +protected: + + float _alpha { 1 }; + rgbColor _color; + entity::Shape _shape { entity::Shape::Sphere }; +}; + +#endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp deleted file mode 100644 index 7ad7b39f20..0000000000 --- a/libraries/entities/src/SphereEntityItem.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// -// SphereEntityItem.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#include - -#include - -#include -#include - -#include "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" -#include "SphereEntityItem.h" - -EntityItemPointer SphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity { new SphereEntityItem(entityID) }; - entity->setProperties(properties); - return entity; -} - -// our non-pure virtual subclass for now... -SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Sphere; - _volumeMultiplier *= PI / 6.0f; -} - -EntityItemProperties SphereEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setColor(getXColor()); - return properties; -} - -bool SphereEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "SphereEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - } - return somethingChanged; -} - -int SphereEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); - - return bytesRead; -} - - -// TODO: eventually only include properties changed since the params.lastViewFrustumSent time -EntityPropertyFlags SphereEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; - return requestedProperties; -} - -void SphereEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); -} - -bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const { - // determine the ray in the frame of the entity transformed from a unit sphere - glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); - glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 entityFrameDirection = glm::normalize(glm::vec3(worldToEntityMatrix * glm::vec4(direction, 0.0f))); - - float localDistance; - // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 - if (findRaySphereIntersection(entityFrameOrigin, entityFrameDirection, glm::vec3(0.0f), 0.5f, localDistance)) { - // determine where on the unit sphere the hit point occured - glm::vec3 entityFrameHitAt = entityFrameOrigin + (entityFrameDirection * localDistance); - // then translate back to work coordinates - glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); - distance = glm::distance(origin, hitAt); - bool success; - surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); - if (!success) { - return false; - } - return true; - } - return false; -} - - -void SphereEntityItem::debugDump() const { - quint64 now = usecTimestampNow(); - qCDebug(entities) << "SHPERE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2]; - qCDebug(entities) << " position:" << debugTreeVector(getPosition()); - qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions()); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); -} - diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h deleted file mode 100644 index fda5eab009..0000000000 --- a/libraries/entities/src/SphereEntityItem.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// SphereEntityItem.h -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_SphereEntityItem_h -#define hifi_SphereEntityItem_h - -#include "EntityItem.h" - -class SphereEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - SphereEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const; - virtual bool setProperties(const EntityItemProperties& properties); - - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged); - - const rgbColor& getColor() const { return _color; } - xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } - - void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } - void setColor(const xColor& value) { - _color[RED_INDEX] = value.red; - _color[GREEN_INDEX] = value.green; - _color[BLUE_INDEX] = value.blue; - } - - virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } - virtual bool shouldBePhysical() const { return !isDead(); } - - virtual bool supportsDetailedRayIntersection() const { return true; } - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - void** intersectedObject, bool precisionPicking) const; - - virtual void debugDump() const; - -protected: - - rgbColor _color; -}; - -#endif // hifi_SphereEntityItem_h diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 15bf44744c..6852d17882 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -112,10 +112,12 @@ void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count } } +// The golden ratio +static const float PHI = 1.61803398874f; + const VertexVector& icosahedronVertices() { - static const float phi = (1.0f + sqrtf(5.0f)) / 2.0f; - static const float a = 0.5f; - static const float b = 1.0f / (2.0f * phi); + static const float a = 1; + static const float b = PHI / 2.0f; static const VertexVector vertices{ // vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), // @@ -143,11 +145,10 @@ const VertexVector& icosahedronVertices() { } const VertexVector& tetrahedronVertices() { - static const float a = 1.0f / sqrtf(2.0f); - static const auto A = vec3(0, 1, a); - static const auto B = vec3(0, -1, a); - static const auto C = vec3(1, 0, -a); - static const auto D = vec3(-1, 0, -a); + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(1, -1, -1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(-1, -1, 1); static const VertexVector vertices{ A, B, C, D, B, A, @@ -356,7 +357,7 @@ void GeometryCache::buildShapes() { for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { auto triangleVertexIndex = j; auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(glm::normalize(originalVertices[vertexIndex])); + vertices.push_back(originalVertices[vertexIndex]); vertices.push_back(faceNormal); } } @@ -437,7 +438,7 @@ void GeometryCache::buildShapes() { for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { auto triangleVertexIndex = j; auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(glm::normalize(originalVertices[vertexIndex])); + vertices.push_back(originalVertices[vertexIndex]); vertices.push_back(faceNormal); indices.push_back((uint16_t)(vertexIndex + startingIndex)); } @@ -1801,10 +1802,10 @@ uint32_t toCompactColor(const glm::vec4& color) { static const size_t INSTANCE_COLOR_BUFFER = 0; -void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4& color, bool isWire, +void renderInstances(gpu::Batch& batch, const glm::vec4& color, bool isWire, const render::ShapePipelinePointer& pipeline, GeometryCache::Shape shape) { // Add pipeline to name - std::string instanceName = name + std::to_string(std::hash()(pipeline)); + std::string instanceName = (isWire ? "wire_shapes_" : "solid_shapes_") + std::to_string(shape) + "_" + std::to_string(std::hash()(pipeline)); // Add color to named buffer { @@ -1826,14 +1827,16 @@ void renderInstances(const std::string& name, gpu::Batch& batch, const glm::vec4 }); } +void GeometryCache::renderSolidShapeInstance(gpu::Batch& batch, GeometryCache::Shape shape, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { + renderInstances(batch, color, false, pipeline, shape); +} + void GeometryCache::renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Sphere); + renderInstances(batch, color, false, pipeline, GeometryCache::Sphere); } void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Sphere); + renderInstances(batch, color, true, pipeline, GeometryCache::Sphere); } // Enable this in a debug build to cause 'box' entities to iterate through all the @@ -1841,8 +1844,6 @@ void GeometryCache::renderWireSphereInstance(gpu::Batch& batch, const glm::vec4& //#define DEBUG_SHAPES void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { - static const std::string INSTANCE_NAME = __FUNCTION__; - #ifdef DEBUG_SHAPES static auto startTime = usecTimestampNow(); renderInstances(INSTANCE_NAME, batch, color, pipeline, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { @@ -1876,11 +1877,11 @@ void GeometryCache::renderSolidCubeInstance(gpu::Batch& batch, const glm::vec4& } }); #else - renderInstances(INSTANCE_NAME, batch, color, false, pipeline, GeometryCache::Cube); + renderInstances(batch, color, false, pipeline, GeometryCache::Cube); #endif } void GeometryCache::renderWireCubeInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline) { static const std::string INSTANCE_NAME = __FUNCTION__; - renderInstances(INSTANCE_NAME, batch, color, true, pipeline, GeometryCache::Cube); + renderInstances(batch, color, true, pipeline, GeometryCache::Cube); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index c4531aa102..7fa543abe2 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -163,6 +163,13 @@ public: void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer); + void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec4& color = glm::vec4(1), + const render::ShapePipelinePointer& pipeline = _simplePipeline); + void renderSolidShapeInstance(gpu::Batch& batch, Shape shape, const glm::vec3& color, + const render::ShapePipelinePointer& pipeline = _simplePipeline) { + renderSolidShapeInstance(batch, shape, glm::vec4(color, 1.0f), pipeline); + } + void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec4& color, const render::ShapePipelinePointer& pipeline = _simplePipeline); void renderSolidSphereInstance(gpu::Batch& batch, const glm::vec3& color, diff --git a/scripts/developer/tests/entityEditStressTest.js b/scripts/developer/tests/entityEditStressTest.js index 2d3c8ad0e1..8fa06a968d 100644 --- a/scripts/developer/tests/entityEditStressTest.js +++ b/scripts/developer/tests/entityEditStressTest.js @@ -15,161 +15,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var NUM_ENTITIES = 20000; // number of entities to spawn -var ENTITY_SPAWN_LIMIT = 1000; -var ENTITY_SPAWN_INTERVAL = 0.1; +Script.include("./entitySpawnTool.js"); -var UPDATE_INTERVAL = 0.05; // Re-randomize the entity's position every x seconds / ms -var ENTITY_LIFETIME = 30; // Entity timeout (when/if we crash, we need the entities to delete themselves) -var KEEPALIVE_INTERVAL = 5; // Refreshes the timeout every X seconds - -var RADIUS = 5.0; // Spawn within this radius (square) -var Y_OFFSET = 1.5; // Spawn at an offset below the avatar -var TEST_ENTITY_NAME = "EntitySpawnTest"; - -(function () { - this.makeEntity = function (properties) { - var entity = Entities.addEntity(properties); - // print("spawning entity: " + JSON.stringify(properties)); - - return { - update: function (properties) { - Entities.editEntity(entity, properties); - }, - destroy: function () { - Entities.deleteEntity(entity) - }, - getAge: function () { - return Entities.getEntityProperties(entity).age; - } - }; - } - - this.randomPositionXZ = function (center, radius) { - return { - x: center.x + (Math.random() * radius * 2.0) - radius, - y: center.y, - z: center.z + (Math.random() * radius * 2.0) - radius - }; - } - this.randomColor = function () { - var shade = Math.floor(Math.random() * 255); - var hue = Math.floor(Math.random() * (255 - shade)); - - return { - red: shade + hue, - green: shade, - blue: shade - }; - } - this.randomDimensions = function () { - return { - x: 0.1 + Math.random() * 0.5, - y: 0.1 + Math.random() * 0.1, - z: 0.1 + Math.random() * 0.5 - }; - } -})(); - -(function () { - var entities = []; - var entitiesToCreate = 0; - var entitiesSpawned = 0; - - - function clear () { - var ids = Entities.findEntities(MyAvatar.position, 50); - var that = this; - ids.forEach(function(id) { - var properties = Entities.getEntityProperties(id); - if (properties.name == TEST_ENTITY_NAME) { - Entities.deleteEntity(id); - } - }, this); - } - - function createEntities () { - print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")"); - entitiesToCreate = NUM_ENTITIES; - Script.update.connect(spawnEntities); - } - - var spawnTimer = 0.0; - function spawnEntities (dt) { - if (entitiesToCreate <= 0) { - Script.update.disconnect(spawnEntities); - print("Finished spawning entities"); - } - else if ((spawnTimer -= dt) < 0.0){ - spawnTimer = ENTITY_SPAWN_INTERVAL; - - var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT); - print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")"); - - entitiesToCreate -= n; - - var center = MyAvatar.position; - center.y -= Y_OFFSET; - - for (; n > 0; --n) { - entities.push(makeEntity({ - type: "Box", - name: TEST_ENTITY_NAME, - position: randomPositionXZ(center, RADIUS), - color: randomColor(), - dimensions: randomDimensions(), - lifetime: ENTITY_LIFETIME - })); - } - } - } - - function despawnEntities () { - print("despawning entities"); - entities.forEach(function (entity) { - entity.destroy(); - }); - entities = []; - } - - var keepAliveTimer = 0.0; - var updateTimer = 0.0; - - // Runs the following entity updates: - // a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and - // b) re-randomizes its position every UPDATE_INTERVAL seconds. - // This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again). - function updateEntities (dt) { - var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false; - var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false; - - if (updateLifetime || updateProperties) { - var center = MyAvatar.position; - center.y -= Y_OFFSET; - - entities.forEach((updateLifetime && updateProperties && function (entity) { - entity.update({ - lifetime: entity.getAge() + ENTITY_LIFETIME, - position: randomPositionXZ(center, RADIUS) - }); - }) || (updateLifetime && function (entity) { - entity.update({ - lifetime: entity.getAge() + ENTITY_LIFETIME - }); - }) || (updateProperties && function (entity) { - entity.update({ - position: randomPositionXZ(center, RADIUS) - }); - }) || null, this); - } - } - - function init () { - Script.update.disconnect(init); - clear(); - createEntities(); - Script.update.connect(updateEntities); - Script.scriptEnding.connect(despawnEntities); - } - Script.update.connect(init); -})(); \ No newline at end of file +ENTITY_SPAWNER({ + count: 20000, + spawnLimit: 1000, + spawnInterval: 0.1, + updateInterval: 0.05 +}); diff --git a/scripts/developer/tests/entitySpawnTool.js b/scripts/developer/tests/entitySpawnTool.js new file mode 100644 index 0000000000..d88933b867 --- /dev/null +++ b/scripts/developer/tests/entitySpawnTool.js @@ -0,0 +1,184 @@ +// entityEditStressTest.js +// +// Created by Seiji Emery on 8/31/15 +// Copyright 2015 High Fidelity, Inc. +// +// Stress tests the client + server-side entity trees by spawning huge numbers of entities in +// close proximity to your avatar and updating them continuously (ie. applying position edits), +// with the intent of discovering crashes and other bugs related to the entity, scripting, +// rendering, networking, and/or physics subsystems. +// +// This script was originally created to find + diagnose an a clientside crash caused by improper +// locking of the entity tree, but can be reused for other purposes. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +ENTITY_SPAWNER = function (properties) { + properties = properties || {}; + var RADIUS = properties.radius || 5.0; // Spawn within this radius (square) + var Y_OFFSET = properties.yOffset || 1.5; // Spawn at an offset below the avatar + var TEST_ENTITY_NAME = properties.entityName || "EntitySpawnTest"; + + var NUM_ENTITIES = properties.count || 1000; // number of entities to spawn + var ENTITY_SPAWN_LIMIT = properties.spawnLimit || 100; + var ENTITY_SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 1.0; + + var UPDATE_INTERVAL = properties.updateInterval || properties.interval || 0.1; // Re-randomize the entity's position every x seconds / ms + var ENTITY_LIFETIME = properties.lifetime || 30; // Entity timeout (when/if we crash, we need the entities to delete themselves) + var KEEPALIVE_INTERVAL = properties.keepAlive || 5; // Refreshes the timeout every X seconds + var UPDATES = properties.updates || false + var SHAPES = properties.shapes || ["Icosahedron", "Tetrahedron", "Cube", "Sphere" ]; + + function makeEntity(properties) { + var entity = Entities.addEntity(properties); + // print("spawning entity: " + JSON.stringify(properties)); + + return { + update: function (properties) { + Entities.editEntity(entity, properties); + }, + destroy: function () { + Entities.deleteEntity(entity) + }, + getAge: function () { + return Entities.getEntityProperties(entity).age; + } + }; + } + + function randomPositionXZ(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + function randomPosition(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y + (Math.random() * radius * 2.0) - radius, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + + function randomColor() { + return { + red: Math.floor(Math.random() * 255), + green: Math.floor(Math.random() * 255), + blue: Math.floor(Math.random() * 255), + }; + } + + function randomDimensions() { + return { + x: 0.1 + Math.random() * 0.5, + y: 0.1 + Math.random() * 0.1, + z: 0.1 + Math.random() * 0.5 + }; + } + + var entities = []; + var entitiesToCreate = 0; + var entitiesSpawned = 0; + var spawnTimer = 0.0; + var keepAliveTimer = 0.0; + var updateTimer = 0.0; + + function clear () { + var ids = Entities.findEntities(MyAvatar.position, 50); + var that = this; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == TEST_ENTITY_NAME) { + Entities.deleteEntity(id); + } + }, this); + } + + function createEntities () { + print("Creating " + NUM_ENTITIES + " entities (UPDATE_INTERVAL = " + UPDATE_INTERVAL + ", KEEPALIVE_INTERVAL = " + KEEPALIVE_INTERVAL + ")"); + entitiesToCreate = NUM_ENTITIES; + Script.update.connect(spawnEntities); + } + + function spawnEntities (dt) { + if (entitiesToCreate <= 0) { + Script.update.disconnect(spawnEntities); + print("Finished spawning entities"); + } + else if ((spawnTimer -= dt) < 0.0){ + spawnTimer = ENTITY_SPAWN_INTERVAL; + + var n = Math.min(entitiesToCreate, ENTITY_SPAWN_LIMIT); + print("Spawning " + n + " entities (" + (entitiesSpawned += n) + ")"); + + entitiesToCreate -= n; + + var center = MyAvatar.position; + center.y -= Y_OFFSET; + + for (; n > 0; --n) { + entities.push(makeEntity({ + type: "Shape", + shape: SHAPES[n % SHAPES.length], + name: TEST_ENTITY_NAME, + position: randomPosition(center, RADIUS), + color: randomColor(), + dimensions: randomDimensions(), + lifetime: ENTITY_LIFETIME + })); + } + } + } + + function despawnEntities () { + print("despawning entities"); + entities.forEach(function (entity) { + entity.destroy(); + }); + entities = []; + } + + // Runs the following entity updates: + // a) refreshes the timeout interval every KEEPALIVE_INTERVAL seconds, and + // b) re-randomizes its position every UPDATE_INTERVAL seconds. + // This should be sufficient to crash the client until the entity tree bug is fixed (and thereafter if it shows up again). + function updateEntities (dt) { + var updateLifetime = ((keepAliveTimer -= dt) < 0.0) ? ((keepAliveTimer = KEEPALIVE_INTERVAL), true) : false; + var updateProperties = ((updateTimer -= dt) < 0.0) ? ((updateTimer = UPDATE_INTERVAL), true) : false; + + if (updateLifetime || updateProperties) { + var center = MyAvatar.position; + center.y -= Y_OFFSET; + + entities.forEach((updateLifetime && updateProperties && function (entity) { + entity.update({ + lifetime: entity.getAge() + ENTITY_LIFETIME, + position: randomPosition(center, RADIUS) + }); + }) || (updateLifetime && function (entity) { + entity.update({ + lifetime: entity.getAge() + ENTITY_LIFETIME + }); + }) || (updateProperties && function (entity) { + entity.update({ + position: randomPosition(center, RADIUS) + }); + }) || null, this); + } + } + + function init () { + Script.update.disconnect(init); + clear(); + createEntities(); + Script.update.connect(updateEntities); + Script.scriptEnding.connect(despawnEntities); + } + + Script.update.connect(init); +}; \ No newline at end of file diff --git a/scripts/developer/tests/primitivesTest.js b/scripts/developer/tests/primitivesTest.js new file mode 100644 index 0000000000..e401963a83 --- /dev/null +++ b/scripts/developer/tests/primitivesTest.js @@ -0,0 +1,24 @@ +// entityEditStressTest.js +// +// Created by Seiji Emery on 8/31/15 +// Copyright 2015 High Fidelity, Inc. +// +// Stress tests the client + server-side entity trees by spawning huge numbers of entities in +// close proximity to your avatar and updating them continuously (ie. applying position edits), +// with the intent of discovering crashes and other bugs related to the entity, scripting, +// rendering, networking, and/or physics subsystems. +// +// This script was originally created to find + diagnose an a clientside crash caused by improper +// locking of the entity tree, but can be reused for other purposes. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("./entitySpawnTool.js"); + + +ENTITY_SPAWNER({ + updateInterval: 2.0 +}) + diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index efe7e6cc65..4a3b5a14a4 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -25,6 +25,7 @@ var ICON_FOR_TYPE = { Box: "V", Sphere: "n", + Shape: "n", ParticleEffect: "", Model: "", Web: "q", @@ -403,6 +404,10 @@ var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); + var elShapeSections = document.querySelectorAll(".shape-section"); + allSections.push(elShapeSections); + var elShape = document.getElementById("property-shape"); + var elLightSections = document.querySelectorAll(".light-section"); allSections.push(elLightSections); var elLightSpotLight = document.getElementById("property-light-spot-light"); @@ -666,7 +671,18 @@ elHyperlinkSections[i].style.display = 'table'; } - if (properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { + if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { + for (var i = 0; i < elShapeSections.length; i++) { + elShapeSections[i].style.display = 'table'; + } + } else { + for (var i = 0; i < elShapeSections.length; i++) { + console.log("Hiding shape section " + elShapeSections[i]) + elShapeSections[i].style.display = 'none'; + } + } + + if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { for (var i = 0; i < elColorSections.length; i++) { elColorSections[i].style.display = 'table'; } @@ -958,6 +974,8 @@ elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); + elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); @@ -1344,6 +1362,36 @@
+
+ M +
+ +
+ + +
+
+ + +
+ -
M
diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index c0e21276d8..792ef7d9c6 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include #include @@ -155,7 +155,7 @@ int main(int argc, char** argv) { QFile file(getTestResourceDir() + "packet.bin"); if (!file.open(QIODevice::ReadOnly)) return -1; QByteArray packet = file.readAll(); - EntityItemPointer item = BoxEntityItem::factory(EntityItemID(), EntityItemProperties()); + EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties()); ReadBitstreamToTreeParams params; params.bitstreamVersion = 33; From 180f4ba4f509e2c70d6f86905d5e4c49ecd704c9 Mon Sep 17 00:00:00 2001 From: Geenz Date: Sun, 22 May 2016 21:02:28 -0400 Subject: [PATCH 215/264] Tweaked light attenuation formula some more. Keeping the ulightvec parameter for now - I want to revisit this later. --- libraries/model/src/model/Light.slh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 46eb352d16..de15e50908 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -106,9 +106,11 @@ float evalLightAttenuation(Light l, float d, vec3 ulightvec) { float denom = d / radius + 1.0; float attenuation = 1.0 / (denom * denom); + float cutoff = getLightCutoffRadius(l); + // "Fade" the edges of light sources to make things look a bit more attractive. // Note: this tends to look a bit odd at lower exponents. - attenuation *= clamp(0, 1, mix(0, 1, getLightCutoffSquareRadius(l) - dot(ulightvec, ulightvec))); + attenuation *= min(1, max(0, -(d - cutoff))); return attenuation; } From ff3fca3dc345c048009fc8ba03263850734aef9f Mon Sep 17 00:00:00 2001 From: Geenz Date: Sun, 22 May 2016 21:50:48 -0400 Subject: [PATCH 216/264] Remove no longer necessary light vector parameter. --- libraries/model/src/model/Light.slh | 6 ++---- libraries/render-utils/src/point_light.slf | 2 +- libraries/render-utils/src/spot_light.slf | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index de15e50908..7cb745ff53 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -98,10 +98,8 @@ float getLightShowContour(Light l) { return l._control.w; } -// Light is the light source its self, d is the light's distance, and ulightvec is the unnormalized light vector. -// Note that ulightvec should be calculated as light position - fragment position. -// Additionally, length(light position) != dot(light position, light position). -float evalLightAttenuation(Light l, float d, vec3 ulightvec) { +// Light is the light source its self, d is the light's distance calculated as length(unnormalized light vector). +float evalLightAttenuation(Light l, float d) { float radius = getLightRadius(l); float denom = d / radius + 1.0; float attenuation = 1.0 / (denom * denom); diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index 581aa8c250..8c9ff2c8ad 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -67,7 +67,7 @@ void main(void) { vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance, fragLightVec); + float radialAttenuation = evalLightAttenuation(light, fragLightDistance); // Final Lighting color vec3 fragColor = (shading.w * frag.diffuse + shading.xyz); diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 1cb6ebb878..63e87e95c0 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -74,7 +74,7 @@ void main(void) { vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance, fragLightVec); + float radialAttenuation = evalLightAttenuation(light, fragLightDistance4); float angularAttenuation = evalLightSpotAttenuation(light, cosSpotAngle); // Final Lighting color From 2c703e963cdb9780c241381948806416deffdd6a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 22 May 2016 17:41:50 -0700 Subject: [PATCH 217/264] More shapes --- .../src/RenderableShapeEntityItem.cpp | 4 +- libraries/entities/src/ShapeEntityItem.cpp | 2 +- libraries/entities/src/ShapeEntityItem.h | 2 +- libraries/render-utils/src/GeometryCache.cpp | 651 +++++++++--------- libraries/render-utils/src/GeometryCache.h | 4 +- scripts/system/html/entityProperties.html | 6 +- tests/gpu-test/CMakeLists.txt | 4 +- tests/gpu-test/src/TestHelpers.cpp | 20 + tests/gpu-test/src/TestHelpers.h | 33 + tests/gpu-test/src/TestInstancedShapes.cpp | 84 +++ tests/gpu-test/src/TestInstancedShapes.h | 23 + tests/gpu-test/src/TestShapes.cpp | 48 ++ tests/gpu-test/src/TestShapes.h | 22 + tests/gpu-test/src/TestWindow.cpp | 180 +++++ tests/gpu-test/src/TestWindow.h | 53 ++ tests/gpu-test/src/main.cpp | 539 +++------------ 16 files changed, 895 insertions(+), 780 deletions(-) create mode 100644 tests/gpu-test/src/TestHelpers.cpp create mode 100644 tests/gpu-test/src/TestHelpers.h create mode 100644 tests/gpu-test/src/TestInstancedShapes.cpp create mode 100644 tests/gpu-test/src/TestInstancedShapes.h create mode 100644 tests/gpu-test/src/TestShapes.cpp create mode 100644 tests/gpu-test/src/TestShapes.h create mode 100644 tests/gpu-test/src/TestWindow.cpp create mode 100644 tests/gpu-test/src/TestWindow.h diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 7d30b7a47c..c93ae252e3 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -30,7 +30,7 @@ static GeometryCache::Shape MAPPING[entity::NUM_SHAPES] = { GeometryCache::Cube, GeometryCache::Sphere, GeometryCache::Tetrahedron, - GeometryCache::Octahetron, + GeometryCache::Octahedron, GeometryCache::Dodecahedron, GeometryCache::Icosahedron, GeometryCache::Torus, @@ -93,7 +93,7 @@ void RenderableShapeEntityItem::render(RenderArgs* args) { if (!success) { return; } - if (_shape != entity::Cube) { + if (_shape == entity::Sphere) { modelTransform.postScale(SPHERE_ENTITY_SCALE); } batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index a24c7e1df5..2527dedab2 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -27,7 +27,7 @@ namespace entity { "Cube", "Sphere", "Tetrahedron", - "Octahetron", + "Octahedron", "Dodecahedron", "Icosahedron", "Torus", diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index f3cb2abd66..2ae4ae2ca1 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -19,7 +19,7 @@ namespace entity { Cube, Sphere, Tetrahedron, - Octahetron, + Octahedron, Dodecahedron, Icosahedron, Torus, diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 6852d17882..02aca4216e 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -51,8 +51,8 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); -static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT16; -static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint16); +static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; +static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32); void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) { vertexBuffer->append(vertices); @@ -112,102 +112,7 @@ void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count } } -// The golden ratio -static const float PHI = 1.61803398874f; - -const VertexVector& icosahedronVertices() { - static const float a = 1; - static const float b = PHI / 2.0f; - - static const VertexVector vertices{ // - vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), // - vec3(0, b, a), vec3(b, a, 0), vec3(-b, a, 0), // - vec3(0, b, a), vec3(-a, 0, b), vec3(0, -b, a), // - vec3(0, b, a), vec3(0, -b, a), vec3(a, 0, b), // - vec3(0, b, -a), vec3(a, 0, -b), vec3(0, -b, -a),// - vec3(0, b, -a), vec3(0, -b, -a), vec3(-a, 0, -b), // - vec3(0, -b, a), vec3(-b, -a, 0), vec3(b, -a, 0), // - vec3(0, -b, -a), vec3(b, -a, 0), vec3(-b, -a, 0), // - vec3(-b, a, 0), vec3(-a, 0, -b), vec3(-a, 0, b), // - vec3(-b, -a, 0), vec3(-a, 0, b), vec3(-a, 0, -b), // - vec3(b, a, 0), vec3(a, 0, b), vec3(a, 0, -b), // - vec3(b, -a, 0), vec3(a, 0, -b), vec3(a, 0, b), // - vec3(0, b, a), vec3(-b, a, 0), vec3(-a, 0, b), // - vec3(0, b, a), vec3(a, 0, b), vec3(b, a, 0), // - vec3(0, b, -a), vec3(-a, 0, -b), vec3(-b, a, 0), // - vec3(0, b, -a), vec3(b, a, 0), vec3(a, 0, -b), // - vec3(0, -b, -a), vec3(-b, -a, 0), vec3(-a, 0, -b), // - vec3(0, -b, -a), vec3(a, 0, -b), vec3(b, -a, 0), // - vec3(0, -b, a), vec3(-a, 0, b), vec3(-b, -a, 0), // - vec3(0, -b, a), vec3(b, -a, 0), vec3(a, 0, b) - }; // - return vertices; -} - -const VertexVector& tetrahedronVertices() { - static const auto A = vec3(1, 1, 1); - static const auto B = vec3(1, -1, -1); - static const auto C = vec3(-1, 1, -1); - static const auto D = vec3(-1, -1, 1); - static const VertexVector vertices{ - A, B, C, - D, B, A, - C, D, A, - C, B, D, - }; - return vertices; -} - -static const size_t TESSELTATION_MULTIPLIER = 4; static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3; -static const size_t VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER = 2; - - -VertexVector tesselate(const VertexVector& startingTriangles, int count) { - VertexVector triangles = startingTriangles; - if (0 != (triangles.size() % 3)) { - throw std::runtime_error("Bad number of vertices for tesselation"); - } - - for (size_t i = 0; i < triangles.size(); ++i) { - triangles[i] = glm::normalize(triangles[i]); - } - - VertexVector newTriangles; - while (count) { - newTriangles.clear(); - // Tesselation takes one triangle and makes it into 4 triangles - // See https://en.wikipedia.org/wiki/Space-filling_tree#/media/File:Space_Filling_Tree_Tri_iter_1_2_3.png - newTriangles.reserve(triangles.size() * TESSELTATION_MULTIPLIER); - for (size_t i = 0; i < triangles.size(); i += VERTICES_PER_TRIANGLE) { - const vec3& a = triangles[i]; - const vec3& b = triangles[i + 1]; - const vec3& c = triangles[i + 2]; - vec3 ab = glm::normalize(a + b); - vec3 bc = glm::normalize(b + c); - vec3 ca = glm::normalize(c + a); - - newTriangles.push_back(a); - newTriangles.push_back(ab); - newTriangles.push_back(ca); - - newTriangles.push_back(b); - newTriangles.push_back(bc); - newTriangles.push_back(ab); - - newTriangles.push_back(c); - newTriangles.push_back(ca); - newTriangles.push_back(bc); - - newTriangles.push_back(ab); - newTriangles.push_back(bc); - newTriangles.push_back(ca); - } - triangles.swap(newTriangles); - --count; - } - return triangles; -} size_t GeometryCache::getShapeTriangleCount(Shape shape) { return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE; @@ -221,6 +126,324 @@ size_t GeometryCache::getCubeTriangleCount() { return getShapeTriangleCount(Cube); } +using Index = uint32_t; +using IndexPair = uint64_t; +using IndexPairs = std::unordered_set; + +template +using Face = std::array; + +template +using FaceVector = std::vector>; + +template +struct Solid { + VertexVector vertices; + FaceVector faces; + + Solid& fitDimension(float newMaxDimension) { + float maxDimension = 0; + for (const auto& vertex : vertices) { + maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z)); + } + float multiplier = newMaxDimension / maxDimension; + for (auto& vertex : vertices) { + vertex *= multiplier; + } + return *this; + } + + vec3 getFaceNormal(size_t faceIndex) const { + vec3 result; + const auto& face = faces[faceIndex]; + for (size_t i = 0; i < N; ++i) { + result += vertices[face[i]]; + } + result /= N; + return glm::normalize(result); + } +}; + +template +static size_t triangulatedFaceTriangleCount() { + return N - 2; +} + +template +static size_t triangulatedFaceIndexCount() { + return triangulatedFaceTriangleCount() * VERTICES_PER_TRIANGLE; +} + +static IndexPair indexToken(Index a, Index b) { + if (a > b) { + std::swap(a, b); + } + return (((IndexPair)a) << 32) | ((IndexPair)b); +} + +static Solid<3> tesselate(Solid<3> solid, int count) { + float length = glm::length(solid.vertices[0]); + for (int i = 0; i < count; ++i) { + Solid<3> result { solid.vertices, {} }; + result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3); + for (size_t f = 0; f < solid.faces.size(); ++f) { + Index baseVertex = (Index)result.vertices.size(); + const Face<3>& oldFace = solid.faces[f]; + const vec3& a = solid.vertices[oldFace[0]]; + const vec3& b = solid.vertices[oldFace[1]]; + const vec3& c = solid.vertices[oldFace[2]]; + vec3 ab = glm::normalize(a + b) * length; + vec3 bc = glm::normalize(b + c) * length; + vec3 ca = glm::normalize(c + a) * length; + result.vertices.push_back(ab); + result.vertices.push_back(bc); + result.vertices.push_back(ca); + result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } }); + result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); + } + solid = result; + } + return solid; +} + +template +void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + VertexVector vertices; + IndexVector solidIndices, wireIndices; + IndexPairs wireSeenIndices; + + size_t faceCount = shape.faces.size(); + size_t faceIndexCount = triangulatedFaceIndexCount(); + + vertices.reserve(N * faceCount * 2); + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& face = shape.faces[f]; + // Compute the face normal + vec3 faceNormal = shape.getFaceNormal(f); + + // Create the vertices for the face + for (Index i = 0; i < N; ++i) { + Index originalIndex = face[i]; + vertices.push_back(shape.vertices[originalIndex]); + vertices.push_back(faceNormal); + } + + // Create the wire indices for unseen edges + for (Index i = 0; i < N; ++i) { + Index a = i; + Index b = (i + 1) % N; + auto token = indexToken(face[a], face[b]); + if (0 == wireSeenIndices.count(token)) { + wireSeenIndices.insert(token); + wireIndices.push_back(a + baseVertex); + wireIndices.push_back(b + baseVertex); + } + } + + // Create the solid face indices + for (Index i = 0; i < N - 2; ++i) { + solidIndices.push_back(0 + baseVertex); + solidIndices.push_back(i + 1 + baseVertex); + solidIndices.push_back(i + 2 + baseVertex); + } + baseVertex += (Index)N; + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} + +template +void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); + + VertexVector vertices; + vertices.reserve(shape.vertices.size() * 2); + for (const auto& vertex : shape.vertices) { + vertices.push_back(vertex); + vertices.push_back(vertex); + } + + IndexVector solidIndices, wireIndices; + IndexPairs wireSeenIndices; + + size_t faceCount = shape.faces.size(); + size_t faceIndexCount = triangulatedFaceIndexCount(); + + solidIndices.reserve(faceIndexCount * faceCount); + + for (size_t f = 0; f < faceCount; ++f) { + const Face& face = shape.faces[f]; + // Create the wire indices for unseen edges + for (Index i = 0; i < N; ++i) { + Index a = face[i]; + Index b = face[(i + 1) % N]; + auto token = indexToken(a, b); + if (0 == wireSeenIndices.count(token)) { + wireSeenIndices.insert(token); + wireIndices.push_back(a + baseVertex); + wireIndices.push_back(b + baseVertex); + } + } + + // Create the solid face indices + for (Index i = 0; i < N - 2; ++i) { + solidIndices.push_back(face[i] + baseVertex); + solidIndices.push_back(face[i + 1] + baseVertex); + solidIndices.push_back(face[i + 2] + baseVertex); + } + } + + shapeData.setupVertices(vertexBuffer, vertices); + shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); +} + +// The golden ratio +static const float PHI = 1.61803398874f; + +static const Solid<3>& tetrahedron() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(1, -1, -1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(-1, -1, 1); + static const Solid<3> TETRAHEDRON = Solid<3>{ + { A, B, C, D }, + FaceVector<3>{ + Face<3> { { 0, 1, 2 } }, + Face<3> { { 3, 1, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 2, 1, 3 } }, + } + }.fitDimension(0.5f); + return TETRAHEDRON; +} + +static const Solid<4>& cube() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(-1, 1, 1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(1, 1, -1); + static const Solid<4> CUBE = Solid<4>{ + { A, B, C, D, -A, -B, -C, -D }, + FaceVector<4>{ + Face<4> { { 3, 2, 1, 0 } }, + Face<4> { { 0, 1, 7, 6 } }, + Face<4> { { 1, 2, 4, 7 } }, + Face<4> { { 2, 3, 5, 4 } }, + Face<4> { { 3, 0, 6, 5 } }, + Face<4> { { 4, 5, 6, 7 } }, + } + }.fitDimension(0.5f); + return CUBE; +} + +static const Solid<3>& octahedron() { + static const auto A = vec3(0, 1, 0); + static const auto B = vec3(0, -1, 0); + static const auto C = vec3(0, 0, 1); + static const auto D = vec3(0, 0, -1); + static const auto E = vec3(1, 0, 0); + static const auto F = vec3(-1, 0, 0); + static const Solid<3> OCTAHEDRON = Solid<3>{ + { A, B, C, D, E, F}, + FaceVector<3> { + Face<3> { { 0, 2, 4, } }, + Face<3> { { 0, 4, 3, } }, + Face<3> { { 0, 3, 5, } }, + Face<3> { { 0, 5, 2, } }, + Face<3> { { 1, 4, 2, } }, + Face<3> { { 1, 3, 4, } }, + Face<3> { { 1, 5, 3, } }, + Face<3> { { 1, 2, 5, } }, + } + }.fitDimension(0.5f); + return OCTAHEDRON; +} + +static const Solid<5>& dodecahedron() { + static const float P = PHI; + static const float IP = 1.0f / PHI; + static const vec3 A = vec3(IP, P, 0); + static const vec3 B = vec3(-IP, P, 0); + static const vec3 C = vec3(-1, 1, 1); + static const vec3 D = vec3(0, IP, P); + static const vec3 E = vec3(1, 1, 1); + static const vec3 F = vec3(1, 1, -1); + static const vec3 G = vec3(-1, 1, -1); + static const vec3 H = vec3(-P, 0, IP); + static const vec3 I = vec3(0, -IP, P); + static const vec3 J = vec3(P, 0, IP); + + static const Solid<5> DODECAHEDRON = Solid<5>{ + { + A, B, C, D, E, F, G, H, I, J, + -A, -B, -C, -D, -E, -F, -G, -H, -I, -J, + }, + FaceVector<5> { + Face<5> { { 0, 1, 2, 3, 4 } }, + Face<5> { { 0, 5, 18, 6, 1 } }, + Face<5> { { 1, 6, 19, 7, 2 } }, + Face<5> { { 2, 7, 15, 8, 3 } }, + Face<5> { { 3, 8, 16, 9, 4 } }, + Face<5> { { 4, 9, 17, 5, 0 } }, + Face<5> { { 14, 13, 12, 11, 10 } }, + Face<5> { { 11, 16, 8, 15, 10 } }, + Face<5> { { 12, 17, 9, 16, 11 } }, + Face<5> { { 13, 18, 5, 17, 12 } }, + Face<5> { { 14, 19, 6, 18, 13 } }, + Face<5> { { 10, 15, 7, 19, 14 } }, + } + }.fitDimension(0.5f); + return DODECAHEDRON; +} + +static const Solid<3>& icosahedron() { + static const float N = 1.0f / PHI; + static const float P = 1.0f; + static const auto A = vec3(N, P, 0); + static const auto B = vec3(-N, P, 0); + static const auto C = vec3(0, N, P); + static const auto D = vec3(P, 0, N); + static const auto E = vec3(P, 0, -N); + static const auto F = vec3(0, N, -P); + + static const Solid<3> ICOSAHEDRON = Solid<3> { + { + A, B, C, D, E, F, + -A, -B, -C, -D, -E, -F, + }, + FaceVector<3> { + Face<3> { { 1, 2, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 3, 4, 0 } }, + Face<3> { { 4, 5, 0 } }, + Face<3> { { 5, 1, 0 } }, + + Face<3> { { 1, 10, 2 } }, + Face<3> { { 11, 2, 10 } }, + Face<3> { { 2, 11, 3 } }, + Face<3> { { 7, 3, 11 } }, + Face<3> { { 3, 7, 4 } }, + Face<3> { { 8, 4, 7 } }, + Face<3> { { 4, 8, 5 } }, + Face<3> { { 9, 5, 8 } }, + Face<3> { { 5, 9, 1 } }, + Face<3> { { 10, 1, 9 } }, + + Face<3> { { 8, 7, 6 } }, + Face<3> { { 9, 8, 6 } }, + Face<3> { { 10, 9, 6 } }, + Face<3> { { 11, 10, 6 } }, + Face<3> { { 7, 11, 6 } }, + } + }.fitDimension(0.5f); + return ICOSAHEDRON; +} // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements @@ -230,229 +453,28 @@ size_t GeometryCache::getCubeTriangleCount() { void GeometryCache::buildShapes() { auto vertexBuffer = std::make_shared(); auto indexBuffer = std::make_shared(); - size_t startingIndex = 0; - // Cube - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Cube]; - VertexVector vertices; - // front - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(0, 0, 1)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(0, 0, 1)); - - // right - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(1, 0, 0)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(1, 0, 0)); - - // top - vertices.push_back(vec3(1, 1, 1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(0, 1, 0)); - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(0, 1, 0)); - - // left - vertices.push_back(vec3(-1, 1, 1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(-1, 0, 0)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(-1, 0, 0)); - - // bottom - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(1, -1, 1)); - vertices.push_back(vec3(0, -1, 0)); - vertices.push_back(vec3(-1, -1, 1)); - vertices.push_back(vec3(0, -1, 0)); - - // back - vertices.push_back(vec3(1, -1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(-1, -1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(-1, 1, -1)); - vertices.push_back(vec3(0, 0, -1)); - vertices.push_back(vec3(1, 1, -1)); - vertices.push_back(vec3(0, 0, -1)); - - static const size_t VERTEX_FORMAT_SIZE = 2; - static const size_t VERTEX_OFFSET = 0; - - for (size_t i = 0; i < vertices.size(); ++i) { - auto vertexIndex = i; - // Make a unit cube by having the vertices (at index N) - // while leaving the normals (at index N + 1) alone - if (VERTEX_OFFSET == vertexIndex % VERTEX_FORMAT_SIZE) { - vertices[vertexIndex] *= 0.5f; - } - } - shapeData.setupVertices(_shapeVertices, vertices); - - IndexVector indices{ - 0, 1, 2, 2, 3, 0, // front - 4, 5, 6, 6, 7, 4, // right - 8, 9, 10, 10, 11, 8, // top - 12, 13, 14, 14, 15, 12, // left - 16, 17, 18, 18, 19, 16, // bottom - 20, 21, 22, 22, 23, 20 // back - }; - for (auto& index : indices) { - index += (uint16_t)startingIndex; - } - - IndexVector wireIndices{ - 0, 1, 1, 2, 2, 3, 3, 0, // front - 20, 21, 21, 22, 22, 23, 23, 20, // back - 0, 23, 1, 22, 2, 21, 3, 20 // sides - }; - - for (size_t i = 0; i < wireIndices.size(); ++i) { - indices[i] += (uint16_t)startingIndex; - } - - shapeData.setupIndices(_shapeIndices, indices, wireIndices); - } - + setupFlatShape(_shapes[Cube], cube(), _shapeVertices, _shapeIndices); // Tetrahedron - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Tetrahedron]; - size_t vertexCount = 4; - VertexVector vertices; - { - VertexVector originalVertices = tetrahedronVertices(); - vertexCount = originalVertices.size(); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - vec3 faceNormal; - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - faceNormal += originalVertices[vertexIndex]; - } - faceNormal = glm::normalize(faceNormal); - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(originalVertices[vertexIndex]); - vertices.push_back(faceNormal); - } - } - } - shapeData.setupVertices(_shapeVertices, vertices); - - IndexVector indices; - for (size_t i = 0; i < vertexCount; i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - - IndexVector wireIndices{ - 0, 1, 1, 2, 2, 0, - 0, 3, 1, 3, 2, 3, - }; - - for (size_t i = 0; i < wireIndices.size(); ++i) { - wireIndices[i] += (uint16_t)startingIndex; - } - - shapeData.setupIndices(_shapeIndices, indices, wireIndices); - } - + setupFlatShape(_shapes[Tetrahedron], tetrahedron(), _shapeVertices, _shapeIndices); + // Icosahedron + setupFlatShape(_shapes[Icosahedron], icosahedron(), _shapeVertices, _shapeIndices); + // Octahedron + setupFlatShape(_shapes[Octahedron], octahedron(), _shapeVertices, _shapeIndices); + // Dodecahedron + setupFlatShape(_shapes[Dodecahedron], dodecahedron(), _shapeVertices, _shapeIndices); + // Sphere // FIXME this uses way more vertices than required. Should find a way to calculate the indices // using shared vertices for better vertex caching - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Sphere]; - VertexVector vertices; - IndexVector indices; - { - VertexVector originalVertices = tesselate(icosahedronVertices(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { - auto triangleStartIndex = i; - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - const auto& vertex = originalVertices[i + j]; - // Spheres use the same values for vertices and normals - vertices.push_back(vertex); - vertices.push_back(vertex); - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - } - - shapeData.setupVertices(_shapeVertices, vertices); - // FIXME don't use solid indices for wire drawing. - shapeData.setupIndices(_shapeIndices, indices, indices); - } - - // Icosahedron - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; - { - ShapeData& shapeData = _shapes[Icosahedron]; - - VertexVector vertices; - IndexVector indices; - { - const VertexVector& originalVertices = icosahedronVertices(); - vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); - for (size_t i = 0; i < originalVertices.size(); i += 3) { - auto triangleStartIndex = i; - vec3 faceNormal; - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - faceNormal += originalVertices[vertexIndex]; - } - faceNormal = glm::normalize(faceNormal); - for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { - auto triangleVertexIndex = j; - auto vertexIndex = triangleStartIndex + triangleVertexIndex; - vertices.push_back(originalVertices[vertexIndex]); - vertices.push_back(faceNormal); - indices.push_back((uint16_t)(vertexIndex + startingIndex)); - } - } - } - - shapeData.setupVertices(_shapeVertices, vertices); - // FIXME don't use solid indices for wire drawing. - shapeData.setupIndices(_shapeIndices, indices, indices); - } + Solid<3> sphere = icosahedron(); + sphere = tesselate(sphere, ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); + sphere.fitDimension(1.0f); + setupSmoothShape(_shapes[Sphere], sphere, _shapeVertices, _shapeIndices); // Line - startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; { + Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); ShapeData& shapeData = _shapes[Line]; shapeData.setupVertices(_shapeVertices, VertexVector{ vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), @@ -460,9 +482,8 @@ void GeometryCache::buildShapes() { }); IndexVector wireIndices; // Only two indices - wireIndices.push_back(0 + (uint16_t)startingIndex); - wireIndices.push_back(1 + (uint16_t)startingIndex); - + wireIndices.push_back(0 + baseVertex); + wireIndices.push_back(1 + baseVertex); shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 7fa543abe2..a2f79de029 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -121,8 +121,8 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) { seed); } +using IndexVector = std::vector; using VertexVector = std::vector; -using IndexVector = std::vector; /// Stores cached geometry. class GeometryCache : public Dependency { @@ -137,7 +137,7 @@ public: Cube, Sphere, Tetrahedron, - Octahetron, + Octahedron, Dodecahedron, Icosahedron, Torus, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 4a3b5a14a4..c611fc5b38 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1368,10 +1368,12 @@
diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index 4fc6143ff5..21ae9c5a99 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -4,4 +4,6 @@ AUTOSCRIBE_SHADER_LIB(gpu model render-utils) setup_hifi_project(Quick Gui OpenGL Script Widgets) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries(networking gl gpu gpu-gl procedural shared fbx model model-networking animation script-engine render render-utils ) -package_libraries_for_deployment() \ No newline at end of file +package_libraries_for_deployment() + +target_nsight() diff --git a/tests/gpu-test/src/TestHelpers.cpp b/tests/gpu-test/src/TestHelpers.cpp new file mode 100644 index 0000000000..75586da904 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.cpp @@ -0,0 +1,20 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TestHelpers.h" + +gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { + auto vs = gpu::Shader::createVertex(vertexShaderSrc); + auto fs = gpu::Shader::createPixel(fragmentShaderSrc); + auto shader = gpu::Shader::createProgram(vs, fs); + if (!gpu::Shader::makeProgram(*shader, bindings)) { + printf("Could not compile shader\n"); + exit(-1); + } + return shader; +} diff --git a/tests/gpu-test/src/TestHelpers.h b/tests/gpu-test/src/TestHelpers.h new file mode 100644 index 0000000000..fd8989f628 --- /dev/null +++ b/tests/gpu-test/src/TestHelpers.h @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +class GpuTestBase { +public: + virtual ~GpuTestBase() {} + virtual bool isReady() const { return true; } + virtual size_t getTestCount() const { return 1; } + virtual void renderTest(size_t test, RenderArgs* args) = 0; +}; + +uint32_t toCompactColor(const glm::vec4& color); +gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings); + diff --git a/tests/gpu-test/src/TestInstancedShapes.cpp b/tests/gpu-test/src/TestInstancedShapes.cpp new file mode 100644 index 0000000000..6a98ee58b9 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.cpp @@ -0,0 +1,84 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TestInstancedShapes.h" + +gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); + +static const size_t TYPE_COUNT = 4; +static const size_t ITEM_COUNT = 50; +static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; +static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; + +static GeometryCache::Shape SHAPE[TYPE_COUNT] = { + GeometryCache::Icosahedron, + GeometryCache::Cube, + GeometryCache::Sphere, + GeometryCache::Tetrahedron, +}; + +const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; + + +TestInstancedShapes::TestInstancedShapes() { + auto geometryCache = DependencyManager::get(); + colorBuffer = std::make_shared(); + + static const float ITEM_RADIUS = 20; + static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS }; + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + //indirectCommand._count + float startingInterval = ITEM_INTERVAL * i; + std::vector typeTransforms; + for (size_t j = 0; j < ITEM_COUNT; ++j) { + float theta = j * SHAPE_INTERVAL + startingInterval; + auto transform = glm::rotate(mat4(), theta, Vectors::UP); + transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); + transform = glm::translate(transform, ITEM_TRANSLATION); + transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); + typeTransforms.push_back(transform); + auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; + color /= 255.0f; + colors.push_back(color); + colorBuffer->append(toCompactColor(color)); + } + transforms.push_back(typeTransforms); + } +} + +void TestInstancedShapes::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch); + batch.setInputFormat(getInstancedSolidStreamFormat()); + for (size_t i = 0; i < TYPE_COUNT; ++i) { + GeometryCache::Shape shape = SHAPE[i]; + GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; + + std::string namedCall = __FUNCTION__ + std::to_string(i); + + //batch.addInstanceModelTransforms(transforms[i]); + for (size_t j = 0; j < ITEM_COUNT; ++j) { + batch.setModelTransform(transforms[i][j]); + batch.setupNamedCalls(namedCall, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData&) { + batch.setInputBuffer(gpu::Stream::COLOR, gpu::BufferView(colorBuffer, i * ITEM_COUNT * 4, colorBuffer->getSize(), COLOR_ELEMENT)); + shapeData.drawInstances(batch, ITEM_COUNT); + }); + } + + //for (size_t j = 0; j < ITEM_COUNT; ++j) { + // batch.setModelTransform(transforms[j + i * ITEM_COUNT]); + // shapeData.draw(batch); + //} + } +} + diff --git a/tests/gpu-test/src/TestInstancedShapes.h b/tests/gpu-test/src/TestInstancedShapes.h new file mode 100644 index 0000000000..b509a13e60 --- /dev/null +++ b/tests/gpu-test/src/TestInstancedShapes.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include "TestHelpers.h" + +class TestInstancedShapes : public GpuTestBase { + + std::vector> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + TestInstancedShapes(); + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestShapes.cpp b/tests/gpu-test/src/TestShapes.cpp new file mode 100644 index 0000000000..253d89cf61 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.cpp @@ -0,0 +1,48 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TestShapes.h" + +static const size_t TYPE_COUNT = 6; + +static GeometryCache::Shape SHAPE[TYPE_COUNT] = { + GeometryCache::Cube, + GeometryCache::Tetrahedron, + GeometryCache::Octahedron, + GeometryCache::Dodecahedron, + GeometryCache::Icosahedron, + GeometryCache::Sphere, +}; + +void TestShapes::renderTest(size_t testId, RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + auto geometryCache = DependencyManager::get(); + geometryCache->bindSimpleProgram(batch); + + // Render unlit cube + sphere + static auto startSecs = secTimestampNow(); + float seconds = secTimestampNow() - startSecs; + seconds /= 4.0f; + batch.setModelTransform(Transform()); + batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + + bool wire = (seconds - floorf(seconds) > 0.5f); + int shapeIndex = ((int)seconds) % TYPE_COUNT; + if (wire) { + geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + } else { + geometryCache->renderShape(batch, SHAPE[shapeIndex]); + } + + batch.setModelTransform(Transform().setScale(1.01f)); + batch._glColor4f(1, 1, 1, 1); + geometryCache->renderWireCube(batch); +} + + + diff --git a/tests/gpu-test/src/TestShapes.h b/tests/gpu-test/src/TestShapes.h new file mode 100644 index 0000000000..606d3a45f7 --- /dev/null +++ b/tests/gpu-test/src/TestShapes.h @@ -0,0 +1,22 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include "TestHelpers.h" + +class TestShapes : public GpuTestBase { + + std::vector> transforms; + std::vector colors; + gpu::BufferPointer colorBuffer; + gpu::BufferView instanceXfmView; +public: + void renderTest(size_t testId, RenderArgs* args) override; +}; + diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp new file mode 100644 index 0000000000..4fe25e989d --- /dev/null +++ b/tests/gpu-test/src/TestWindow.cpp @@ -0,0 +1,180 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "TestWindow.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef DEFERRED_LIGHTING +extern void initDeferredPipelines(render::ShapePlumber& plumber); +extern void initStencilPipeline(gpu::PipelinePointer& pipeline); +#endif + +TestWindow::TestWindow() { + setSurfaceType(QSurface::OpenGLSurface); + + + auto timer = new QTimer(this); + timer->setInterval(5); + connect(timer, &QTimer::timeout, [&] { draw(); }); + timer->start(); + + connect(qApp, &QCoreApplication::aboutToQuit, [this, timer] { + timer->stop(); + _aboutToQuit = true; + }); + +#ifdef DEFERRED_LIGHTING + _light->setType(model::Light::SUN); + _light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE); + _light->setIntensity(1.0f); + _light->setAmbientIntensity(0.5f); + _light->setColor(vec3(1.0f)); + _light->setPosition(vec3(1, 1, 1)); + _renderContext->args = _renderArgs; +#endif + + QSurfaceFormat format = getDefaultOpenGLSurfaceFormat(); + format.setOption(QSurfaceFormat::DebugContext); + //format.setSwapInterval(0); + setFormat(format); + _glContext.setFormat(format); + _glContext.create(); + _glContext.makeCurrent(this); + show(); +} + +void TestWindow::initGl() { + _glContext.makeCurrent(this); + gpu::Context::init(); + _renderArgs->_context = std::make_shared(); + _glContext.makeCurrent(this); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + resize(QSize(800, 600)); + + setupDebugLogger(this); +#ifdef DEFERRED_LIGHTING + auto deferredLightingEffect = DependencyManager::get(); + deferredLightingEffect->init(); + deferredLightingEffect->setGlobalLight(_light); + initDeferredPipelines(*_shapePlumber); + initStencilPipeline(_opaquePipeline); +#endif +} + +void TestWindow::resizeWindow(const QSize& size) { + _size = size; + _renderArgs->_viewport = ivec4(0, 0, _size.width(), _size.height()); + auto fboCache = DependencyManager::get(); + if (fboCache) { + fboCache->setFrameBufferSize(_size); + } +} + +void TestWindow::beginFrame() { + _renderArgs->_context->syncCache(); + +#ifdef DEFERRED_LIGHTING + auto deferredLightingEffect = DependencyManager::get(); + deferredLightingEffect->prepare(_renderArgs); +#else + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); + batch.clearDepthFramebuffer(1e4); + batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); + }); +#endif + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.setViewportTransform(_renderArgs->_viewport); + batch.setStateScissorRect(_renderArgs->_viewport); + batch.setProjectionTransform(_projectionMatrix); + }); +} + +void TestWindow::endFrame() { +#ifdef DEFERRED_LIGHTING + RenderArgs* args = _renderContext->args; + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + auto deferredFboColorDepthStencil = DependencyManager::get()->getDeferredFramebufferDepthColor(); + batch.setViewportTransform(args->_viewport); + batch.setStateScissorRect(args->_viewport); + batch.setFramebuffer(deferredFboColorDepthStencil); + batch.setPipeline(_opaquePipeline); + batch.draw(gpu::TRIANGLE_STRIP, 4); + batch.setResourceTexture(0, nullptr); + }); + + auto deferredLightingEffect = DependencyManager::get(); + deferredLightingEffect->render(_renderContext); + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "blit"); + // Blit to screen + auto framebufferCache = DependencyManager::get(); + auto framebuffer = framebufferCache->getLightingFramebuffer(); + batch.blit(framebuffer, _renderArgs->_viewport, nullptr, _renderArgs->_viewport); + }); +#endif + + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.resetStages(); + }); + _glContext.swapBuffers(this); +} + +void TestWindow::draw() { + if (_aboutToQuit) { + return; + } + + // Attempting to draw before we're visible and have a valid size will + // produce GL errors. + if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { + return; + } + + if (!_glContext.makeCurrent(this)) { + return; + } + + static std::once_flag once; + std::call_once(once, [&] { initGl(); }); + beginFrame(); + + renderFrame(); + + endFrame(); +} + +void TestWindow::resizeEvent(QResizeEvent* ev) { + resizeWindow(ev->size()); + float fov_degrees = 60.0f; + float aspect_ratio = (float)_size.width() / _size.height(); + float near_clip = 0.1f; + float far_clip = 1000.0f; + _projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); +} diff --git a/tests/gpu-test/src/TestWindow.h b/tests/gpu-test/src/TestWindow.h new file mode 100644 index 0000000000..b7f8df48f5 --- /dev/null +++ b/tests/gpu-test/src/TestWindow.h @@ -0,0 +1,53 @@ +// +// Created by Bradley Austin Davis on 2016/05/16 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define DEFERRED_LIGHTING + +class TestWindow : public QWindow { +protected: + QOpenGLContextWrapper _glContext; + QSize _size; + glm::mat4 _projectionMatrix; + bool _aboutToQuit { false }; + +#ifdef DEFERRED_LIGHTING + // Prepare the ShapePipelines + render::ShapePlumberPointer _shapePlumber { std::make_shared() }; + render::RenderContextPointer _renderContext { std::make_shared() }; + gpu::PipelinePointer _opaquePipeline; + model::LightPointer _light { std::make_shared() }; +#endif + + RenderArgs* _renderArgs { new RenderArgs() }; + + TestWindow(); + virtual void initGl(); + virtual void renderFrame() = 0; + +private: + void resizeWindow(const QSize& size); + + void beginFrame(); + void endFrame(); + void draw(); + void resizeEvent(QResizeEvent* ev) override; +}; + diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index b80539b33a..e672fe3c86 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -42,238 +42,64 @@ #include #include +#include #include +#include +#include +#include +#include -#include "unlit_frag.h" -#include "unlit_vert.h" +#include +#include -class RateCounter { - std::vector times; - QElapsedTimer timer; -public: - RateCounter() { - timer.start(); - } +#include +#include +#include +#include +#include +#include +#include +#include - void reset() { - times.clear(); - } +#include "TestWindow.h" +#include "TestInstancedShapes.h" +#include "TestShapes.h" - unsigned int count() const { - return (unsigned int)times.size() - 1; - } - - float elapsed() const { - if (times.size() < 1) { - return 0.0f; - } - float elapsed = *times.rbegin() - *times.begin(); - return elapsed; - } - - void increment() { - times.push_back(timer.elapsed() / 1000.0f); - } - - float rate() const { - if (elapsed() == 0.0f) { - return NAN; - } - return (float) count() / elapsed(); - } -}; - -uint32_t toCompactColor(const glm::vec4& color); +using namespace render; -const char* VERTEX_SHADER = R"SHADER( - -layout(location = 0) in vec4 inPosition; -layout(location = 3) in vec2 inTexCoord0; - -struct TransformObject { - mat4 _model; - mat4 _modelInverse; -}; - -layout(location=15) in ivec2 _drawCallInfo; - -uniform samplerBuffer transformObjectBuffer; - -TransformObject getTransformObject() { - int offset = 8 * _drawCallInfo.x; - TransformObject object; - object._model[0] = texelFetch(transformObjectBuffer, offset); - object._model[1] = texelFetch(transformObjectBuffer, offset + 1); - object._model[2] = texelFetch(transformObjectBuffer, offset + 2); - object._model[3] = texelFetch(transformObjectBuffer, offset + 3); - - object._modelInverse[0] = texelFetch(transformObjectBuffer, offset + 4); - object._modelInverse[1] = texelFetch(transformObjectBuffer, offset + 5); - object._modelInverse[2] = texelFetch(transformObjectBuffer, offset + 6); - object._modelInverse[3] = texelFetch(transformObjectBuffer, offset + 7); - - return object; -} - -struct TransformCamera { - mat4 _view; - mat4 _viewInverse; - mat4 _projectionViewUntranslated; - mat4 _projection; - mat4 _projectionInverse; - vec4 _viewport; -}; - -layout(std140) uniform transformCameraBuffer { - TransformCamera _camera; -}; - -TransformCamera getTransformCamera() { - return _camera; -} - -// the interpolated normal -out vec2 _texCoord0; - -void main(void) { - _texCoord0 = inTexCoord0.st; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - { // transformModelToClipPos - vec4 eyeWAPos; - { // _transformModelToEyeWorldAlignedPos - highp mat4 _mv = obj._model; - _mv[3].xyz -= cam._viewInverse[3].xyz; - highp vec4 _eyeWApos = (_mv * inPosition); - eyeWAPos = _eyeWApos; - } - gl_Position = cam._projectionViewUntranslated * eyeWAPos; - } - -})SHADER"; - -const char* FRAGMENT_SHADER = R"SHADER( - -uniform sampler2D originalTexture; - -in vec2 _texCoord0; - -layout(location = 0) out vec4 _fragColor0; - -void main(void) { - //_fragColor0 = vec4(_texCoord0, 0.0, 1.0); - _fragColor0 = texture(originalTexture, _texCoord0); -} -)SHADER"; +using TestBuilder = std::function; +using TestBuilders = std::list; -gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { - auto vs = gpu::Shader::createVertex(vertexShaderSrc); - auto fs = gpu::Shader::createPixel(fragmentShaderSrc); - auto shader = gpu::Shader::createProgram(vs, fs); - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader\n"); - exit(-1); - } - return shader; -} +#define INTERACTIVE -float getSeconds(quint64 start = 0) { - auto usecs = usecTimestampNow() - start; - auto msecs = usecs / USECS_PER_MSEC; - float seconds = (float)msecs / MSECS_PER_SECOND; - return seconds; -} - -static const size_t TYPE_COUNT = 4; -static GeometryCache::Shape SHAPE[TYPE_COUNT] = { - GeometryCache::Icosahedron, - GeometryCache::Cube, - GeometryCache::Sphere, - GeometryCache::Tetrahedron, - //GeometryCache::Line, -}; - -gpu::Stream::FormatPointer& getInstancedSolidStreamFormat(); - -// Creates an OpenGL window that renders a simple unlit scene using the gpu library and GeometryCache -// Should eventually get refactored into something that supports multiple gpu backends. -class QTestWindow : public QWindow { - Q_OBJECT - - QOpenGLContextWrapper _qGlContext; - QSize _size; - - gpu::ContextPointer _context; - gpu::PipelinePointer _pipeline; - glm::mat4 _projectionMatrix; - RateCounter fps; - QTime _time; +class MyTestWindow : public TestWindow { + using Parent = TestWindow; + TestBuilders _testBuilders; + GpuTestBase* _currentTest { nullptr }; + size_t _currentTestId { 0 }; + size_t _currentMaxTests { 0 }; glm::mat4 _camera; + QTime _time; -protected: - void renderText(); - -private: - void resizeWindow(const QSize& size) { - _size = size; - } - -public: - QTestWindow() { - setSurfaceType(QSurface::OpenGLSurface); - - QSurfaceFormat format; - // Qt Quick may need a depth and stencil buffer. Always make sure these are available. - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - setGLFormatVersion(format); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setOption(QSurfaceFormat::DebugContext); - //format.setSwapInterval(0); - - setFormat(format); - - _qGlContext.setFormat(format); - _qGlContext.create(); - - show(); - makeCurrent(); - setupDebugLogger(this); - - gpu::Context::init(); - _context = std::make_shared(); - makeCurrent(); - auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); - auto state = std::make_shared(); - state->setMultisampleEnable(true); - state->setDepthTest(gpu::State::DepthTest { true }); - _pipeline = gpu::Pipeline::create(shader, state); - - - - // Clear screen - gpu::Batch batch; - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); - _context->render(batch); - - DependencyManager::set(); - DependencyManager::set(); - DependencyManager::set(); - - resize(QSize(800, 600)); - + void initGl() override { + Parent::initGl(); +#ifdef INTERACTIVE _time.start(); - } - - virtual ~QTestWindow() { +#endif + updateCamera(); + _testBuilders = TestBuilders({ + //[this] { return new TestFbx(_shapePlumber); }, + [] { return new TestShapes(); }, + }); } void updateCamera() { - float t = _time.elapsed() * 1e-4f; + float t = 0; +#ifdef INTERACTIVE + t = _time.elapsed() * 1e-3f; +#endif glm::vec3 unitscale { 1.0f }; glm::vec3 up { 0.0f, 1.0f, 0.0f }; @@ -283,263 +109,64 @@ public: static const vec3 camera_focus(0); static const vec3 camera_up(0, 1, 0); _camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up)); + + ViewFrustum frustum; + frustum.setPosition(camera_position); + frustum.setOrientation(glm::quat_cast(_camera)); + frustum.setProjection(_projectionMatrix); + _renderArgs->setViewFrustum(frustum); } + void renderFrame() override { + updateCamera(); - void drawFloorGrid(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - // Render grid on xz plane (not the optimal way to do things, but w/e) - // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only - static const std::string GRID_INSTANCE = "Grid"; - static auto compactColor1 = toCompactColor(vec4 { 0.35f, 0.25f, 0.15f, 1.0f }); - static auto compactColor2 = toCompactColor(vec4 { 0.15f, 0.25f, 0.35f, 1.0f }); - static std::vector transforms; - static gpu::BufferPointer colorBuffer; - if (!transforms.empty()) { - transforms.reserve(200); - colorBuffer = std::make_shared(); - for (int i = 0; i < 100; ++i) { - { - glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor1); - } + while ((!_currentTest || (_currentTestId >= _currentMaxTests)) && !_testBuilders.empty()) { + if (_currentTest) { + delete _currentTest; + _currentTest = nullptr; + } - { - glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); - transform = glm::translate(transform, vec3(0, -1, -50 + i)); - transform = glm::scale(transform, vec3(100, 1, 1)); - transforms.push_back(transform); - colorBuffer->append(compactColor2); - } + _currentTest = _testBuilders.front()(); + _testBuilders.pop_front(); + + if (_currentTest) { + _currentMaxTests = _currentTest->getTestCount(); + _currentTestId = 0; } } - auto pipeline = geometryCache->getSimplePipeline(); - for (auto& transform : transforms) { - batch.setModelTransform(transform); - batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); - }); - } - } - void drawSimpleShapes(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - static const size_t ITEM_COUNT = 1000; - static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; - static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; - - static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element NORMAL_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element COLOR_ELEMENT { gpu::VEC4, gpu::NUINT8, gpu::RGBA }; - - static std::vector transforms; - static std::vector colors; - static gpu::BufferPointer colorBuffer; - static gpu::BufferView colorView; - static gpu::BufferView instanceXfmView; - if (!colorBuffer) { - colorBuffer = std::make_shared(); - - static const float ITEM_RADIUS = 20; - static const vec3 ITEM_TRANSLATION { 0, 0, -ITEM_RADIUS }; - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; - //indirectCommand._count - float startingInterval = ITEM_INTERVAL * i; - for (size_t j = 0; j < ITEM_COUNT; ++j) { - float theta = j * SHAPE_INTERVAL + startingInterval; - auto transform = glm::rotate(mat4(), theta, Vectors::UP); - transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); - transform = glm::translate(transform, ITEM_TRANSLATION); - transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); - transforms.push_back(transform); - auto color = vec4 { randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; - color /= 255.0f; - colors.push_back(color); - colorBuffer->append(toCompactColor(color)); - } - } - colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT); - } - - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - batch.setInputFormat(getInstancedSolidStreamFormat()); - for (size_t i = 0; i < TYPE_COUNT; ++i) { - GeometryCache::Shape shape = SHAPE[i]; - GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; - batch.setInputBuffer(gpu::Stream::COLOR, colorView); - for (size_t j = 0; j < ITEM_COUNT; ++j) { - batch.setModelTransform(transforms[j]); - shapeData.draw(batch); - } - } - } - - void drawCenterShape(gpu::Batch& batch) { - // Render unlit cube + sphere - static auto startUsecs = usecTimestampNow(); - float seconds = getSeconds(startUsecs); - seconds /= 4.0f; - batch.setModelTransform(Transform()); - batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); - - bool wire = (seconds - floorf(seconds) > 0.5f); - auto geometryCache = DependencyManager::get(); - int shapeIndex = ((int)seconds) % TYPE_COUNT; - if (wire) { - geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); - } else { - geometryCache->renderShape(batch, SHAPE[shapeIndex]); - } - - batch.setModelTransform(Transform().setScale(2.05f)); - batch._glColor4f(1, 1, 1, 1); - geometryCache->renderWireCube(batch); - } - - void drawTerrain(gpu::Batch& batch) { - auto geometryCache = DependencyManager::get(); - static std::once_flag once; - static gpu::BufferPointer vertexBuffer { std::make_shared() }; - static gpu::BufferPointer indexBuffer { std::make_shared() }; - - static gpu::BufferView positionView; - static gpu::BufferView textureView; - static gpu::Stream::FormatPointer vertexFormat { std::make_shared() }; - - static gpu::TexturePointer texture; - static gpu::PipelinePointer pipeline; - std::call_once(once, [&] { - static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec4) * 2; // position, normals, textures - static const uint SHAPE_TEXTURES_OFFSET = sizeof(glm::vec4); - static const gpu::Element POSITION_ELEMENT { gpu::VEC3, gpu::FLOAT, gpu::XYZ }; - static const gpu::Element TEXTURE_ELEMENT { gpu::VEC2, gpu::FLOAT, gpu::UV }; - std::vector vertices; - const int MINX = -1000; - const int MAXX = 1000; - - // top - vertices.push_back(vec4(MAXX, 0, MAXX, 1)); - vertices.push_back(vec4(MAXX, MAXX, 0, 0)); - - vertices.push_back(vec4(MAXX, 0, MINX, 1)); - vertices.push_back(vec4(MAXX, 0, 0, 0)); - - vertices.push_back(vec4(MINX, 0, MINX, 1)); - vertices.push_back(vec4(0, 0, 0, 0)); - - vertices.push_back(vec4(MINX, 0, MAXX, 1)); - vertices.push_back(vec4(0, MAXX, 0, 0)); - - vertexBuffer->append(vertices); - indexBuffer->append(std::vector({ 0, 1, 2, 2, 3, 0 })); - - positionView = gpu::BufferView(vertexBuffer, 0, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); - textureView = gpu::BufferView(vertexBuffer, SHAPE_TEXTURES_OFFSET, vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, TEXTURE_ELEMENT); - texture = DependencyManager::get()->getImageTexture("C:/Users/bdavis/Git/openvr/samples/bin/cube_texture.png"); - // texture = DependencyManager::get()->getImageTexture("H:/test.png"); - //texture = DependencyManager::get()->getImageTexture("H:/crate_blue.fbm/lambert8SG_Normal_OpenGL.png"); - - auto shader = makeShader(VERTEX_SHADER, FRAGMENT_SHADER, gpu::Shader::BindingSet {}); - auto state = std::make_shared(); - state->setMultisampleEnable(false); - state->setDepthTest(gpu::State::DepthTest { true }); - pipeline = gpu::Pipeline::create(shader, state); - vertexFormat->setAttribute(gpu::Stream::POSITION); - vertexFormat->setAttribute(gpu::Stream::TEXCOORD); - }); - - static auto start = usecTimestampNow(); - auto now = usecTimestampNow(); - if ((now - start) > USECS_PER_SECOND * 1) { - start = now; - texture->incremementMinMip(); - } - - batch.setPipeline(pipeline); - batch.setInputBuffer(gpu::Stream::POSITION, positionView); - batch.setInputBuffer(gpu::Stream::TEXCOORD, textureView); - batch.setIndexBuffer(gpu::UINT16, indexBuffer, 0); - batch.setInputFormat(vertexFormat); - - batch.setResourceTexture(0, texture); - batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.1, 0))); - batch.drawIndexed(gpu::TRIANGLES, 6, 0); - - batch.setResourceTexture(0, DependencyManager::get()->getBlueTexture()); - batch.setModelTransform(glm::translate(glm::mat4(), vec3(0, -0.2, 0))); - batch.drawIndexed(gpu::TRIANGLES, 6, 0); - } - - void draw() { - // Attempting to draw before we're visible and have a valid size will - // produce GL errors. - if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { + if (!_currentTest && _testBuilders.empty()) { + qApp->quit(); return; } - updateCamera(); - makeCurrent(); - - gpu::Batch batch; - batch.resetStages(); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f }); - batch.clearDepthFramebuffer(1e4); - batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); - batch.setProjectionTransform(_projectionMatrix); - - batch.setViewTransform(_camera); - batch.setPipeline(_pipeline); - batch.setModelTransform(Transform()); - //drawFloorGrid(batch); - //drawSimpleShapes(batch); - //drawCenterShape(batch); - drawTerrain(batch); - - _context->render(batch); - _qGlContext.swapBuffers(this); - - fps.increment(); - if (fps.elapsed() >= 0.5f) { - qDebug() << "FPS: " << fps.rate(); - fps.reset(); + // Tests might need to wait for resources to download + if (!_currentTest->isReady()) { + return; } - } - - void makeCurrent() { - _qGlContext.makeCurrent(this); - } -protected: - void resizeEvent(QResizeEvent* ev) override { - resizeWindow(ev->size()); - - float fov_degrees = 60.0f; - float aspect_ratio = (float)_size.width() / _size.height(); - float near_clip = 0.1f; - float far_clip = 1000.0f; - _projectionMatrix = glm::perspective(glm::radians(fov_degrees), aspect_ratio, near_clip, far_clip); - } + gpu::doInBatch(_renderArgs->_context, [&](gpu::Batch& batch) { + batch.setViewTransform(_camera); + _renderArgs->_batch = &batch; + _currentTest->renderTest(_currentTestId, _renderArgs); + _renderArgs->_batch = nullptr; + }); + +#ifdef INTERACTIVE + +#else + // TODO Capture the current rendered framebuffer and save + // Increment the test ID + ++_currentTestId; +#endif + } }; + int main(int argc, char** argv) { QGuiApplication app(argc, argv); - QTestWindow window; - auto timer = new QTimer(&app); - timer->setInterval(0); - app.connect(timer, &QTimer::timeout, &app, [&] { - window.draw(); - }); - timer->start(); + MyTestWindow window; app.exec(); return 0; } -#include "main.moc" - From 11de8a52b268b723e36ea9d88767e7f8dea2d755 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 22 May 2016 22:52:56 -0700 Subject: [PATCH 218/264] fix broken procedural skybox when leaving and re-entering --- libraries/procedural/src/procedural/Procedural.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index cedf76b37a..781b508bc7 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -100,10 +100,6 @@ bool Procedural::parseUrl(const QUrl& shaderUrl) { return false; } - if (_shaderUrl == shaderUrl) { - return true; - } - _shaderUrl = shaderUrl; _shaderDirty = true; From 1f2f9da01911001914953503bfae11eda6d9e3d7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 22 May 2016 23:34:45 -0700 Subject: [PATCH 219/264] fix keys getting stuck --- libraries/ui/src/OffscreenUi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 4fb25e3e3f..dfd9056703 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -590,6 +590,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // let the parent class do it's work bool result = OffscreenQmlSurface::eventFilter(originalDestination, event); + // Check if this is a key press/release event that might need special attention auto type = event->type(); if (type != QEvent::KeyPress && type != QEvent::KeyRelease) { @@ -597,7 +598,8 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { } QKeyEvent* keyEvent = dynamic_cast(event); - bool& pressed = _pressedKeys[keyEvent->key()]; + auto key = keyEvent->key(); + bool& pressed = _pressedKeys[key]; // Keep track of which key press events the QML has accepted if (result && QEvent::KeyPress == type) { @@ -607,7 +609,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // QML input elements absorb key press, but apparently not key release. // therefore we want to ensure that key release events for key presses that were // accepted by the QML layer are suppressed - if (!result && type == QEvent::KeyRelease && pressed) { + if (type == QEvent::KeyRelease && pressed) { pressed = false; return true; } From bd6f6d2eef4916646334b16878bf8cc565c3ce7d Mon Sep 17 00:00:00 2001 From: Geenz Date: Mon, 23 May 2016 09:53:22 -0400 Subject: [PATCH 220/264] Typo. Not quite sure how this got here. --- libraries/render-utils/src/spot_light.slf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 63e87e95c0..4191ba3f63 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -74,7 +74,7 @@ void main(void) { vec4 shading = evalFragShading(fragNormal, fragLightDir, fragEyeDir, frag.metallic, frag.specular, frag.roughness); // Eval attenuation - float radialAttenuation = evalLightAttenuation(light, fragLightDistance4); + float radialAttenuation = evalLightAttenuation(light, fragLightDistance); float angularAttenuation = evalLightSpotAttenuation(light, cosSpotAngle); // Final Lighting color From 5fe01acaa84c30d284d0033b32b5496e6a3ad87e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 10:03:13 -0700 Subject: [PATCH 221/264] Added more comments to AvatarDataPacket section. --- libraries/avatars/src/AvatarData.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 7a20f24da8..b74fc8de2e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -61,12 +61,14 @@ namespace AvatarDataPacket { } PACKED_END; const size_t HEADER_SIZE = 49; + // only present if HAS_REFERENTIAL flag is set in header.flags PACKED_BEGIN struct ParentInfo { uint8_t parentUUID[16]; // rfc 4122 encoded uint16_t parentJointIndex; } PACKED_END; const size_t PARENT_INFO_SIZE = 18; + // only present if IS_FACESHIFT_CONNECTED flag is set in header.flags PACKED_BEGIN struct FaceTrackerInfo { float leftEyeBlink; float rightEyeBlink; @@ -76,6 +78,17 @@ namespace AvatarDataPacket { // float blendshapeCoefficients[numBlendshapeCoefficients]; } PACKED_END; const size_t FACE_TRACKER_INFO_SIZE = 17; + + // variable length structure follows + /* + struct JointData { + uint8_t numJoints; + uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows. + SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes() + uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows. + SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed() + }; + */ } #define ASSERT(COND) do { if (!(COND)) { int* bad = nullptr; *bad = 0xbad; } } while(0) From 9aad38b2c2582a73d72ad0ad22740d07fe0e7ab4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 10:39:39 -0700 Subject: [PATCH 222/264] merge fix --- libraries/avatars/src/AvatarData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 7d571a826f..61ee649273 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -296,7 +296,7 @@ public: QUrl skeletonModelURL; QVector attachmentData; QString displayName; - AvatarEntityMap avatarEntityMap; + AvatarEntityMap avatarEntityData; }; static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); From 4342a071086731f25aa1f936acb3831e2d7043fd Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 11:11:37 -0700 Subject: [PATCH 223/264] Updated AvatarDataPacket section with sequence number info. --- libraries/avatars/src/AvatarData.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 1ca4b1beb1..cc61036915 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -49,6 +49,7 @@ const glm::vec3 DEFAULT_LOCAL_AABOX_SCALE(1.0f); const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; namespace AvatarDataPacket { + // NOTE: AvatarDataPackets start with a uint16_t sequence number that is not reflected in the Header structure. PACKED_BEGIN struct Header { float position[3]; // skeletal model's position From 099a675a186e4f91c88829d806ea38963bba5604 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 24 May 2016 08:03:29 +1200 Subject: [PATCH 224/264] Remove extra logging --- interface/resources/qml/dialogs/FileDialog.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index b00ee76b5e..5cd972a38f 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -130,8 +130,6 @@ ModalWindow { choices = [], i, length; - console.log("####### folder parts: " + JSON.stringify(folders)); - if (folders[folders.length - 1] === "") { folders.pop(); } @@ -160,9 +158,6 @@ ModalWindow { onLastValidFolderChanged: { var folder = d.capitalizeDrive(lastValidFolder); - - console.log("####### lastValidFolder: " + folder); - calculatePathChoices(folder); } From fdcd667d3c4f54c8958de1c89c9007adf8130582 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 23 May 2016 13:33:42 -0700 Subject: [PATCH 225/264] Fix hand controller pointer after input changes. --- interface/resources/controllers/hydra.json | 8 +- interface/resources/controllers/vive.json | 4 +- .../controllers/handControllerPointer.js | 112 ++++++------------ 3 files changed, 46 insertions(+), 78 deletions(-) diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 42237033af..8233685763 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -16,8 +16,12 @@ { "from": "Hydra.L0", "to": "Standard.Back" }, { "from": "Hydra.R0", "to": "Standard.Start" }, - { "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, - { "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.L3" ], "to": "Standard.L3" }, + { "from": [ "Hydra.R3" ], "to": "Standard.R3" }, + { "from": [ "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" }, + { "from": [ "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" }, { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, { "from": "Hydra.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 4085d71c27..fec93c9132 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -17,8 +17,8 @@ { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, - { "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" }, - { "from": "Vive.RightApplicationMenu", "to": "Standard.Start" }, + { "from": "Vive.LeftApplicationMenu", "to": "Standard.LeftSecondaryThumb" }, + { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index f4e4492a88..e7be3af5dd 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -22,10 +22,7 @@ // (For now, the thumb buttons on both controllers are always on.) // When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. // Otherwise, the active hand controller shows a red ball where a click will act. -// -// Bugs: -// On Windows, the upper left corner of Interface must be in the upper left corner of the screen, and the title bar must be 50px high. (System bug.) -// While hardware mouse move switches to mouse move, hardware mouse click (without amove) does not. + // UTILITIES ------------- @@ -270,75 +267,43 @@ function toggleHand() { } // Create clickMappings as needed, on demand. -var clickMappings = {}, clickMapping, clickMapToggle; -var hardware; // undefined -function checkHardware() { - var newHardware = Controller.Hardware.Hydra ? 'Hydra' : (Controller.Hardware.Vive ? 'Vive' : null); // not undefined - if (hardware === newHardware) { - return; - } - print('Setting mapping for new controller hardware:', newHardware); - if (clickMapToggle) { - clickMapToggle.setState(false); - } - hardware = newHardware; - if (clickMappings[hardware]) { - clickMapping = clickMappings[hardware]; - } else { - clickMapping = Controller.newMapping(Script.resolvePath('') + '-click-' + hardware); - Script.scriptEnding.connect(clickMapping.disable); - function mapToAction(button, action) { - clickMapping.from(Controller.Hardware[hardware][button]).peek().to(Controller.Actions[action]); - } - function makeHandToggle(button, hand, optionalWhen) { - var whenThunk = optionalWhen || function () { - return true; - }; - function maybeToggle() { - if (activeHand !== Controller.Standard[hand]) { - toggleHand(); - } +var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); +Script.scriptEnding.connect(clickMapping.disable); - } - clickMapping.from(Controller.Hardware[hardware][button]).peek().when(whenThunk).to(maybeToggle); - } - function makeViveWhen(click, x, y) { - var viveClick = Controller.Hardware.Vive[click], - viveX = Controller.Standard[x], // Standard after filtering by mapping - viveY = Controller.Standard[y]; - return function () { - var clickValue = Controller.getValue(viveClick); - var xValue = Controller.getValue(viveX); - var yValue = Controller.getValue(viveY); - return clickValue && !xValue && !yValue; - }; - } - switch (hardware) { - case 'Hydra': - makeHandToggle('R3', 'RightHand'); - makeHandToggle('L3', 'LeftHand'); - - mapToAction('R3', 'ReticleClick'); - mapToAction('L3', 'ReticleClick'); - mapToAction('R4', 'ContextMenu'); - mapToAction('L4', 'ContextMenu'); - break; - case 'Vive': - // When touchpad click is NOT treated as movement, treat as left click - makeHandToggle('RS', 'RightHand', makeViveWhen('RS', 'RX', 'RY')); - makeHandToggle('LS', 'LeftHand', makeViveWhen('LS', 'LX', 'LY')); - clickMapping.from(Controller.Hardware.Vive.RS).when(makeViveWhen('RS', 'RX', 'RY')).to(Controller.Actions.ReticleClick); - clickMapping.from(Controller.Hardware.Vive.LS).when(makeViveWhen('LS', 'LX', 'LY')).to(Controller.Actions.ReticleClick); - mapToAction('RightApplicationMenu', 'ContextMenu'); - mapToAction('LeftApplicationMenu', 'ContextMenu'); - break; - } - clickMappings[hardware] = clickMapping; - } - clickMapToggle = new LatchedToggle(clickMapping.enable, clickMapping.disable); - clickMapToggle.setState(true); +// Move these to vive.json +function makeCenterClickWhen(click, x, y) { + var clickKey = Controller.Standard[click], + xKey = Controller.Standard[x], // Standard after filtering by mapping + yKey = Controller.Standard[y]; + return function () { + var clickValue = Controller.getValue(clickKey); + var xValue = Controller.getValue(xKey); + var yValue = Controller.getValue(yKey); + var answer = clickValue && !xValue && !yValue; + return answer; + }; } -checkHardware(); +if (Controller.Hardware.Vive) { + clickMapping.from(Controller.Hardware.Vive.RS).when(makeCenterClickWhen('RS', 'RX', 'RY')).to(Controller.Standard.R3); + clickMapping.from(Controller.Hardware.Vive.LS).when(makeCenterClickWhen('LS', 'LX', 'LY')).to(Controller.Standard.L3); +} + + +clickMapping.from(Controller.Standard.R3).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.L3).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); +clickMapping.from(Controller.Standard.R3).peek().to(function (on) { + if (on && (activeHand !== Controller.Standard.RightHand)) { + toggleHand(); + } +}); +clickMapping.from(Controller.Standard.L3).peek().to(function (on) { + if (on && (activeHand !== Controller.Standard.LeftHand)) { + toggleHand(); + } +}); +clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere @@ -415,8 +380,8 @@ function update() { return turnOffVisualization(); } var controllerPose = Controller.getPoseValue(activeHand); - // Vive is effectively invalid when not in HMD - if (!controllerPose.valid || ((hardware === 'Vive') && !HMD.active)) { + // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) + if (!controllerPose.valid) { return turnOffVisualization(); } // Controller is cradled. var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), @@ -458,7 +423,6 @@ Script.scriptEnding.connect(function () { var SETTINGS_CHANGE_RECHECK_INTERVAL = 10 * 1000; // milliseconds function checkSettings() { updateFieldOfView(); - checkHardware(); } checkSettings(); var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); From 097b16aadc5e0f135e2df0a93a6bf7a939abccbe Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 15:40:29 -0700 Subject: [PATCH 226/264] Fix for sensor reset on oculus. --- plugins/oculus/src/OculusBaseDisplayPlugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a92c5b5b22..3b6545ae96 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -13,6 +13,8 @@ void OculusBaseDisplayPlugin::resetSensors() { ovr_RecenterTrackingOrigin(_session); + + _currentRenderFrameInfo.renderPose = glm::mat4(); // identity } void OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { From f82c3ba4f2e39e106a634d637c9ebbcbc585cb4a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 16:30:05 -0700 Subject: [PATCH 227/264] Revert "OpenVRDispalyPlugin: fix one-frame lag in resetSensors." This reverts commit 8381e74fb3478750362de176aa0710b369469c0f. --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 1f6b11f862..38719fdca5 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -122,19 +122,7 @@ void OpenVrDisplayPlugin::customizeContext() { void OpenVrDisplayPlugin::resetSensors() { Lock lock(_poseMutex); glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - - glm::mat4 oldSensorResetMat = _sensorResetMat; _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); - - glm::mat4 undoRedoMat = _sensorResetMat * glm::inverse(oldSensorResetMat); - - // update the device poses, by undoing the previous sensorResetMatrix then applying the new one. - for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { - _trackedDevicePoseMat4[i] = undoRedoMat * _trackedDevicePoseMat4[i]; - _trackedDeviceLinearVelocities[i] = transformVectorFast(undoRedoMat, _trackedDeviceLinearVelocities[i]); - _trackedDeviceAngularVelocities[i] = transformVectorFast(undoRedoMat, _trackedDeviceAngularVelocities[i]); - } - _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; } From edfce0d5badcac66e0b857c24b47875c37d33b04 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 23 May 2016 16:34:19 -0700 Subject: [PATCH 228/264] MyAvatar: reset fix, for both oculus and vive --- interface/src/avatar/MyAvatar.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2505e258ed..12d2764e74 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -263,8 +263,9 @@ void MyAvatar::reset(bool andRecenter, bool andReload) { setPosition(worldBodyPos); setOrientation(worldBodyRot); - // now sample the new hmd orientation AFTER sensor reset. - updateFromHMDSensorMatrix(qApp->getHMDSensorPose()); + // now sample the new hmd orientation AFTER sensor reset, which should be identity. + glm::mat4 identity; + updateFromHMDSensorMatrix(identity); // update the body in sensor space using the new hmd sensor sample _bodySensorMatrix = deriveBodyFromHMDSensor(); From 7dabce9cff02f74db3a9b737ca98b06708d57580 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 23 May 2016 19:04:35 -0700 Subject: [PATCH 229/264] Check throttle before idling/painting --- interface/src/Application.cpp | 45 +++++++++++++++++++---------------- interface/src/Application.h | 3 ++- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b0e4880011..7d1610c78e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -174,7 +174,6 @@ static const QString FBX_EXTENSION = ".fbx"; static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; -static const int MSECS_PER_SEC = 1000; static const int MIRROR_VIEW_TOP_PADDING = 5; static const int MIRROR_VIEW_LEFT_PADDING = 10; static const int MIRROR_VIEW_WIDTH = 265; @@ -633,7 +632,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one - const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC; + const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; auto discoverabilityManager = DependencyManager::get(); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); @@ -1828,9 +1827,9 @@ bool Application::event(QEvent* event) { // Presentation/painting logic // TODO: Decouple presentation and painting loops - static bool isPainting = false; + static bool isPaintingThrottled = false; if ((int)event->type() == (int)Present) { - if (isPainting) { + if (isPaintingThrottled) { // If painting (triggered by presentation) is hogging the main thread, // repost as low priority to avoid hanging the GUI. // This has the effect of allowing presentation to exceed the paint budget by X times and @@ -1838,14 +1837,17 @@ bool Application::event(QEvent* event) { // (e.g. at a 60FPS target, painting for 17us would fall to 58.82FPS instead of 30FPS). removePostedEvents(this, Present); postEvent(this, new QEvent(static_cast(Present)), Qt::LowEventPriority); - isPainting = false; + isPaintingThrottled = false; return true; } - idle(); - - postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); - isPainting = true; + float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed(); + if (shouldPaint(nsecsElapsed)) { + _lastTimeUpdated.start(); + idle(nsecsElapsed); + postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority); + } + isPaintingThrottled = true; return true; } else if ((int)event->type() == (int)Paint) { @@ -1855,7 +1857,7 @@ bool Application::event(QEvent* event) { paintGL(); - isPainting = false; + isPaintingThrottled = false; return true; } @@ -2639,10 +2641,9 @@ bool Application::acceptSnapshot(const QString& urlString) { static uint32_t _renderedFrameIndex { INVALID_FRAME }; -void Application::idle() { - // idle is called on a queued connection, so make sure we should be here. - if (_inPaint || _aboutToQuit) { - return; +bool Application::shouldPaint(float nsecsElapsed) { + if (_aboutToQuit) { + return false; } auto displayPlugin = getActiveDisplayPlugin(); @@ -2661,16 +2662,21 @@ void Application::idle() { } #endif - float msecondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_USEC / USECS_PER_MSEC; + float msecondsSinceLastUpdate = nsecsElapsed / NSECS_PER_USEC / USECS_PER_MSEC; // Throttle if requested if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) { - return; + return false; } // Sync up the _renderedFrameIndex _renderedFrameIndex = displayPlugin->presentCount(); + return true; +} + +void Application::idle(float nsecsElapsed) { + // Update the deadlock watchdog updateHeartbeat(); @@ -2687,7 +2693,7 @@ void Application::idle() { PROFILE_RANGE(__FUNCTION__); - float secondsSinceLastUpdate = msecondsSinceLastUpdate / MSECS_PER_SECOND; + float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND; // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) { @@ -2697,9 +2703,6 @@ void Application::idle() { _keyboardDeviceHasFocus = true; } - // We're going to execute idle processing, so restart the last idle timer - _lastTimeUpdated.start(); - checkChangeCursor(); Stats::getInstance()->updateStats(); @@ -2926,7 +2929,7 @@ void Application::loadSettings() { } void Application::saveSettings() const { - sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC); + sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SECOND); DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 28dbcead47..11a591776e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -329,7 +329,8 @@ private: void cleanupBeforeQuit(); - void idle(); + bool shouldPaint(float nsecsElapsed); + void idle(float nsecsElapsed); void update(float deltaTime); // Various helper functions called during update() From 3391430f0a2f061c95a9bdf7f74bbfdbc0ab62ec Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 24 May 2016 20:28:49 +1200 Subject: [PATCH 230/264] Tidy zone flying and ghosting entities editor options Move options to top of section from under "skybox" subsection Fix capitalization of labels --- scripts/system/html/entityProperties.html | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index efe7e6cc65..67d168481b 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -1717,6 +1717,14 @@
+
+ + +
+
+ + +
@@ -1801,15 +1809,6 @@
-
- - -
-
- - -
-
M From 1d9981e6242d7fda4e9e6db824948b5f6ad45a2a Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 13:31:19 -0700 Subject: [PATCH 231/264] first cut at support for verifying all protocol version compatibility --- domain-server/src/DomainGatekeeper.cpp | 42 +++++++++++++---- domain-server/src/DomainGatekeeper.h | 5 +- domain-server/src/DomainServer.cpp | 39 +++++++++++++++ domain-server/src/DomainServer.h | 2 + domain-server/src/NodeConnectionData.cpp | 9 ++++ domain-server/src/NodeConnectionData.h | 2 + interface/src/Application.cpp | 23 ++++++++- interface/src/Application.h | 8 ++++ interface/src/Menu.cpp | 7 +++ interface/src/Menu.h | 1 + .../src/scripting/WindowScriptingInterface.h | 4 +- libraries/networking/src/DomainHandler.cpp | 47 +++++++++++++------ libraries/networking/src/DomainHandler.h | 12 ++++- libraries/networking/src/LimitedNodeList.cpp | 4 ++ libraries/networking/src/LimitedNodeList.h | 6 ++- libraries/networking/src/NLPacket.cpp | 9 ++-- libraries/networking/src/NLPacket.h | 4 +- libraries/networking/src/NodeList.cpp | 32 +++++++++++-- libraries/networking/src/NodeList.h | 15 ++++++ libraries/networking/src/PacketReceiver.cpp | 13 ++++- .../networking/src/udt/PacketHeaders.cpp | 43 ++++++++++++++++- libraries/networking/src/udt/PacketHeaders.h | 21 ++++++++- 22 files changed, 304 insertions(+), 44 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 61cc775e08..9d5ee75818 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -56,10 +56,24 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetVersion(); + + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr()); + + QByteArray myProtocolVersion = protocolVersionsSignature(); + if (nodeConnection.protocolVersion != myProtocolVersion) { + QString protocolVersionError = "Protocol version mismatch - Domain version:" + QCoreApplication::applicationVersion(); + qDebug() << "Protocol Version mismatch - denying connection."; + sendConnectionDeniedPacket(protocolVersionError, message->getSenderSockAddr(), + DomainHandler::ConnectionRefusedReason::ProtocolMismatch); + return; + } + //qDebug() << __FUNCTION__ << "Protocol Version MATCH - continue with processing connection."; + if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) { qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection."; @@ -97,7 +111,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr(); @@ -332,7 +351,7 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node bool DomainGatekeeper::verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { - + // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -370,7 +389,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, } else { if (!senderSockAddr.isNull()) { qDebug() << "Error decrypting username signature for " << username << "- denying connection."; - sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr); + sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } // free up the public key, we don't need it anymore @@ -382,13 +402,15 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, // we can't let this user in since we couldn't convert their public key to an RSA key we could use if (!senderSockAddr.isNull()) { qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; - sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr); + sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } } else { if (!senderSockAddr.isNull()) { qDebug() << "Insufficient data to decrypt username signature - denying connection."; - sendConnectionDeniedPacket("Insufficient data", senderSockAddr); + sendConnectionDeniedPacket("Insufficient data", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); } } @@ -402,7 +424,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt if (username.isEmpty()) { qDebug() << "Connect request denied - no username provided."; - sendConnectionDeniedPacket("No username provided", senderSockAddr); + sendConnectionDeniedPacket("No username provided", senderSockAddr, + DomainHandler::ConnectionRefusedReason::LoginError); return false; } @@ -416,7 +439,8 @@ bool DomainGatekeeper::isVerifiedAllowedUser(const QString& username, const QByt } } else { qDebug() << "Connect request denied for user" << username << "- not in allowed users list."; - sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr); + sendConnectionDeniedPacket("User not on whitelist.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::NotAuthorized); return false; } @@ -452,7 +476,8 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA // deny connection from this user qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection."; - sendConnectionDeniedPacket("Too many connected users.", senderSockAddr); + sendConnectionDeniedPacket("Too many connected users.", senderSockAddr, + DomainHandler::ConnectionRefusedReason::TooManyUsers); return false; } @@ -516,7 +541,8 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { } } -void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr) { +void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode) { // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index c4ac32fabf..09e3b04ed7 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -19,6 +19,8 @@ #include #include +#include + #include #include #include @@ -74,7 +76,8 @@ private: const HifiSockAddr& senderSockAddr); void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr); - void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr); + void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr, + DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown); void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index cfec72a24b..18ca7e2941 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -303,6 +303,36 @@ const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; + + +bool DomainServer::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + //qDebug() << __FUNCTION__ << "type:" << headerType << "version:" << (int)headerVersion; + + auto nodeList = DependencyManager::get(); + + // This implements a special case that handles OLD clients which don't know how to negotiate matching + // protocol versions. We know these clients will sent DomainConnectRequest with older versions. We also + // know these clients will show a warning dialog if they get an EntityData with a protocol version they + // don't understand, so we can send them an empty EntityData with our latest version and they will + // warn the user that the protocol is not compatible + if (headerType == PacketType::DomainConnectRequest && + headerVersion < static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + + //qDebug() << __FUNCTION__ << "OLD VERSION checkin sending an intentional bad packet -------------------------------"; + + auto packetWithBadVersion = NLPacket::create(PacketType::EntityData); + nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr()); + return false; + } + + // let the normal nodeList implementation handle all other packets. + return nodeList->isPacketVerified(packet); +} + + void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; @@ -376,6 +406,10 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); + + // set packetVersionMatch as the verify packet operator for the udt::Socket + //using std::placeholders::_1; + nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -666,6 +700,8 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { + + //qDebug() << __FUNCTION__ << "---------------"; QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); @@ -746,6 +782,9 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) { } void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) { + + //qDebug() << __FUNCTION__ << "---------------"; + const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2; // setup the extended header for the domain list packets diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index fef3221b7d..c39e405380 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -99,6 +99,8 @@ private: void optionallyGetTemporaryName(const QStringList& arguments); + static bool packetVersionMatch(const udt::Packet& packet); + bool resetAccountManagerAccessToken(); void setupAutomaticNetworking(); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 28f769298c..5ddcbf1792 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -19,6 +19,15 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c if (isConnectRequest) { dataStream >> newHeader.connectUUID; + + // Read out the protocol version signature from the connect message + char* rawBytes; + uint length; + + // FIXME -- do we need to delete the rawBytes after it's been copied into the QByteArray? + dataStream.readBytes(rawBytes, length); + newHeader.protocolVersion = QByteArray(rawBytes, length); + //qDebug() << __FUNCTION__ << "...got protocol version from node... version:" << newHeader.protocolVersion; } dataStream >> newHeader.nodeType diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 34119ffdab..9264db637e 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -28,6 +28,8 @@ public: HifiSockAddr senderSockAddr; QList interestList; QString placeName; + + QByteArray protocolVersion; }; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b0e4880011..821cd83c31 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -631,6 +631,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + + connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC; @@ -653,7 +655,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + + connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached); + //connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); + + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager @@ -4569,6 +4575,21 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const { Physics::setSessionUUID(sessionUUID); } + +// If we're not getting anything back from the domain server checkin, it might be that the domain speaks an +// older version of the DomainConnectRequest protocal. We will attempt to send and older version of DomainConnectRequest. +// We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we +// need to be downgraded to talk to it). +void Application::limitOfSilentDomainCheckInsReached() { + //qDebug() << __FUNCTION__; + + auto nodeList = DependencyManager::get(); + + nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version + + nodeList->reset(); +} + bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); if (realUrl.isLocalFile()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 28dbcead47..edd1b6187d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -261,6 +261,12 @@ public slots: void resetSensors(bool andReload = false); void setActiveFaceTracker() const; +#if (PR_BUILD || DEV_BUILD) + void sendWrongProtocolVersionsSignature(bool checked) { + ::sendWrongProtocolVersionsSignature(checked); + } +#endif + #ifdef HAVE_IVIEWHMD void setActiveEyeTracker(); void calibrateEyeTracker1Point(); @@ -314,6 +320,8 @@ private slots: bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; void setSessionUUID(const QUuid& sessionUUID) const; + void limitOfSilentDomainCheckInsReached(); + void domainChanged(const QString& domainHostname); void updateWindowTitle() const; void nodeAdded(SharedNodePointer node) const; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 538410a47d..a21aa71753 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -545,6 +545,13 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); + #if (PR_BUILD || DEV_BUILD) + addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::SendWrongProtocolVersion, 0, false, + qApp, SLOT(sendWrongProtocolVersionsSignature(bool))); + #endif + + + // Developer > Timing >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 36d285e2cf..fcaf8e6caa 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -167,6 +167,7 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 0f51a484c4..145d17faaf 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -16,6 +16,8 @@ #include #include +#include + class WebWindowClass; class WindowScriptingInterface : public QObject, public Dependency { @@ -45,7 +47,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reason); + void domainConnectionRefused(const QString& reasonMessage, DomainHandler::ConnectionRefusedReason reason = DomainHandler::ConnectionRefusedReason::Unknown); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 44ce63e6c6..08810010a6 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -355,34 +355,53 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes } } +bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { + switch (reasonCode) { + case ConnectionRefusedReason::LoginError: + case ConnectionRefusedReason::NotAuthorized: + return true; + + default: + case ConnectionRefusedReason::Unknown: + case ConnectionRefusedReason::ProtocolMismatch: + case ConnectionRefusedReason::TooManyUsers: + return false; + } + return false; +} + void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { // Read deny reason from packet + ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown; quint16 reasonSize; message->readPrimitive(&reasonSize); - QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + QString reasonMessage = QString::fromUtf8(message->readWithoutCopy(reasonSize)); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in - qCWarning(networking) << "The domain-server denied a connection request: " << reason; + qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; qCWarning(networking) << "Make sure you are logged in."; - if (!_domainConnectionRefusals.contains(reason)) { - _domainConnectionRefusals.append(reason); - emit domainConnectionRefused(reason); + if (!_domainConnectionRefusals.contains(reasonMessage)) { + _domainConnectionRefusals.append(reasonMessage); + emit domainConnectionRefused(reasonMessage, reasonCode); } auto accountManager = DependencyManager::get(); - if (!_hasCheckedForAccessToken) { - accountManager->checkAndSignalForAccessToken(); - _hasCheckedForAccessToken = true; - } + // Some connection refusal reasons imply that a login is required. If so, suggest a new login + if (reasonSuggestsLogin(reasonCode)) { + if (!_hasCheckedForAccessToken) { + accountManager->checkAndSignalForAccessToken(); + _hasCheckedForAccessToken = true; + } - static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; + static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3; - // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts - if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { - accountManager->generateNewUserKeypair(); - _connectionDenialsSinceKeypairRegen = 0; + // force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts + if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) { + accountManager->generateNewUserKeypair(); + _connectionDenialsSinceKeypairRegen = 0; + } } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 03141d8fef..4d8505e549 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,6 +84,15 @@ public: bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); } void softReset(); + + enum class ConnectionRefusedReason : uint8_t { + Unknown, + ProtocolMismatch, + LoginError, + NotAuthorized, + TooManyUsers + }; + public slots: void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); @@ -115,9 +124,10 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reason); + void domainConnectionRefused(QString reasonMessage, ConnectionRefusedReason reason = ConnectionRefusedReason::Unknown); private: + bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); void sendDisconnectPacket(); void hardReset(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 2c10d0627e..714b69fd89 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -162,13 +162,17 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { } bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { + //qDebug() << __FUNCTION__; return packetVersionMatch(packet) && packetSourceAndHashMatch(packet); } bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); + //qDebug() << __FUNCTION__ << "headerType:" << headerType << "version:" << (int)headerVersion; + if (headerVersion != versionForPacketType(headerType)) { static QMultiHash sourcedVersionDebugSuppressMap; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 0cbe9668b3..3b648a138b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -221,6 +221,10 @@ public: void setConnectionMaxBandwidth(int maxBandwidth) { _nodeSocket.setConnectionMaxBandwidth(maxBandwidth); } + void setPacketFilterOperator(udt::PacketFilterOperator filterOperator) { _nodeSocket.setPacketFilterOperator(filterOperator); } + bool packetVersionMatch(const udt::Packet& packet); + bool isPacketVerified(const udt::Packet& packet); + public slots: void reset(); void eraseAllNodes(); @@ -267,8 +271,6 @@ protected: void setLocalSocket(const HifiSockAddr& sockAddr); - bool isPacketVerified(const udt::Packet& packet); - bool packetVersionMatch(const udt::Packet& packet); bool packetSourceAndHashMatch(const udt::Packet& packet); void processSTUNResponse(std::unique_ptr packet); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 575a2c7a9c..93f8659663 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -24,8 +24,8 @@ int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) { return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type); } -std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) { - auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage)); +std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) { + auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage, version)); packet->open(QIODevice::ReadWrite); @@ -61,13 +61,12 @@ std::unique_ptr NLPacket::createCopy(const NLPacket& other) { return std::unique_ptr(new NLPacket(other)); } -NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) : +NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage, PacketVersion version) : Packet((size == -1) ? -1 : NLPacket::localHeaderSize(type) + size, isReliable, isPartOfMessage), _type(type), - _version(versionForPacketType(type)) + _version((version == 0) ? versionForPacketType(type) : version) { adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); - writeTypeAndVersion(); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 4527094322..f49f8498a5 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -38,7 +38,7 @@ public: sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; static std::unique_ptr create(PacketType type, qint64 size = -1, - bool isReliable = false, bool isPartOfMessage = false); + bool isReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); @@ -73,7 +73,7 @@ public: protected: - NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false); + NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false, PacketVersion version = 0); NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); NLPacket(const NLPacket& other); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index c295ffc700..50677908a5 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -292,7 +292,9 @@ void NodeList::sendDomainServerCheckIn() { return; } - auto domainPacket = NLPacket::create(domainPacketType); + auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0; + //qDebug() << __FUNCTION__ << " NLPacket::create() version:" << (int)packetVersion; + auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion); QDataStream packetStream(domainPacket.get()); @@ -312,12 +314,28 @@ void NodeList::sendDomainServerCheckIn() { // pack the connect UUID for this connect request packetStream << connectUUID; + + // include the protocol version signature in our connect request + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { + QByteArray protocolVersionSig = protocolVersionsSignature(); + packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); + //qDebug() << __FUNCTION__ << " including protocol version --------------------------"; + } else { + //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasProtocolVersions - not including protocol version"; + } + } else { + //qDebug() << __FUNCTION__ << "NOT a DomainConnnectRequest ----------- not including checkin details -------"; } // pack our data to send to the domain-server including // the hostname information (so the domain-server can see which place name we came in on) - packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList() - << DependencyManager::get()->getPlaceName(); + packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); + if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasHostname)) { + packetStream << DependencyManager::get()->getPlaceName(); + //qDebug() << __FUNCTION__ << " including host name --------------------------"; + } else { + //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasHostname - not including host name"; + } if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); @@ -345,6 +363,7 @@ void NodeList::sendDomainServerCheckIn() { // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; + //qDebug() << __FUNCTION__ << " _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns << " --------------------------"; } } @@ -504,15 +523,22 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { + //qDebug() << __FUNCTION__; + if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS return; } + //qDebug() << __FUNCTION__ << "_numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; + // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; + //qDebug() << __FUNCTION__ << "RESET.... _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; + // emit our signal so listeners know we just heard from the DS + //qDebug() << __FUNCTION__ << "about to emit receivedDomainServerList() -----------------------------------------------"; emit receivedDomainServerList(); DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 4b196d5f7b..3158262c87 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -68,6 +68,13 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + /// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers + void downgradeDomainServerCheckInVersion() { + qDebug() << __FUNCTION__ << "----------------------------------------------------------"; + _domainConnectRequestVersion--; + + } + public slots: void reset(); void sendDomainServerCheckIn(); @@ -85,6 +92,12 @@ public slots: void processICEPingPacket(QSharedPointer message); + void resetDomainServerCheckInVersion() + { + qDebug() << __FUNCTION__ << "----------------------------------------------------------"; + _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); + } + signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); @@ -123,6 +136,8 @@ private: HifiSockAddr _assignmentServerSocket; bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; + + PacketVersion _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 530efc5fb3..cb47625d6d 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -309,22 +309,29 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); - + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); - + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + } else { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage)); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } } else { listenerIsDead = true; } } else { + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "Got verified unsourced packet list." << "packetType:" << packetType; + // qDebug() << "Got verified unsourced packet list: " << QString(nlPacketList->getMessage()); emit dataReceived(NodeType::Unassigned, receivedMessage->getSize()); @@ -332,6 +339,8 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei if (listener.object) { success = listener.method.invoke(listener.object, Q_ARG(QSharedPointer, receivedMessage)); + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else { listenerIsDead = true; } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b04d582d6d..72875322f6 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -12,7 +12,9 @@ #include "PacketHeaders.h" #include +#include +#include #include #include @@ -58,9 +60,13 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + + case PacketType::DomainConnectionDenied: + return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); + case PacketType::DomainConnectRequest: - // addition of referring hostname information - return 18; + return static_cast(DomainConnectRequestVersion::HasProtocolVersions); + default: return 17; } @@ -80,3 +86,36 @@ QDebug operator<<(QDebug debug, const PacketType& type) { debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } + +#if (PR_BUILD || DEV_BUILD) +static bool sendWrongProtocolVersion = false; +void sendWrongProtocolVersionsSignature(bool sendWrongVersion) { + sendWrongProtocolVersion = sendWrongVersion; +} +#endif + +QByteArray protocolVersionsSignature() { + static QByteArray protocolVersionSignature; + static std::once_flag once; + std::call_once(once, [&] { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + uint8_t numberOfProtocols = static_cast(PacketType::LAST_PACKET_TYPE) + 1; + stream << numberOfProtocols; + for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) { + uint8_t packetTypeVersion = static_cast(versionForPacketType(static_cast(packetType))); + stream << packetTypeVersion; + } + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(buffer); + protocolVersionSignature = hash.result(); + }); + + #if (PR_BUILD || DEV_BUILD) + if (sendWrongProtocolVersion) { + return QByteArray("INCORRECTVERSION"); // only for debugging version checking + } + #endif + + return protocolVersionSignature; +} diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 030b4af8c9..b6237e74d6 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -61,7 +61,7 @@ public: AssignmentClientStatus, NoisyMute, AvatarIdentity, - AvatarBillboard, + TYPE_UNUSED_1, DomainConnectRequest, DomainServerRequireDTLS, NodeJsonStats, @@ -94,7 +94,8 @@ public: ICEServerHeartbeatDenied, AssetMappingOperation, AssetMappingOperationReply, - ICEServerHeartbeatACK + ICEServerHeartbeatACK, + LAST_PACKET_TYPE = ICEServerHeartbeatACK }; }; @@ -109,6 +110,11 @@ extern const QSet NON_SOURCED_PACKETS; extern const QSet RELIABLE_PACKETS; PacketVersion versionForPacketType(PacketType packetType); +QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols + +#if (PR_BUILD || DEV_BUILD) +void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation +#endif uint qHash(const PacketType& key, uint seed); QDebug operator<<(QDebug debug, const PacketType& type); @@ -179,4 +185,15 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarEntities }; +enum class DomainConnectRequestVersion : PacketVersion { + NoHostname = 17, + HasHostname, + HasProtocolVersions +}; + +enum class DomainConnectionDeniedVersion : PacketVersion { + ReasonMessageOnly = 17, + IncludesReasonCode +}; + #endif // hifi_PacketHeaders_h From 12a1857280f835db093cd12ece0cd1583d43520b Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 15:20:11 -0700 Subject: [PATCH 232/264] check point with protocol refusal working --- domain-server/src/DomainGatekeeper.cpp | 17 +++++++- interface/src/Application.cpp | 13 +++++- interface/src/Application.h | 1 + .../src/scripting/WindowScriptingInterface.h | 2 +- libraries/networking/src/DomainHandler.cpp | 41 ++++++++++++++++--- libraries/networking/src/DomainHandler.h | 2 +- libraries/networking/src/PacketReceiver.cpp | 10 ++--- 7 files changed, 72 insertions(+), 14 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9d5ee75818..80137935ca 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -548,12 +548,27 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H quint16 payloadSize = utfString.size(); // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, payloadSize + sizeof(payloadSize)); + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied); // , payloadSize + sizeof(payloadSize) // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); + qDebug() << __FUNCTION__ << "about to write reasonCode:" << (int)reasonCode; + uint8_t reasonCodeWire = (uint8_t)reasonCode; + qDebug() << __FUNCTION__ << "about to write reasonCodeWire:" << (int)reasonCodeWire; + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); + connectionDeniedPacket->writePrimitive(reasonCodeWire); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); + qDebug() << __FUNCTION__ << "about to write payloadSize:" << payloadSize; + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); connectionDeniedPacket->writePrimitive(payloadSize); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); + qDebug() << __FUNCTION__ << "about to write utfString:" << utfString; + qDebug() << __FUNCTION__ << "about to write utfString.size():" << utfString.size(); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); connectionDeniedPacket->write(utfString); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); + } // send the packet off diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eca3ecc679..58a273737c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -630,8 +630,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); - connect(&domainHandler, &DomainHandler::resetting, nodeList.data(), &NodeList::resetDomainServerCheckInVersion); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; @@ -1068,6 +1068,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : firstRun.set(false); } +void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { + qDebug() << __FUNCTION__ << "message:" << reasonMessage << "code:" << reasonCode; + qDebug() << __FUNCTION__ << "DomainHandler::ConnectionRefusedReason::ProtocolMismatch:" << (int)DomainHandler::ConnectionRefusedReason::ProtocolMismatch; + + if (static_cast(reasonCode) == DomainHandler::ConnectionRefusedReason::ProtocolMismatch) { + qDebug() << __FUNCTION__ << " PROTOCOL MISMATCH!!!"; + notifyPacketVersionMismatch(); + } +} + + QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; diff --git a/interface/src/Application.h b/interface/src/Application.h index edb69eb2ad..69f48e9541 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -330,6 +330,7 @@ private slots: static void packetSent(quint64 length); void updateDisplayMode(); void updateInputModes(); + void domainConnectionRefused(const QString& reasonMessage, int reason); private: static void initDisplay(); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 145d17faaf..72f4ccd866 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -47,7 +47,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reasonMessage, DomainHandler::ConnectionRefusedReason reason = DomainHandler::ConnectionRefusedReason::Unknown); + void domainConnectionRefused(const QString& reasonMessage, int reason); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d80d4e76fd..67a41c866a 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -103,7 +103,9 @@ void DomainHandler::hardReset() { _sockAddr.clear(); _hasCheckedForAccessToken = false; - _domainConnectionRefusals.clear(); + + //qDebug() << __FUNCTION__ << "about to call _domainConnectionRefusals.clear();"; + //_domainConnectionRefusals.clear(); // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); @@ -142,6 +144,10 @@ void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const // set the new hostname _hostname = hostname; + // FIXME - is this the right place??? + qDebug() << __FUNCTION__ << "about to call _domainConnectionRefusals.clear();"; + _domainConnectionRefusals.clear(); + qCDebug(networking) << "Updated domain hostname to" << _hostname; // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname @@ -366,25 +372,50 @@ bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer message) { // Read deny reason from packet - ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown; + uint8_t reasonCodeWire; + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); + message->readPrimitive(&reasonCodeWire); + qDebug() << __FUNCTION__ << "reasonCodeWire:" << reasonCodeWire; + ConnectionRefusedReason reasonCode = static_cast(reasonCodeWire); + qDebug() << __FUNCTION__ << "reasonCode:" << (int)reasonCode; + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); + quint16 reasonSize; message->readPrimitive(&reasonSize); - QString reasonMessage = QString::fromUtf8(message->readWithoutCopy(reasonSize)); + qDebug() << __FUNCTION__ << "reasonSize:" << reasonSize; + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); + auto reasonText = message->readWithoutCopy(reasonSize); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "reasonText:" << reasonText; + QString reasonMessage = QString::fromUtf8(reasonText); + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "reasonMessage:" << reasonMessage; + + qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; - qCWarning(networking) << "Make sure you are logged in."; + + qDebug(networking) << "_domainConnectionRefusals:" << _domainConnectionRefusals; if (!_domainConnectionRefusals.contains(reasonMessage)) { + qDebug(networking) << "about to call _domainConnectionRefusals.append(reasonMessage);"; _domainConnectionRefusals.append(reasonMessage); - emit domainConnectionRefused(reasonMessage, reasonCode); + qDebug(networking) << "_domainConnectionRefusals:" << _domainConnectionRefusals; + + + emit domainConnectionRefused(reasonMessage, (int)reasonCode); + } else { + qDebug(networking) << "ALREADY EMITTED domainConnectionRefused() ----------------------------"; } auto accountManager = DependencyManager::get(); // Some connection refusal reasons imply that a login is required. If so, suggest a new login if (reasonSuggestsLogin(reasonCode)) { + qCWarning(networking) << "Make sure you are logged in."; + if (!_hasCheckedForAccessToken) { accountManager->checkAndSignalForAccessToken(); _hasCheckedForAccessToken = true; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 41a3e544f6..226186f1d0 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -124,7 +124,7 @@ signals: void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); - void domainConnectionRefused(QString reasonMessage, ConnectionRefusedReason reason = ConnectionRefusedReason::Unknown); + void domainConnectionRefused(QString reasonMessage, int reason); private: bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index cb47625d6d..8df9a1038a 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -310,7 +310,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, @@ -318,19 +318,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage)); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } } else { listenerIsDead = true; } } else { - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "Got verified unsourced packet list." << "packetType:" << packetType; + //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "Got verified unsourced packet list." << "packetType:" << packetType; // qDebug() << "Got verified unsourced packet list: " << QString(nlPacketList->getMessage()); emit dataReceived(NodeType::Unassigned, receivedMessage->getSize()); @@ -340,7 +340,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei success = listener.method.invoke(listener.object, Q_ARG(QSharedPointer, receivedMessage)); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; + //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else { listenerIsDead = true; } From b9aa667c558be8c59dc58667d8656a696d5902f8 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 25 May 2016 10:37:30 +1200 Subject: [PATCH 233/264] Fix QML test environment --- tests/ui/qml/main.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 1745658193..97f0c0a613 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -23,14 +23,17 @@ ApplicationWindow { Desktop { id: desktop anchors.fill: parent - rootMenu: StubMenu { id: rootMenu } + + //rootMenu: StubMenu { id: rootMenu } //Component.onCompleted: offscreenWindow = appWindow + /* MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); } + */ Row { id: testButtons From 2eef07e414854815b17ef8a155ea5dce9939dae9 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 15:38:08 -0700 Subject: [PATCH 234/264] cleanup and dead code removal --- domain-server/src/DomainGatekeeper.cpp | 26 +------------------ domain-server/src/DomainServer.cpp | 13 +--------- domain-server/src/NodeConnectionData.cpp | 5 ++-- interface/src/Application.cpp | 13 ---------- interface/src/Application.h | 4 +-- .../src/scripting/WindowScriptingInterface.h | 4 +-- libraries/networking/src/DomainHandler.cpp | 24 ----------------- libraries/networking/src/LimitedNodeList.cpp | 4 --- libraries/networking/src/NLPacket.cpp | 1 + libraries/networking/src/NodeList.cpp | 17 ------------ libraries/networking/src/NodeList.h | 13 +++------- libraries/networking/src/PacketReceiver.cpp | 11 -------- 12 files changed, 11 insertions(+), 124 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 80137935ca..bc89b99e8a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -55,10 +55,6 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } - - //qDebug() << __FUNCTION__ << "packetVersion:" << message->getVersion(); - - QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it @@ -72,8 +68,6 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSenderSockAddr(); @@ -548,27 +537,14 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H quint16 payloadSize = utfString.size(); // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied); // , payloadSize + sizeof(payloadSize) + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied); // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); - qDebug() << __FUNCTION__ << "about to write reasonCode:" << (int)reasonCode; uint8_t reasonCodeWire = (uint8_t)reasonCode; - qDebug() << __FUNCTION__ << "about to write reasonCodeWire:" << (int)reasonCodeWire; - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); connectionDeniedPacket->writePrimitive(reasonCodeWire); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); - qDebug() << __FUNCTION__ << "about to write payloadSize:" << payloadSize; - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); connectionDeniedPacket->writePrimitive(payloadSize); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); - qDebug() << __FUNCTION__ << "about to write utfString:" << utfString; - qDebug() << __FUNCTION__ << "about to write utfString.size():" << utfString.size(); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); connectionDeniedPacket->write(utfString); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "connectionDeniedPacket->getDataSize():" << connectionDeniedPacket->getDataSize(); - } // send the packet off diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 18ca7e2941..f6fbb3f470 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -309,8 +309,6 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); - //qDebug() << __FUNCTION__ << "type:" << headerType << "version:" << (int)headerVersion; - auto nodeList = DependencyManager::get(); // This implements a special case that handles OLD clients which don't know how to negotiate matching @@ -320,9 +318,6 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { // warn the user that the protocol is not compatible if (headerType == PacketType::DomainConnectRequest && headerVersion < static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { - - //qDebug() << __FUNCTION__ << "OLD VERSION checkin sending an intentional bad packet -------------------------------"; - auto packetWithBadVersion = NLPacket::create(PacketType::EntityData); nodeList->sendPacket(std::move(packetWithBadVersion), packet.getSenderSockAddr()); return false; @@ -407,8 +402,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); - // set packetVersionMatch as the verify packet operator for the udt::Socket - //using std::placeholders::_1; + // set a custum packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } @@ -701,8 +695,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { - //qDebug() << __FUNCTION__ << "---------------"; - QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); @@ -782,9 +774,6 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode) { } void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr) { - - //qDebug() << __FUNCTION__ << "---------------"; - const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NUM_BYTES_RFC4122_UUID + 2; // setup the extended header for the domain list packets diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 5ddcbf1792..13bb9123d8 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -24,10 +24,11 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c char* rawBytes; uint length; - // FIXME -- do we need to delete the rawBytes after it's been copied into the QByteArray? dataStream.readBytes(rawBytes, length); newHeader.protocolVersion = QByteArray(rawBytes, length); - //qDebug() << __FUNCTION__ << "...got protocol version from node... version:" << newHeader.protocolVersion; + + // NOTE: QDataStream::readBytes() - The buffer is allocated using new []. Destroy it with the delete [] operator. + delete[] rawBytes; } dataStream >> newHeader.nodeType diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 58a273737c..bf897015f8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -654,11 +654,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); - connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, this, &Application::limitOfSilentDomainCheckInsReached); - //connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); - - connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager @@ -1069,16 +1065,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { - qDebug() << __FUNCTION__ << "message:" << reasonMessage << "code:" << reasonCode; - qDebug() << __FUNCTION__ << "DomainHandler::ConnectionRefusedReason::ProtocolMismatch:" << (int)DomainHandler::ConnectionRefusedReason::ProtocolMismatch; - if (static_cast(reasonCode) == DomainHandler::ConnectionRefusedReason::ProtocolMismatch) { - qDebug() << __FUNCTION__ << " PROTOCOL MISMATCH!!!"; notifyPacketVersionMismatch(); } } - QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; @@ -4595,12 +4586,8 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const { // We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we // need to be downgraded to talk to it). void Application::limitOfSilentDomainCheckInsReached() { - //qDebug() << __FUNCTION__; - auto nodeList = DependencyManager::get(); - nodeList->downgradeDomainServerCheckInVersion(); // attempt to use an older domain checkin version - nodeList->reset(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 69f48e9541..a17250a58e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -262,9 +262,7 @@ public slots: void setActiveFaceTracker() const; #if (PR_BUILD || DEV_BUILD) - void sendWrongProtocolVersionsSignature(bool checked) { - ::sendWrongProtocolVersionsSignature(checked); - } + void sendWrongProtocolVersionsSignature(bool checked) { ::sendWrongProtocolVersionsSignature(checked); } #endif #ifdef HAVE_IVIEWHMD diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 72f4ccd866..dfe02a5064 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -16,8 +16,6 @@ #include #include -#include - class WebWindowClass; class WindowScriptingInterface : public QObject, public Dependency { @@ -47,7 +45,7 @@ public slots: signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); - void domainConnectionRefused(const QString& reasonMessage, int reason); + void domainConnectionRefused(const QString& reasonMessage, int reasonCode); private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 67a41c866a..1efcfc7f27 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -104,9 +104,6 @@ void DomainHandler::hardReset() { _hasCheckedForAccessToken = false; - //qDebug() << __FUNCTION__ << "about to call _domainConnectionRefusals.clear();"; - //_domainConnectionRefusals.clear(); - // clear any pending path we may have wanted to ask the previous DS about _pendingPath.clear(); } @@ -145,7 +142,6 @@ void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const _hostname = hostname; // FIXME - is this the right place??? - qDebug() << __FUNCTION__ << "about to call _domainConnectionRefusals.clear();"; _domainConnectionRefusals.clear(); qCDebug(networking) << "Updated domain hostname to" << _hostname; @@ -374,40 +370,20 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointergetPosition():" << message->getPosition(); message->readPrimitive(&reasonCodeWire); - qDebug() << __FUNCTION__ << "reasonCodeWire:" << reasonCodeWire; ConnectionRefusedReason reasonCode = static_cast(reasonCodeWire); - qDebug() << __FUNCTION__ << "reasonCode:" << (int)reasonCode; - - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); - quint16 reasonSize; message->readPrimitive(&reasonSize); - qDebug() << __FUNCTION__ << "reasonSize:" << reasonSize; - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); auto reasonText = message->readWithoutCopy(reasonSize); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "reasonText:" << reasonText; QString reasonMessage = QString::fromUtf8(reasonText); - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "reasonMessage:" << reasonMessage; - - qDebug() << __FUNCTION__ << "line:" << __LINE__ << "message->getPosition():" << message->getPosition(); // output to the log so the user knows they got a denied connection request // and check and signal for an access token so that we can make sure they are logged in qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage; - qDebug(networking) << "_domainConnectionRefusals:" << _domainConnectionRefusals; - if (!_domainConnectionRefusals.contains(reasonMessage)) { - qDebug(networking) << "about to call _domainConnectionRefusals.append(reasonMessage);"; _domainConnectionRefusals.append(reasonMessage); - qDebug(networking) << "_domainConnectionRefusals:" << _domainConnectionRefusals; - - emit domainConnectionRefused(reasonMessage, (int)reasonCode); - } else { - qDebug(networking) << "ALREADY EMITTED domainConnectionRefused() ----------------------------"; } auto accountManager = DependencyManager::get(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 714b69fd89..2c10d0627e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -162,17 +162,13 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { } bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { - //qDebug() << __FUNCTION__; return packetVersionMatch(packet) && packetSourceAndHashMatch(packet); } bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { - PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); - //qDebug() << __FUNCTION__ << "headerType:" << headerType << "version:" << (int)headerVersion; - if (headerVersion != versionForPacketType(headerType)) { static QMultiHash sourcedVersionDebugSuppressMap; diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 93f8659663..34a159ae6c 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -67,6 +67,7 @@ NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfM _version((version == 0) ? versionForPacketType(type) : version) { adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); + writeTypeAndVersion(); } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5f3f34dafb..082200fccc 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -293,7 +293,6 @@ void NodeList::sendDomainServerCheckIn() { } auto packetVersion = (domainPacketType == PacketType::DomainConnectRequest) ? _domainConnectRequestVersion : 0; - //qDebug() << __FUNCTION__ << " NLPacket::create() version:" << (int)packetVersion; auto domainPacket = NLPacket::create(domainPacketType, -1, false, false, packetVersion); QDataStream packetStream(domainPacket.get()); @@ -319,12 +318,7 @@ void NodeList::sendDomainServerCheckIn() { if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasProtocolVersions)) { QByteArray protocolVersionSig = protocolVersionsSignature(); packetStream.writeBytes(protocolVersionSig.constData(), protocolVersionSig.size()); - //qDebug() << __FUNCTION__ << " including protocol version --------------------------"; - } else { - //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasProtocolVersions - not including protocol version"; } - } else { - //qDebug() << __FUNCTION__ << "NOT a DomainConnnectRequest ----------- not including checkin details -------"; } // pack our data to send to the domain-server including @@ -332,9 +326,6 @@ void NodeList::sendDomainServerCheckIn() { packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); if (_domainConnectRequestVersion >= static_cast(DomainConnectRequestVersion::HasHostname)) { packetStream << DependencyManager::get()->getPlaceName(); - //qDebug() << __FUNCTION__ << " including host name --------------------------"; - } else { - //qDebug() << __FUNCTION__ << "_domainConnectRequestVersion less than HasHostname - not including host name"; } if (!_domainHandler.isConnected()) { @@ -363,7 +354,6 @@ void NodeList::sendDomainServerCheckIn() { // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; - //qDebug() << __FUNCTION__ << " _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns << " --------------------------"; } if (!_publicSockAddr.isNull() && !_domainHandler.isConnected() && !_domainHandler.getPendingDomainID().isNull()) { @@ -531,22 +521,15 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { - //qDebug() << __FUNCTION__; - if (_domainHandler.getSockAddr().isNull()) { // refuse to process this packet if we aren't currently connected to the DS return; } - //qDebug() << __FUNCTION__ << "_numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; - // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; - //qDebug() << __FUNCTION__ << "RESET.... _numNoReplyDomainCheckIns:" << _numNoReplyDomainCheckIns; - // emit our signal so listeners know we just heard from the DS - //qDebug() << __FUNCTION__ << "about to emit receivedDomainServerList() -----------------------------------------------"; emit receivedDomainServerList(); DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 3158262c87..b269554e77 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -69,11 +69,7 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } /// downgrades the DomainConnnectRequest PacketVersion to attempt to probe for older domain servers - void downgradeDomainServerCheckInVersion() { - qDebug() << __FUNCTION__ << "----------------------------------------------------------"; - _domainConnectRequestVersion--; - - } + void downgradeDomainServerCheckInVersion() { _domainConnectRequestVersion--; } public slots: void reset(); @@ -92,11 +88,8 @@ public slots: void processICEPingPacket(QSharedPointer message); - void resetDomainServerCheckInVersion() - { - qDebug() << __FUNCTION__ << "----------------------------------------------------------"; - _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); - } + void resetDomainServerCheckInVersion() + { _domainConnectRequestVersion = versionForPacketType(PacketType::DomainConnectRequest); } signals: void limitOfSilentDomainCheckInsReached(); diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 8df9a1038a..9cbff8abbd 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -309,29 +309,20 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); - - //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; - } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); - - //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; - } else { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage)); - //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } } else { listenerIsDead = true; } } else { - //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "Got verified unsourced packet list." << "packetType:" << packetType; - // qDebug() << "Got verified unsourced packet list: " << QString(nlPacketList->getMessage()); emit dataReceived(NodeType::Unassigned, receivedMessage->getSize()); @@ -339,8 +330,6 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei if (listener.object) { success = listener.method.invoke(listener.object, Q_ARG(QSharedPointer, receivedMessage)); - - //qDebug() << __FUNCTION__ << "line:" << __LINE__ << "success:" << success << "packetType:" << packetType; } else { listenerIsDead = true; } From 0553ff5f9fc0c7edcbd24e0e4e1c6f14b6a7b561 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 15:41:11 -0700 Subject: [PATCH 235/264] cleanup and dead code removal --- libraries/networking/src/PacketReceiver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 9cbff8abbd..423e753ae0 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -309,11 +309,13 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); + } else { success = metaMethod.invoke(listener.object, connectionType, From 3b0081fbbba9276029f5b37a8efc11ecfd03eaea Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 15:42:55 -0700 Subject: [PATCH 236/264] cleanup and dead code removal --- libraries/networking/src/PacketReceiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 423e753ae0..87c77967a5 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -315,7 +315,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(QSharedPointer, matchingNode)); - + } else { success = metaMethod.invoke(listener.object, connectionType, From 5668db9e4a6f54e4971fe34f9abce516f365b867 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 15:43:30 -0700 Subject: [PATCH 237/264] cleanup and dead code removal --- libraries/networking/src/PacketReceiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 87c77967a5..530efc5fb3 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -309,7 +309,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei connectionType, Q_ARG(QSharedPointer, receivedMessage), Q_ARG(SharedNodePointer, matchingNode)); - + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { success = metaMethod.invoke(listener.object, connectionType, From 183f38e4f06c28fa69bcd4dfba5bcc4b669c3d65 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 16:07:57 -0700 Subject: [PATCH 238/264] pop up warning if domain over capacity --- domain-server/src/DomainGatekeeper.cpp | 4 ++-- interface/src/Application.cpp | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index bc89b99e8a..9023510214 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -443,10 +443,10 @@ bool DomainGatekeeper::isWithinMaxCapacity(const QString& username, const QByteA // find out what our maximum capacity is const QVariant* maximumUserCapacityVariant = valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; - + if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); - + if (connectedUsers >= maximumUserCapacity) { // too many users, deny the new connection unless this user is an allowed editor diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf897015f8..f3322caffb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1065,8 +1065,20 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : } void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { - if (static_cast(reasonCode) == DomainHandler::ConnectionRefusedReason::ProtocolMismatch) { - notifyPacketVersionMismatch(); + switch (static_cast(reasonCode)) { + case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: + notifyPacketVersionMismatch(); + break; + case DomainHandler::ConnectionRefusedReason::TooManyUsers: + case DomainHandler::ConnectionRefusedReason::Unknown: { + QString message = "Unable to connect to the location you are visiting.\n"; + message += reasonMessage; + OffscreenUi::warning("", message); + break; + } + default: + // nothing to do. + break; } } From a3f1ece978987b203b50e438628b417fdfa1823e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 24 May 2016 16:10:12 -0700 Subject: [PATCH 239/264] Do button mapping in C++ and simplify vive.json accordingly. Vive and Hydra now use PrimaryThumb and SecondaryThumb. Fix warnings for Neuron. --- interface/resources/controllers/hydra.json | 10 +++---- interface/resources/controllers/neuron.json | 4 +-- interface/resources/controllers/vive.json | 10 ++++--- .../src/controllers/StandardControls.h | 4 +++ plugins/openvr/src/ViveControllerManager.cpp | 22 ++++++++++++++- plugins/openvr/src/ViveControllerManager.h | 1 + .../controllers/handControllerPointer.js | 28 +++---------------- 7 files changed, 42 insertions(+), 37 deletions(-) diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 8233685763..066676140c 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -16,12 +16,10 @@ { "from": "Hydra.L0", "to": "Standard.Back" }, { "from": "Hydra.R0", "to": "Standard.Start" }, - { "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftPrimaryThumb" }, - { "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightPrimaryThumb" }, - { "from": [ "Hydra.L3" ], "to": "Standard.L3" }, - { "from": [ "Hydra.R3" ], "to": "Standard.R3" }, - { "from": [ "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" }, - { "from": [ "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L3" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" }, + { "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" }, { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, { "from": "Hydra.RightHand", "to": "Standard.RightHand" } diff --git a/interface/resources/controllers/neuron.json b/interface/resources/controllers/neuron.json index 2d61f80c35..d0962c72db 100644 --- a/interface/resources/controllers/neuron.json +++ b/interface/resources/controllers/neuron.json @@ -1,7 +1,7 @@ { "name": "Neuron to Standard", "channels": [ - { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, - { "from": "Hydra.RightHand", "to": "Standard.RightHand" } + { "from": "Neuron.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Neuron.RightHand", "to": "Standard.RightHand" } ] } diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index fec93c9132..60a46ba3ce 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,23 +1,25 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LS", "filters": ["invert" ,{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RS", "filters": ["invert", { "type": "deadZone", "min": 0.6 }], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RS", "filters": [{ "type": "deadZone", "min": 0.6 }], "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RightGrip", "to": "Standard.RB" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, + { "from": "Vive.LSCenter", "to": "Standard.LeftPrimaryThumb" }, { "from": "Vive.LeftApplicationMenu", "to": "Standard.LeftSecondaryThumb" }, + { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index f101ba6c51..79c23bc6ee 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -43,6 +43,8 @@ namespace controller { LEFT_SECONDARY_THUMB_TOUCH, LS_TOUCH, LEFT_THUMB_UP, + LS_CENTER, + LS_OUTER, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -50,6 +52,8 @@ namespace controller { RIGHT_SECONDARY_THUMB_TOUCH, RS_TOUCH, RIGHT_THUMB_UP, + RS_CENTER, + RS_OUTER, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 12567b10d1..6e75454b5f 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -282,7 +282,22 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { handleAxisEvent(deltaTime, i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, isLeftHand); } - } + + // pseudo buttons the depend on both of the above for-loops + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_OUTER); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_OUTER); + } + } +} + +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int outerPseudoButton) { + // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. + const float CENTER_DEADBAND = 0.6f; + if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { + float absX = abs(_axisStateMap[xAxis]); + float absY = abs(_axisStateMap[yAxis]); + bool isCenter = (absX < CENTER_DEADBAND) && (absY < CENTER_DEADBAND); // square deadband + _buttonPressedMap.insert(isCenter ? centerPseudoButton : outerPseudoButton); } } @@ -443,6 +458,11 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI // touch pad press makePair(LS, "LS"), makePair(RS, "RS"), + // Differentiate where we are in the touch pad click + makePair(LS_CENTER, "LSCenter"), + makePair(LS_OUTER, "LSOuter"), + makePair(RS_CENTER, "RSCenter"), + makePair(RS_OUTER, "RSOuter"), // triggers makePair(LT, "LT"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 672ad59cfe..bd5d4a39f4 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,6 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); + void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int outerPseudoButton); class FilteredStick { public: diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index e7be3af5dd..ca3b5e8cf2 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -266,39 +266,19 @@ function toggleHand() { } } -// Create clickMappings as needed, on demand. var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(clickMapping.disable); -// Move these to vive.json -function makeCenterClickWhen(click, x, y) { - var clickKey = Controller.Standard[click], - xKey = Controller.Standard[x], // Standard after filtering by mapping - yKey = Controller.Standard[y]; - return function () { - var clickValue = Controller.getValue(clickKey); - var xValue = Controller.getValue(xKey); - var yValue = Controller.getValue(yKey); - var answer = clickValue && !xValue && !yValue; - return answer; - }; -} -if (Controller.Hardware.Vive) { - clickMapping.from(Controller.Hardware.Vive.RS).when(makeCenterClickWhen('RS', 'RX', 'RY')).to(Controller.Standard.R3); - clickMapping.from(Controller.Hardware.Vive.LS).when(makeCenterClickWhen('LS', 'LX', 'LY')).to(Controller.Standard.L3); -} - - -clickMapping.from(Controller.Standard.R3).peek().to(Controller.Actions.ReticleClick); -clickMapping.from(Controller.Standard.L3).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); -clickMapping.from(Controller.Standard.R3).peek().to(function (on) { +clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(function (on) { if (on && (activeHand !== Controller.Standard.RightHand)) { toggleHand(); } }); -clickMapping.from(Controller.Standard.L3).peek().to(function (on) { +clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(function (on) { if (on && (activeHand !== Controller.Standard.LeftHand)) { toggleHand(); } From 66a90cc3f8ad1e415ae38f29c025793ef6d478d5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 25 May 2016 11:31:39 +1200 Subject: [PATCH 240/264] Make file browser dialog resizable --- interface/resources/qml/dialogs/FileDialog.qml | 2 +- interface/resources/qml/windows-uit/ModalFrame.qml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 5cd972a38f..0b539f9667 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -25,7 +25,7 @@ import "fileDialog" //FIXME implement shortcuts for favorite location ModalWindow { id: root - //resizable: true + resizable: true implicitWidth: 640 implicitHeight: 480 diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml index 77344829d5..415ae03284 100644 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ b/interface/resources/qml/windows-uit/ModalFrame.qml @@ -18,13 +18,13 @@ Frame { HifiConstants { id: hifi } Rectangle { - id: modalFrame + id: frameContent readonly property bool hasTitle: window.title != "" anchors { fill: parent - topMargin: -hifi.dimensions.modalDialogMargin.y - (modalFrame.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + topMargin: -hifi.dimensions.modalDialogMargin.y - (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) leftMargin: -hifi.dimensions.modalDialogMargin.x rightMargin: -hifi.dimensions.modalDialogMargin.x bottomMargin: -hifi.dimensions.modalDialogMargin.y @@ -38,7 +38,7 @@ Frame { color: hifi.colors.faintGray Item { - visible: modalFrame.hasTitle + visible: frameContent.hasTitle anchors.fill: parent anchors { topMargin: -parent.anchors.topMargin From fb9f6185db724c0fc3f30ec46ad70353e3a848de Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 17:19:53 -0700 Subject: [PATCH 241/264] CR feedback --- domain-server/src/DomainGatekeeper.cpp | 5 +++-- interface/src/Application.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 9023510214..b940d46849 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -535,9 +535,10 @@ void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const H // this is an agent and we've decided we won't let them connect - send them a packet to deny connection QByteArray utfString = reason.toUtf8(); quint16 payloadSize = utfString.size(); - + // setup the DomainConnectionDenied packet - auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied); + auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied, + payloadSize + sizeof(payloadSize) + sizeof(uint8_t)); // pack in the reason the connection was denied (the client displays this) if (payloadSize > 0) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3322caffb..48b418b93c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4594,7 +4594,7 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const { // If we're not getting anything back from the domain server checkin, it might be that the domain speaks an -// older version of the DomainConnectRequest protocal. We will attempt to send and older version of DomainConnectRequest. +// older version of the DomainConnectRequest protocol. We will attempt to send and older version of DomainConnectRequest. // We won't actually complete the connection, but if the server responds, we know that it needs to be upgraded (or we // need to be downgraded to talk to it). void Application::limitOfSilentDomainCheckInsReached() { From ff45633c21ca3f9a496fa620e5fb7490968af874 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 24 May 2016 17:36:52 -0700 Subject: [PATCH 242/264] bump entities version/CR feedback --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 9de149d32d..a960edbdc8 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_NO_FLY_ZONES; + return VERSION_ENTITIES_MORE_SHAPES; case PacketType::AvatarData: case PacketType::BulkAvatarData: return static_cast(AvatarMixerPacketVersion::AvatarEntities); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b6237e74d6..a04a737c41 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -178,6 +178,7 @@ const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55; const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; +const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, From 1c4eed640fcdd1747484e383b8d59b4f092dbaea Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 25 May 2016 13:10:26 +1200 Subject: [PATCH 243/264] Fix up file browser resize handle and outlining --- .../resources/qml/dialogs/FileDialog.qml | 2 ++ .../qml/styles-uit/HifiConstants.qml | 1 + .../qml/windows-uit/DefaultFrame.qml | 24 ++++++++++------ interface/resources/qml/windows-uit/Frame.qml | 28 +++++++++---------- .../resources/qml/windows-uit/ModalFrame.qml | 13 ++++++--- .../resources/qml/windows-uit/ModalWindow.qml | 2 ++ .../resources/qml/windows-uit/Window.qml | 1 + 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 0b539f9667..7921e2549d 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -29,6 +29,8 @@ ModalWindow { implicitWidth: 640 implicitHeight: 480 + minSize: Qt.vector2d(300, 240) + HifiConstants { id: hifi } Settings { diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 16f150b2d9..f2698da574 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -141,6 +141,7 @@ Item { readonly property real textPadding: 8 readonly property real sliderHandleSize: 18 readonly property real sliderGrooveHeight: 8 + readonly property real frameIconSize: 22 readonly property real spinnerSize: 50 readonly property real tablePadding: 12 readonly property real tableRowHeight: largeScreen ? 26 : 23 diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml index 04905656ce..6334086e4e 100644 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ b/interface/resources/qml/windows-uit/DefaultFrame.qml @@ -20,6 +20,14 @@ Frame { Rectangle { // Dialog frame id: frameContent + + readonly property int iconSize: hifi.dimensions.frameIconSize + readonly property int frameMargin: 9 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: 2 * frameMargin + iconSize + readonly property int frameMarginBottom: iconSize + 11 + anchors { topMargin: -frameMarginTop leftMargin: -frameMarginLeft @@ -45,17 +53,17 @@ Frame { anchors { right: parent.right; top: parent.top; - topMargin: frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameMarginRight; + topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: frameContent.frameMarginRight; } - spacing: iconSize / 4 + spacing: frameContent.iconSize / 4 HiFiGlyphs { // "Pin" button visible: false text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize + size: frameContent.iconSize MouseArea { id: pinClickArea anchors.fill: parent @@ -70,7 +78,7 @@ Frame { visible: window ? window.closable : false text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: iconSize + size: frameContent.iconSize MouseArea { id: closeClickArea anchors.fill: parent @@ -85,11 +93,11 @@ Frame { id: titleText anchors { left: parent.left - leftMargin: frameMarginLeft + hifi.dimensions.contentMargin.x + leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x right: controlsRow.left - rightMargin: iconSize + rightMargin: frameContent.iconSize top: parent.top - topMargin: frameMargin + topMargin: frameContent.frameMargin } text: window ? window.title : "" color: hifi.colors.white diff --git a/interface/resources/qml/windows-uit/Frame.qml b/interface/resources/qml/windows-uit/Frame.qml index f21097ea62..9519a44cf0 100644 --- a/interface/resources/qml/windows-uit/Frame.qml +++ b/interface/resources/qml/windows-uit/Frame.qml @@ -22,12 +22,10 @@ Item { property bool gradientsSupported: desktop.gradientsSupported - readonly property int iconSize: 22 - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 + readonly property int frameMarginLeft: frameContent.frameMarginLeft + readonly property int frameMarginRight: frameContent.frameMarginRight + readonly property int frameMarginTop: frameContent.frameMarginTop + readonly property int frameMarginBottom: frameContent.frameMarginBottom // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes @@ -76,8 +74,8 @@ Item { id: sizeOutline x: -frameMarginLeft y: -frameMarginTop - width: window ? window.width + frameMarginLeft + frameMarginRight : 0 - height: window ? window.height + frameMarginTop + frameMarginBottom : 0 + width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 + height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 color: hifi.colors.baseGrayHighlight15 border.width: 3 border.color: hifi.colors.white50 @@ -88,11 +86,11 @@ Item { MouseArea { // Resize handle id: sizeDrag - width: iconSize - height: iconSize + width: hifi.dimensions.frameIconSize + height: hifi.dimensions.frameIconSize enabled: window ? window.resizable : false hoverEnabled: true - x: window ? window.width + frameMarginRight - iconSize : 0 + x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 y: window ? window.height + 4 : 0 property vector2d pressOrigin property vector2d sizeOrigin @@ -124,10 +122,12 @@ Item { HiFiGlyphs { visible: sizeDrag.enabled x: -11 // Move a little to visually align - y: -4 // "" + y: window.modality == Qt.ApplicationModal ? -6 : -4 text: hifi.glyphs.resizeHandle - size: iconSize + 10 - color: sizeDrag.containsMouse || sizeDrag.pressed ? hifi.colors.white : hifi.colors.white50 + size: hifi.dimensions.frameIconSize + 10 + color: sizeDrag.containsMouse || sizeDrag.pressed + ? hifi.colors.white + : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) } } } diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml index 415ae03284..13c560a519 100644 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ b/interface/resources/qml/windows-uit/ModalFrame.qml @@ -22,12 +22,17 @@ Frame { readonly property bool hasTitle: window.title != "" + readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y + anchors { fill: parent - topMargin: -hifi.dimensions.modalDialogMargin.y - (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) - leftMargin: -hifi.dimensions.modalDialogMargin.x - rightMargin: -hifi.dimensions.modalDialogMargin.x - bottomMargin: -hifi.dimensions.modalDialogMargin.y + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom } border { diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml index af099eb275..6b007160d5 100644 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ b/interface/resources/qml/windows-uit/ModalWindow.qml @@ -19,4 +19,6 @@ Window { destroyOnCloseButton: true destroyOnInvisible: true frame: ModalFrame{} + + property int colorScheme: hifi.colorSchemes.light } diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml index e9477f3c7e..d614b21ce2 100644 --- a/interface/resources/qml/windows-uit/Window.qml +++ b/interface/resources/qml/windows-uit/Window.qml @@ -52,6 +52,7 @@ Fadable { // property bool pinned: false property bool resizable: false property bool gradientsSupported: desktop.gradientsSupported + property int colorScheme: hifi.colorSchemes.dark property vector2d minSize: Qt.vector2d(100, 100) property vector2d maxSize: Qt.vector2d(1280, 800) From ea8ba7e2d19fe6dbb7a047048e291aa91609ae72 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 24 May 2016 18:12:40 -0700 Subject: [PATCH 244/264] Fixes based on code review feedback. * Use isNaN instead of glm::isnan * prefer abort() over writing to a nullptr, in release assert. * warn if packet size does not match expectations --- libraries/avatars/src/AvatarData.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index cc61036915..16e4bd5437 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -92,7 +92,7 @@ namespace AvatarDataPacket { */ } -#define ASSERT(COND) do { if (!(COND)) { int* bad = nullptr; *bad = 0xbad; } } while(0) +#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0) AvatarData::AvatarData() : SpatiallyNestable(NestableType::Avatar, QUuid()), @@ -442,7 +442,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { glm::vec3 position = glm::vec3(header->position[0], header->position[1], header->position[2]); _globalPosition = glm::vec3(header->globalPosition[0], header->globalPosition[1], header->globalPosition[2]); - if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { + if (isNaN(position)) { if (shouldLogError(now)) { qCWarning(avatars) << "Discard AvatarData packet: position NaN, uuid " << getSessionUUID(); } @@ -454,7 +454,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { unpackFloatAngleFromTwoByte(header->localOrientation + 0, &yaw); unpackFloatAngleFromTwoByte(header->localOrientation + 1, &pitch); unpackFloatAngleFromTwoByte(header->localOrientation + 2, &roll); - if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { + if (isNaN(yaw) || isNaN(pitch) || isNaN(roll)) { if (shouldLogError(now)) { qCWarning(avatars) << "Discard AvatarData packet: localOriention is NaN, uuid " << getSessionUUID(); } @@ -471,7 +471,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { float scale; unpackFloatRatioFromTwoByte((uint8_t*)&header->scale, scale); - if (glm::isnan(scale)) { + if (isNaN(scale)) { if (shouldLogError(now)) { qCWarning(avatars) << "Discard AvatarData packet: scale NaN, uuid " << getSessionUUID(); } @@ -480,7 +480,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _targetScale = std::max(MIN_AVATAR_SCALE, std::min(MAX_AVATAR_SCALE, scale)); glm::vec3 lookAt = glm::vec3(header->lookAtPosition[0], header->lookAtPosition[1], header->lookAtPosition[2]); - if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { + if (isNaN(lookAt)) { if (shouldLogError(now)) { qCWarning(avatars) << "Discard AvatarData packet: lookAtPosition is NaN, uuid " << getSessionUUID(); } @@ -489,7 +489,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->_lookAtPosition = lookAt; float audioLoudness = header->audioLoudness; - if (glm::isnan(audioLoudness)) { + if (isNaN(audioLoudness)) { if (shouldLogError(now)) { qCWarning(avatars) << "Discard AvatarData packet: audioLoudness is NaN, uuid " << getSessionUUID(); } @@ -522,8 +522,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto parentInfo = reinterpret_cast(sourceBuffer); sourceBuffer += sizeof(AvatarDataPacket::ParentInfo); - const size_t RFC_4122_SIZE = 16; - QByteArray byteArray((const char*)parentInfo->parentUUID, RFC_4122_SIZE); + QByteArray byteArray((const char*)parentInfo->parentUUID, NUM_BYTES_RFC4122_UUID); _parentID = QUuid::fromRfc4122(byteArray); _parentJointIndex = parentInfo->parentJointIndex; } else { @@ -634,8 +633,11 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { int numBytesRead = sourceBuffer - startPosition; - // AJT: Maybe make this a warning. - ASSERT(numBytesRead == buffer.size()); + if (numBytesRead != buffer.size()) { + if (shouldLogError(now)) { + qCWarning(avatars) << "AvatarData packet size mismatch: expected " << numBytesRead << " received " << buffer.size(); + } + } _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; From 9ec6b774608026ef53d9d14e050b8b85e4997003 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 25 May 2016 13:49:01 +1200 Subject: [PATCH 245/264] Make file browser dialog movable --- interface/resources/qml/dialogs/FileDialog.qml | 1 + interface/resources/qml/windows-uit/DefaultFrame.qml | 2 +- interface/resources/qml/windows-uit/ModalFrame.qml | 7 +++++++ interface/resources/qml/windows-uit/ModalWindow.qml | 6 ++++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 7921e2549d..00a66c01cc 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -30,6 +30,7 @@ ModalWindow { implicitHeight: 480 minSize: Qt.vector2d(300, 240) + draggable: true HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml index 6334086e4e..84f435480b 100644 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ b/interface/resources/qml/windows-uit/DefaultFrame.qml @@ -42,7 +42,7 @@ Frame { } radius: hifi.dimensions.borderRadius - // Allow dragging of the window + // Enable dragging of the window MouseArea { anchors.fill: parent drag.target: window diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml index 13c560a519..44c0b6a456 100644 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ b/interface/resources/qml/windows-uit/ModalFrame.qml @@ -42,6 +42,13 @@ Frame { radius: hifi.dimensions.borderRadius color: hifi.colors.faintGray + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + enabled: window.draggable + } + Item { visible: frameContent.hasTitle anchors.fill: parent diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml index 6b007160d5..f429e98ac3 100644 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ b/interface/resources/qml/windows-uit/ModalWindow.qml @@ -14,11 +14,13 @@ import "." Window { id: window - anchors.centerIn: parent modality: Qt.ApplicationModal destroyOnCloseButton: true destroyOnInvisible: true - frame: ModalFrame{} + frame: ModalFrame { } property int colorScheme: hifi.colorSchemes.light + property bool draggable: false + + anchors.centerIn: draggable ? undefined : parent } From ba77aaf7efdb0607001d9f2f6a16b8d03460f6e9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 May 2016 11:06:08 -0700 Subject: [PATCH 246/264] remove collision of isPlaying property and slot --- libraries/script-engine/src/ScriptAudioInjector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 0d16b26fdf..4de12af62c 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -19,7 +19,7 @@ class ScriptAudioInjector : public QObject { Q_OBJECT - Q_PROPERTY(bool isPlaying READ isPlaying) + Q_PROPERTY(bool playing READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: From 64720444cf4527f10eebb360018269af366cb72c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 May 2016 11:11:06 -0700 Subject: [PATCH 247/264] correct existing uses of the isPlaying property --- script-archive/FlockOfbirds.js | 84 +++++++++---------- .../ACAudioSearchAndInject.js | 6 +- script-archive/avatarSelector.js | 4 +- script-archive/baseball/baseballCrowd.js | 4 +- script-archive/controllers/hydra/airGuitar.js | 17 ++-- script-archive/drylake/ratCreator.js | 2 +- script-archive/entityScripts/movable.js | 58 ++++++------- script-archive/example/audio/birdSongs.js | 40 ++++----- script-archive/lobby.js | 72 ++++++++-------- script-archive/playTestSound.js | 25 +++--- 10 files changed, 155 insertions(+), 157 deletions(-) diff --git a/script-archive/FlockOfbirds.js b/script-archive/FlockOfbirds.js index f466fa2909..c2fb54f0a6 100644 --- a/script-archive/FlockOfbirds.js +++ b/script-archive/FlockOfbirds.js @@ -3,8 +3,8 @@ // examples // // Copyright 2014 High Fidelity, Inc. -// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined -// at the start of the script. +// Creates a flock of birds that fly around and chirp, staying inside the corners of the box defined +// at the start of the script. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,12 +13,12 @@ // The rectangular area in the domain where the flock will fly var lowerCorner = { x: 0, y: 0, z: 0 }; var upperCorner = { x: 30, y: 10, z: 30 }; -var STARTING_FRACTION = 0.25; +var STARTING_FRACTION = 0.25; var NUM_BIRDS = 50; var UPDATE_INTERVAL = 0.016; -var playSounds = true; -var SOUND_PROBABILITY = 0.001; +var playSounds = true; +var SOUND_PROBABILITY = 0.001; var STARTING_LIFETIME = (1.0 / SOUND_PROBABILITY) * UPDATE_INTERVAL * 10; var numPlaying = 0; var BIRD_SIZE = 0.08; @@ -36,17 +36,17 @@ var ALIGNMENT_FORCE = 1.5; var COHESION_FORCE = 1.0; var MAX_COHESION_VELOCITY = 0.5; -var followBirds = false; +var followBirds = false; var AVATAR_FOLLOW_RATE = 0.001; var AVATAR_FOLLOW_VELOCITY_TIMESCALE = 2.0; var AVATAR_FOLLOW_ORIENTATION_RATE = 0.005; -var floor = false; +var floor = false; var MAKE_FLOOR = false; var averageVelocity = { x: 0, y: 0, z: 0 }; var averagePosition = { x: 0, y: 0, z: 0 }; -var birdsLoaded = false; +var birdsLoaded = false; var oldAvatarOrientation; var oldAvatarPosition; @@ -79,10 +79,10 @@ function updateBirds(deltaTime) { birds[i].entityId = false; return; } - // Sum up average position and velocity + // Sum up average position and velocity if (Vec3.length(properties.velocity) > MIN_ALIGNMENT_VELOCITY) { sumVelocity = Vec3.sum(sumVelocity, properties.velocity); - birdVelocitiesCounted += 1; + birdVelocitiesCounted += 1; } sumPosition = Vec3.sum(sumPosition, properties.position); birdPositionsCounted += 1; @@ -93,10 +93,10 @@ function updateBirds(deltaTime) { var randomVelocity = randomVector(RANDOM_FLAP_VELOCITY); randomVelocity.y = FLAP_UP + Math.random() * FLAP_UP; - // Alignment Velocity - var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); + // Alignment Velocity + var alignmentVelocityMagnitude = Math.min(MAX_ALIGNMENT_VELOCITY, Vec3.length(Vec3.multiply(ALIGNMENT_FORCE, averageVelocity))); var alignmentVelocity = Vec3.multiply(alignmentVelocityMagnitude, Vec3.normalize(averageVelocity)); - alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; + alignmentVelocity.y *= VERTICAL_ALIGNMENT_COUPLING; // Cohesion var distanceFromCenter = Vec3.length(Vec3.subtract(averagePosition, properties.position)); @@ -107,10 +107,10 @@ function updateBirds(deltaTime) { Entities.editEntity(birds[i].entityId, { velocity: Vec3.sum(properties.velocity, newVelocity) }); - } + } // Check whether to play a chirp - if (playSounds && (!birds[i].audioId || !birds[i].audioId.isPlaying) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { + if (playSounds && (!birds[i].audioId || !birds[i].audioId.playing) && (Math.random() < ((numPlaying > 0) ? SOUND_PROBABILITY / numPlaying : SOUND_PROBABILITY))) { var options = { position: properties.position, volume: BIRD_MASTER_VOLUME @@ -126,43 +126,43 @@ function updateBirds(deltaTime) { // Change size, and update lifetime to keep bird alive Entities.editEntity(birds[i].entityId, { dimensions: Vec3.multiply(1.5, properties.dimensions), lifetime: properties.ageInSeconds + STARTING_LIFETIME}); - + } else if (birds[i].audioId) { - // If bird is playing a chirp - if (!birds[i].audioId.isPlaying) { + // If bird is playing a chirp + if (!birds[i].audioId.playing) { Entities.editEntity(birds[i].entityId, { dimensions: { x: BIRD_SIZE, y: BIRD_SIZE, z: BIRD_SIZE }}); numPlaying--; - } + } } // Keep birds in their 'cage' var bounce = false; - var newVelocity = properties.velocity; - var newPosition = properties.position; + var newVelocity = properties.velocity; + var newPosition = properties.position; if (properties.position.x < lowerCorner.x) { - newPosition.x = lowerCorner.x; + newPosition.x = lowerCorner.x; newVelocity.x *= -1.0; bounce = true; } else if (properties.position.x > upperCorner.x) { - newPosition.x = upperCorner.x; + newPosition.x = upperCorner.x; newVelocity.x *= -1.0; bounce = true; } if (properties.position.y < lowerCorner.y) { - newPosition.y = lowerCorner.y; + newPosition.y = lowerCorner.y; newVelocity.y *= -1.0; bounce = true; } else if (properties.position.y > upperCorner.y) { - newPosition.y = upperCorner.y; + newPosition.y = upperCorner.y; newVelocity.y *= -1.0; bounce = true; - } + } if (properties.position.z < lowerCorner.z) { - newPosition.z = lowerCorner.z; + newPosition.z = lowerCorner.z; newVelocity.z *= -1.0; bounce = true; } else if (properties.position.z > upperCorner.z) { - newPosition.z = upperCorner.z; + newPosition.z = upperCorner.z; newVelocity.z *= -1.0; bounce = true; } @@ -171,7 +171,7 @@ function updateBirds(deltaTime) { } } } - // Update average velocity and position of flock + // Update average velocity and position of flock if (birdVelocitiesCounted > 0) { averageVelocity = Vec3.multiply(1.0 / birdVelocitiesCounted, sumVelocity); //print(Vec3.length(averageVelocity)); @@ -184,10 +184,10 @@ function updateBirds(deltaTime) { MyAvatar.orientation = Quat.mix(MyAvatar.orientation, birdDirection, AVATAR_FOLLOW_ORIENTATION_RATE); } } - } + } if (birdPositionsCounted > 0) { averagePosition = Vec3.multiply(1.0 / birdPositionsCounted, sumPosition); - // If Following birds, update position + // If Following birds, update position if (followBirds) { MyAvatar.position = Vec3.sum(Vec3.multiply(AVATAR_FOLLOW_RATE, MyAvatar.position), Vec3.multiply(1.0 - AVATAR_FOLLOW_RATE, averagePosition)); } @@ -211,12 +211,12 @@ Script.scriptEnding.connect(function() { }); function loadBirds(howMany) { - oldAvatarOrientation = MyAvatar.orientation; + oldAvatarOrientation = MyAvatar.orientation; oldAvatarPosition = MyAvatar.position; var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw"]; /* Here are more sounds/species you can use - , "mexicanWhipoorwill.raw", + , "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -252,19 +252,19 @@ function loadBirds(howMany) { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < howMany; i++) { var whichBird = Math.floor(Math.random() * sound_filenames.length); - var position = { - x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, - y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, + var position = { + x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION, + y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION, z: lowerCorner.z + (upperCorner.z - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION - }; + }; birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[whichBird]), entityId: Entities.addEntity({ type: "Sphere", position: position, @@ -282,8 +282,8 @@ function loadBirds(howMany) { } if (MAKE_FLOOR) { var FLOOR_THICKNESS = 0.05; - floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, - y: lowerCorner.y, + floor = Entities.addEntity({ type: "Box", position: { x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0, + y: lowerCorner.y, z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0 }, dimensions: { x: (upperCorner.x - lowerCorner.x), y: FLOOR_THICKNESS, z: (upperCorner.z - lowerCorner.z)}, color: {red: 100, green: 100, blue: 100} diff --git a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js index 381a7ee902..30567b4fc7 100644 --- a/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js +++ b/script-archive/audioExamples/acAudioSearching/ACAudioSearchAndInject.js @@ -49,7 +49,7 @@ function debug() { // Display the arguments not just [Object object]. EntityViewer.setCenterRadius(QUERY_RADIUS); // ENTITY DATA CACHE -// +// var entityCache = {}; // A dictionary of unexpired EntityData objects. var examinationCount = 0; function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about. @@ -146,7 +146,7 @@ function EntityDatum(entityIdentifier) { // Just the data of an entity that we n return; } that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC - if (!that.injector.isPlaying) { // Subtle: a looping sound will not check playbackGap. + if (!that.injector.playing) { // Subtle: a looping sound will not check playbackGap. if (repeat()) { // WAITING => PLAYING // Setup next play just once, now. Changes won't be looked at while we wait. that.playAfter = randomizedNextPlay(); @@ -208,7 +208,7 @@ function updateAllEntityData() { // A fast update of all entities we know about. stats.entities++; if (datum.url) { stats.sounds++; - if (datum.injector && datum.injector.isPlaying) { + if (datum.injector && datum.injector.playing) { stats.playing++; } } diff --git a/script-archive/avatarSelector.js b/script-archive/avatarSelector.js index dc2916a1a8..47740ef0b3 100644 --- a/script-archive/avatarSelector.js +++ b/script-archive/avatarSelector.js @@ -283,7 +283,7 @@ function actionStartEvent(event) { if (avatarIndex < avatars.length) { var actionPlace = avatars[avatarIndex]; - print("Changing avatar to " + actionPlace.name + print("Changing avatar to " + actionPlace.name + " after click on panel " + panelIndex + " with avatar index " + avatarIndex); MyAvatar.useFullAvatarURL(actionPlace.content_url); @@ -395,7 +395,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/baseball/baseballCrowd.js b/script-archive/baseball/baseballCrowd.js index de9b53f9ec..1459ce6e67 100644 --- a/script-archive/baseball/baseballCrowd.js +++ b/script-archive/baseball/baseballCrowd.js @@ -21,7 +21,7 @@ var CHATTER_VOLUME = 0.20 var EXTRA_VOLUME = 0.25 function playChatter() { - if (chatter.downloaded && !chatter.isPlaying) { + if (chatter.downloaded && !chatter.playing) { Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME }); } } @@ -31,7 +31,7 @@ chatter.ready.connect(playChatter); var currentInjector = null; function playRandomExtras() { - if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) { + if ((!currentInjector || !currentInjector.playing) && (Math.random() < (1.0 / 1800.0))) { // play a random extra sound about every 30s currentInjector = Audio.playSound( extras[Math.floor(Math.random() * extras.length)], diff --git a/script-archive/controllers/hydra/airGuitar.js b/script-archive/controllers/hydra/airGuitar.js index f8606808c1..73c7099eed 100644 --- a/script-archive/controllers/hydra/airGuitar.js +++ b/script-archive/controllers/hydra/airGuitar.js @@ -22,12 +22,12 @@ function printVector(v) { return; } -function vMinus(a, b) { +function vMinus(a, b) { var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; return rval; } -// The model file to be used for the guitar +// The model file to be used for the guitar var guitarModel = HIFI_PUBLIC_BUCKET + "models/attachments/guitar.fst"; // Load sounds that will be played @@ -47,7 +47,7 @@ chords[6] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Me chords[7] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+E+short.raw"); chords[8] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Metal+G+short.raw"); -// Steel Guitar +// Steel Guitar chords[9] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+A.raw"); chords[10] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+B.raw"); chords[11] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Steel+E.raw"); @@ -83,8 +83,8 @@ if (leftHanded) { } var lastPosition = { x: 0.0, - y: 0.0, - z: 0.0 }; + y: 0.0, + z: 0.0 }; var audioInjector = null; var selectorPressed = false; @@ -106,7 +106,7 @@ function checkHands(deltaTime) { var chord = Controller.getValue(chordTrigger); if (volume > 1.0) volume = 1.0; - if ((chord > 0.1) && audioInjector && audioInjector.isPlaying) { + if ((chord > 0.1) && audioInjector && audioInjector.playing) { // If chord finger trigger pulled, stop current chord print("stopping chord because cord trigger pulled"); audioInjector.stop(); @@ -119,7 +119,7 @@ function checkHands(deltaTime) { guitarSelector += NUM_CHORDS; if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { guitarSelector = 0; - } + } print("new guitarBase: " + guitarSelector); stopAudio(true); selectorPressed = true; @@ -160,7 +160,7 @@ function checkHands(deltaTime) { } function stopAudio(killInjector) { - if (audioInjector && audioInjector.isPlaying) { + if (audioInjector && audioInjector.playing) { print("stopped sound"); audioInjector.stop(); } @@ -212,4 +212,3 @@ function scriptEnding() { Script.update.connect(checkHands); Script.scriptEnding.connect(scriptEnding); Controller.keyPressEvent.connect(keyPressEvent); - diff --git a/script-archive/drylake/ratCreator.js b/script-archive/drylake/ratCreator.js index 60ccf1a1a3..6f6b322f84 100644 --- a/script-archive/drylake/ratCreator.js +++ b/script-archive/drylake/ratCreator.js @@ -340,7 +340,7 @@ function moveRats() { var metaRat = getMetaRatByRat(rat); if (metaRat !== undefined) { if (metaRat.injector !== undefined) { - if (metaRat.injector.isPlaying === true) { + if (metaRat.injector.playing === true) { metaRat.injector.options = { loop: true, position: ratPosition diff --git a/script-archive/entityScripts/movable.js b/script-archive/entityScripts/movable.js index b7ecfbbc8e..06b30ce15e 100644 --- a/script-archive/entityScripts/movable.js +++ b/script-archive/entityScripts/movable.js @@ -8,7 +8,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function(){ +(function(){ this.entityID = null; this.properties = null; @@ -30,13 +30,13 @@ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" ]; - + this.turnSoundURLS = [ "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove1.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove2.wav", "http://public.highfidelity.io/sounds/MovingFurniture/FurnitureMove3.wav" - + // TODO: determine if these or other turn sounds work better than move sounds. //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn1.wav", //"http://public.highfidelity.io/sounds/MovingFurniture/FurnitureTurn2.wav", @@ -50,7 +50,7 @@ this.turnSound = null; this.moveInjector = null; this.turnInjector = null; - + var debug = false; var displayRotateTargets = true; // change to false if you don't want the rotate targets var rotateOverlayTargetSize = 10000; // really big target @@ -61,12 +61,12 @@ var yawZero; var rotationNormal; var yawNormal; - var stopSoundDelay = 100; // number of msecs of not moving to have sound stop - + var stopSoundDelay = 100; // number of msecs of not moving to have sound stop + this.getRandomInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; - } - + } + this.downloadSounds = function() { for (var i = 0; i < this.moveSoundURLS.length; i++) { this.moveSounds[i] = SoundCache.getSound(this.moveSoundURLS[i]); @@ -95,7 +95,7 @@ if (debug) { print("playMoveSound() --- calling this.moveInjector = Audio.playSound(this.moveSound...)"); } - + if (!this.moveInjector) { this.moveInjector = Audio.playSound(this.moveSound, { position: this.properties.position, loop: true, volume: 0.1 }); } else { @@ -148,7 +148,7 @@ var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, this.properties.position, upVector); - + var newPosition = Vec3.sum(intersection, this.graboffset); Entities.editEntity(this.entityID, { position: newPosition }); }; @@ -158,7 +158,7 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var upVector = { x: 0, y: 1, z: 0 }; var intersection = this.rayPlaneIntersection(pickRay.origin, pickRay.direction, - this.properties.position, upVector); + this.properties.position, upVector); this.graboffset = Vec3.subtract(this.properties.position, intersection); }; @@ -183,18 +183,18 @@ this.lastMovedPosition.y = mouseEvent.y; } } - + this.move = function(mouseEvent) { this.updatePosition(mouseEvent); - if (this.moveInjector === null || !this.moveInjector.isPlaying) { + if (this.moveInjector === null || !this.moveInjector.playing) { this.playMoveSound(); } }; - + this.release = function(mouseEvent) { this.updatePosition(mouseEvent); }; - + this.rotate = function(mouseEvent) { var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); @@ -205,7 +205,7 @@ var centerToZero = Vec3.subtract(center, zero); var centerToIntersect = Vec3.subtract(center, result.intersection); var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - + var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = false; // var innerRadius = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; @@ -213,10 +213,10 @@ angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; snapToInner = true; } - + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); Entities.editEntity(this.entityID, { rotation: Quat.multiply(yawChange, this.originalRotation) }); - + // update the rotation display accordingly... var startAtCurrent = 360-angleFromZero; @@ -245,7 +245,7 @@ } } - if (this.turnInjector === null || !this.turnInjector.isPlaying) { + if (this.turnInjector === null || !this.turnInjector.playing) { this.playTurnSound(); } }; @@ -267,7 +267,7 @@ this.rotateOverlayOuter = null; this.rotateOverlayCurrent = null; } - + this.displayRotateOverlay = function(mouseEvent) { var yawOverlayAngles = { x: 90, y: 0, z: 0 }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); @@ -356,14 +356,14 @@ var pickRay = Camera.computePickRay(mouseEvent.x, mouseEvent.y) var result = Overlays.findRayIntersection(pickRay); yawZero = result.intersection; - + }; - + this.preload = function(entityID) { this.updateProperties(entityID); // All callbacks start by updating the properties this.downloadSounds(); }; - + this.clickDownOnEntity = function(entityID, mouseEvent) { this.updateProperties(entityID); // All callbacks start by updating the properties this.grab(mouseEvent); @@ -372,13 +372,13 @@ var nowMSecs = nowDate.getTime(); this.clickedAt = nowMSecs; this.firstHolding = true; - + this.clicked.x = mouseEvent.x; this.clicked.y = mouseEvent.y; this.lastMovedPosition.x = mouseEvent.x; this.lastMovedPosition.y = mouseEvent.y; this.lastMovedMSecs = nowMSecs; - + this.pickRandomSounds(); }; @@ -391,7 +391,7 @@ if (this.clicked.x == mouseEvent.x && this.clicked.y == mouseEvent.y) { var d = new Date(); var now = d.getTime(); - + if (now - this.clickedAt > 500) { this.displayRotateOverlay(mouseEvent); this.firstHolding = false; @@ -402,13 +402,13 @@ this.firstHolding = false; } } - + if (this.rotateMode) { this.rotate(mouseEvent); } else { this.move(mouseEvent); } - + this.stopSoundIfNotMoving(mouseEvent); }; this.clickReleaseOnEntity = function(entityID, mouseEvent) { @@ -418,7 +418,7 @@ } else { this.release(mouseEvent); } - + if (this.rotateOverlayTarget != null) { this.cleanupRotateOverlay(); this.rotateMode = false; diff --git a/script-archive/example/audio/birdSongs.js b/script-archive/example/audio/birdSongs.js index 557fc81f5b..9e949a19ed 100644 --- a/script-archive/example/audio/birdSongs.js +++ b/script-archive/example/audio/birdSongs.js @@ -22,7 +22,7 @@ var BIRD_VELOCITY = 2.0; var LIGHT_RADIUS = 10.0; var BIRD_MASTER_VOLUME = 0.5; -var useLights = true; +var useLights = true; function randomVector(scale) { return { x: Math.random() * scale - scale / 2.0, y: Math.random() * scale - scale / 2.0, z: Math.random() * scale - scale / 2.0 }; @@ -33,11 +33,11 @@ function maybePlaySound(deltaTime) { // Set the location and other info for the sound to play var whichBird = Math.floor(Math.random() * birds.length); //print("playing sound # " + whichBird); - var position = { - x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), - y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), - z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) - }; + var position = { + x: lowerCorner.x + Math.random() * (upperCorner.x - lowerCorner.x), + y: lowerCorner.y + Math.random() * (upperCorner.y - lowerCorner.y), + z: lowerCorner.z + Math.random() * (upperCorner.z - lowerCorner.z) + }; var options = { position: position, volume: BIRD_MASTER_VOLUME @@ -63,31 +63,31 @@ function maybePlaySound(deltaTime) { constantAttenuation: 0, linearAttenuation: 4.0, - quadraticAttenuation: 2.0, + quadraticAttenuation: 2.0, lifetime: 10 }); } - + playing.push({ audioId: Audio.playSound(birds[whichBird].sound, options), entityId: entityId, lightId: lightId, color: birds[whichBird].color }); } if (playing.length != numPlaying) { numPlaying = playing.length; //print("number playing = " + numPlaying); - } + } for (var i = 0; i < playing.length; i++) { - if (!playing[i].audioId.isPlaying) { + if (!playing[i].audioId.playing) { Entities.deleteEntity(playing[i].entityId); if (useLights) { Entities.deleteEntity(playing[i].lightId); - } + } playing.splice(i, 1); } else { var loudness = playing[i].audioId.loudness; var newColor = { red: playing[i].color.red, green: playing[i].color.green, blue: playing[i].color.blue }; if (loudness > 0.05) { - newColor.red *= (1.0 - loudness); - newColor.green *= (1.0 - loudness); - newColor.blue *= (1.0 - loudness); + newColor.red *= (1.0 - loudness); + newColor.green *= (1.0 - loudness); + newColor.blue *= (1.0 - loudness); } var properties = Entities.getEntityProperties(playing[i].entityId); var newPosition = Vec3.sum(properties.position, randomVector(BIRD_VELOCITY * deltaTime)); @@ -120,7 +120,7 @@ Script.scriptEnding.connect(function() { }); function loadBirds() { - var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", + var sound_filenames = ["bushtit_1.raw", "bushtit_2.raw", "bushtit_3.raw", "mexicanWhipoorwill.raw", "rosyfacedlovebird.raw", "saysphoebe.raw", "westernscreechowl.raw", "bandtailedpigeon.wav", "bridledtitmouse.wav", "browncrestedflycatcher.wav", "commonnighthawk.wav", "commonpoorwill.wav", "doublecrestedcormorant.wav", "gambelsquail.wav", "goldcrownedkinglet.wav", "greaterroadrunner.wav","groovebilledani.wav","hairywoodpecker.wav", @@ -155,13 +155,13 @@ function loadBirds() { { red: 216, green: 153, blue: 99 }, { red: 242, green: 226, blue: 64 } ]; - + var SOUND_BASE_URL = "http://public.highfidelity.io/sounds/Animals/"; - + for (var i = 0; i < sound_filenames.length; i++) { birds.push({ - sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), - color: colors[i] + sound: SoundCache.getSound(SOUND_BASE_URL + sound_filenames[i]), + color: colors[i] }); } -} \ No newline at end of file +} diff --git a/script-archive/lobby.js b/script-archive/lobby.js index 3095740c93..6fa4a42cb6 100644 --- a/script-archive/lobby.js +++ b/script-archive/lobby.js @@ -88,19 +88,19 @@ var DRONE_VOLUME = 0.3; function drawLobby() { if (!panelWall) { print("Adding overlays for the lobby panel wall and orb shell."); - + var cameraEuler = Quat.safeEulerAngles(Camera.orientation); var towardsMe = Quat.angleAxis(cameraEuler.y + 180, { x: 0, y: 1, z: 0}); - + var orbPosition = Vec3.sum(Camera.position, Vec3.multiplyQbyV(towardsMe, ORB_SHIFT)); - + var panelWallProps = { url: LOBBY_PANEL_WALL_URL, position: Vec3.sum(orbPosition, Vec3.multiplyQbyV(towardsMe, panelsCenterShift)), rotation: towardsMe, dimensions: panelsDimensions }; - + var orbShellProps = { url: LOBBY_SHELL_URL, position: orbPosition, @@ -128,13 +128,13 @@ function drawLobby() { visible: false, isFacingAvatar: true }; - + avatarStickPosition = MyAvatar.position; - panelWall = Overlays.addOverlay("model", panelWallProps); + panelWall = Overlays.addOverlay("model", panelWallProps); orbShell = Overlays.addOverlay("model", orbShellProps); descriptionText = Overlays.addOverlay("text3d", descriptionTextProps); - + if (droneSound.downloaded) { // start the drone sound if (!currentDrone) { @@ -143,7 +143,7 @@ function drawLobby() { currentDrone.restart(); } } - + // start one of our muzak sounds playRandomMuzak(); } @@ -157,31 +157,31 @@ function changeLobbyTextures() { req.send(); places = JSON.parse(req.responseText).data.places; - + var NUM_PANELS = places.length; - var textureProp = { + var textureProp = { textures: {} }; - + for (var j = 0; j < NUM_PANELS; j++) { var panelIndex = placeIndexToPanelIndex(j); textureProp["textures"]["file" + panelIndex] = places[j].previews.lobby; }; - + Overlays.editOverlay(panelWall, textureProp); } var MUZAK_VOLUME = 0.1; -function playCurrentSound(secondOffset) { +function playCurrentSound(secondOffset) { if (currentSound == latinSound) { if (!latinInjector) { latinInjector = Audio.playSound(latinSound, { localOnly: true, secondOffset: secondOffset, volume: MUZAK_VOLUME }); } else { latinInjector.restart(); } - + currentMuzakInjector = latinInjector; } else if (currentSound == elevatorSound) { if (!elevatorInjector) { @@ -189,7 +189,7 @@ function playCurrentSound(secondOffset) { } else { elevatorInjector.restart(); } - + currentMuzakInjector = elevatorInjector; } } @@ -205,14 +205,14 @@ function playNextMuzak() { currentSound = latinSound; } } - + playCurrentSound(0); } } function playRandomMuzak() { currentSound = null; - + if (latinSound.downloaded && elevatorSound.downloaded) { currentSound = Math.random() < 0.5 ? latinSound : elevatorSound; } else if (latinSound.downloaded) { @@ -220,11 +220,11 @@ function playRandomMuzak() { } else if (elevatorSound.downloaded) { currentSound = elevatorSound; } - + if (currentSound) { // pick a random number of seconds from 0-10 to offset the muzak var secondOffset = Math.random() * 10; - + playCurrentSound(secondOffset); } else { currentMuzakInjector = null; @@ -233,36 +233,36 @@ function playRandomMuzak() { function cleanupLobby() { toggleEnvironmentRendering(true); - + // for each of the 21 placeholder textures, set them back to default so the cached model doesn't have changed textures var panelTexturesReset = {}; panelTexturesReset["textures"] = {}; - - for (var j = 0; j < MAX_NUM_PANELS; j++) { + + for (var j = 0; j < MAX_NUM_PANELS; j++) { panelTexturesReset["textures"]["file" + (j + 1)] = LOBBY_BLANK_PANEL_TEXTURE_URL; }; - + Overlays.editOverlay(panelWall, panelTexturesReset); - + Overlays.deleteOverlay(panelWall); Overlays.deleteOverlay(orbShell); Overlays.deleteOverlay(descriptionText); - + panelWall = false; orbShell = false; - + if (currentDrone) { currentDrone.stop(); currentDrone = null } - + if (currentMuzakInjector) { currentMuzakInjector.stop(); currentMuzakInjector = null; } - + places = {}; - + } function actionStartEvent(event) { @@ -271,19 +271,19 @@ function actionStartEvent(event) { // check if we hit a panel and if we should jump there var result = Overlays.findRayIntersection(event.actionRay); if (result.intersects && result.overlayID == panelWall) { - + var panelName = result.extraInfo; - + var panelStringIndex = panelName.indexOf("Panel"); if (panelStringIndex != -1) { var panelIndex = parseInt(panelName.slice(5)); var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - - print("Jumping to " + actionPlace.name + " at " + actionPlace.address + + print("Jumping to " + actionPlace.name + " at " + actionPlace.address + " after click on panel " + panelIndex + " with place index " + placeIndex); - + Window.location = actionPlace.address; maybeCleanupLobby(); } @@ -328,7 +328,7 @@ function handleLookAt(pickRay) { var placeIndex = panelIndexToPlaceIndex(panelIndex); if (placeIndex < places.length) { var actionPlace = places[placeIndex]; - + if (actionPlace.description == "") { Overlays.editOverlay(descriptionText, { text: actionPlace.name, visible: showText }); } else { @@ -378,7 +378,7 @@ function update(deltaTime) { Overlays.editOverlay(descriptionText, { position: textOverlayPosition() }); // if the reticle is up then we may need to play the next muzak - if (currentMuzakInjector && !currentMuzakInjector.isPlaying) { + if (currentMuzakInjector && !currentMuzakInjector.playing) { playNextMuzak(); } } diff --git a/script-archive/playTestSound.js b/script-archive/playTestSound.js index 318df6a257..573c8879c4 100644 --- a/script-archive/playTestSound.js +++ b/script-archive/playTestSound.js @@ -2,12 +2,12 @@ // playTestSound.js // examples // -// Created by Philip Rosedale +// Created by Philip Rosedale // Copyright 2014 High Fidelity, Inc. // -// Creates an object in front of you that changes color and plays a light -// at the start of a drum clip that loops. As you move away it will tell you in the -// log how many meters you are from the source. +// Creates an object in front of you that changes color and plays a light +// at the start of a drum clip that loops. As you move away it will tell you in the +// log how many meters you are from the source. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -17,7 +17,7 @@ var sound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Dru var position = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Quat.getFront(MyAvatar.orientation)); -var time; +var time; var soundPlaying = null; var baseColor = { red: 100, green: 100, blue: 100 }; @@ -38,8 +38,8 @@ var box = Entities.addEntity({ function checkSound(deltaTime) { var started = false; - if (!sound.downloaded) { - return; + if (!sound.downloaded) { + return; } if (soundPlaying == null) { soundPlaying = Audio.playSound(sound, { @@ -47,9 +47,9 @@ function checkSound(deltaTime) { volume: 1.0, loop: false } ); started = true; - } else if (!soundPlaying.isPlaying) { + } else if (!soundPlaying.playing) { soundPlaying.restart(); - started = true; + started = true; } if (started) { Entities.editEntity(box, { color: litColor }); @@ -67,19 +67,19 @@ function checkSound(deltaTime) { lifetime: lightTime / 1000 }); Script.setTimeout(resetColor, lightTime); - } + } var currentDistance = Vec3.distance(MyAvatar.position, position); if (Math.abs(currentDistance - distance) > 1.0) { print("Distance from source: " + currentDistance); distance = currentDistance; - } + } } function resetColor() { Entities.editEntity(box, { color: baseColor }); } - + function scriptEnding() { Entities.deleteEntity(box); if (soundPlaying) { @@ -93,4 +93,3 @@ function scriptEnding() { // Connect a call back that happens every frame Script.scriptEnding.connect(scriptEnding); Script.update.connect(checkSound); - From 8f3918b5baa01bf350253d5a0c4ccc6ced6edfaa Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 25 May 2016 11:21:05 -0700 Subject: [PATCH 248/264] Fix primitive shape collisions --- libraries/entities/src/ShapeEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 2527dedab2..c39a477a35 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -161,7 +161,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit // This value specifes how the shape should be treated by physics calculations. // For now, all polys will act as spheres ShapeType ShapeEntityItem::getShapeType() const { - return SHAPE_TYPE_ELLIPSOID; + return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_SPHERE; } void ShapeEntityItem::setColor(const rgbColor& value) { From 8198a1b4d110bd334fe7f272961148fa4caffa5f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 25 May 2016 11:30:45 -0700 Subject: [PATCH 249/264] Add preference to control behavior. --- interface/src/avatar/MyAvatar.cpp | 2 + interface/src/avatar/MyAvatar.h | 3 ++ interface/src/ui/OverlayConductor.cpp | 62 +++++++++++++------------- interface/src/ui/PreferencesDialog.cpp | 5 +++ 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 94b213420f..e1159cf962 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -733,6 +733,7 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); + settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving); settings.endGroup(); } @@ -855,6 +856,7 @@ void MyAvatar::loadData() { setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); + setSnapTurn(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); settings.endGroup(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 92f006feb8..135e9f6417 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -160,6 +160,8 @@ public: Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } + Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; } + Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; } // get/set avatar data void saveData(); @@ -396,6 +398,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; + bool _clearOverlayWhenDriving { true }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index fa74989f4f..cb211fd918 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -68,37 +68,39 @@ void OverlayConductor::update(float dt) { void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - float speed = glm::length(myAvatar->getVelocity()); - const float MIN_DRIVING = 0.2f; - const float MAX_NOT_DRIVING = 0.01f; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - bool nowDriving = _driving; // Assume current _driving mode unless... - if (speed > MIN_DRIVING) { // ... we're definitely moving... - nowDriving = true; - } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. - nowDriving = false; + if (myAvatar->getClearOverlayWhenDriving()) { + float speed = glm::length(myAvatar->getVelocity()); + const float MIN_DRIVING = 0.2f; + const float MAX_NOT_DRIVING = 0.01f; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; + bool nowDriving = _driving; // Assume current _driving mode unless... + if (speed > MIN_DRIVING) { // ... we're definitely moving... + nowDriving = true; + } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. + nowDriving = false; + } + // Check that we're in this new mode for long enough to really trigger a transition. + if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. + _timeInPotentialMode = 0; + } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. + _timeInPotentialMode = usecTimestampNow(); + } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + _timeInPotentialMode = 0; // a real transition + if (nowDriving) { + _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); + } else { // reset when coming out of driving + _mode = FLAT; // Seems appropriate to let things reset, below, after the following. + // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. + qApp->getActiveDisplayPlugin()->resetSensors(); + myAvatar->reset(true, false); + } + if (_wantsOverlays) { + setEnabled(!nowDriving, false); + } + _driving = nowDriving; + } // Else haven't accumulated enough time in new mode, but keep timing. } - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - if (nowDriving) { - _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - } else { // reset when coming out of driving - _mode = FLAT; // Seems appropriate to let things reset, below, after the following. - // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. - qApp->getActiveDisplayPlugin()->resetSensors(); - myAvatar->reset(true, false); - } - if (_wantsOverlays) { - setEnabled(!nowDriving, false); - } - _driving = nowDriving; - } // Else haven't accumulated enough time in new mode, but keep timing. Mode newMode; if (qApp->isHMDMode()) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 9b1146340e..8f60844cc3 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -61,6 +61,11 @@ void setupPreferences() { auto setter = [=](bool value) { myAvatar->setSnapTurn(value); }; preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } + { + auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); }; + auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); }; + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter)); + } { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; auto setter = [](const QString& value) { Snapshot::snapshotsLocation.set(value); }; From 3137cd64e1c9cb499e85dd579fdbf120bdbddf46 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 May 2016 11:40:17 -0700 Subject: [PATCH 250/264] clear AddressManager previous lookup on 404 --- libraries/networking/src/AddressManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 619a1d7903..80989acd2c 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -383,8 +383,12 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); if (errorReply.error() == QNetworkReply::ContentNotFoundError) { + // if this is a lookup that has no result, don't keep re-trying it + _previousLookup.clear(); + emit lookupResultIsNotFound(); } + emit lookupResultsFinished(); } From b0ce65ec0197e2c7154ca7f21573337638235063 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 25 May 2016 11:42:06 -0700 Subject: [PATCH 251/264] trying to mimic previous behavior more closely --- assignment-client/src/Agent.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 12 ++++--- libraries/script-engine/src/ScriptEngine.h | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 31 +++++++++++-------- libraries/script-engine/src/ScriptEngines.h | 1 + 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 65e193dec6..327f6de695 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -476,7 +476,7 @@ void Agent::aboutToFinish() { setIsAvatar(false);// will stop timers for sending identity packets if (_scriptEngine) { - _scriptEngine->stop(); + _scriptEngine->stop(false); } // our entity tree is going to go away so tell that to the EntityScriptingInterface diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 56f6438e70..bc3c6b2441 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -114,7 +114,7 @@ void EntityTreeRenderer::clear() { // Unload and stop the engine here (instead of in its deleter) to // avoid marshalling unload signals back to this thread _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(); + _entitiesScriptEngine->stop(false); } if (_wantScripts && !_shuttingDown) { diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e7938ac5a0..61ab8c0c63 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -290,7 +290,7 @@ void ScriptEngine::waitTillDoneRunning() { assert(workerThread != QThread::currentThread()); // Engine should be stopped already, but be defensive - stop(); + stop(false); auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { @@ -941,13 +941,15 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { } -void ScriptEngine::stop() { +void ScriptEngine::stop(bool marshal) { _isStopping = true; // this can be done on any thread // marshal us over to the correct thread - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stop"); - return; + if (marshal) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "stop"); + return; + } } if (!_isFinished) { _isFinished = true; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index ef7e075021..4d39365626 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -84,7 +84,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(); // this can be called from any thread + Q_INVOKABLE void stop(bool marshal); // this can be called from any thread // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 5e5b8bfde9..6575a12c99 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -160,7 +160,7 @@ void ScriptEngines::shutdownScripting() { scriptEngine->disconnect(this); // Gracefully stop the engine's scripting thread - scriptEngine->stop(); + scriptEngine->stop(false); // We need to wait for the engine to be done running before we proceed, because we don't // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing @@ -359,7 +359,10 @@ QStringList ScriptEngines::getRunningScripts() { QList urls = _scriptEnginesHash.keys(); QStringList result; for (auto url : urls) { - result.append(url.toString()); + ScriptEngine* engine = getScriptEngineInternal(url); + if (engine) { + result.append(url.toString()); + } } return result; } @@ -388,7 +391,7 @@ void ScriptEngines::stopAllScripts(bool restart) { reloadScript(scriptName); }); } - it.value()->stop(); + it.value()->stop(true); qCDebug(scriptengine) << "stopping script..." << it.key(); } } @@ -411,7 +414,7 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { reloadScript(scriptName); }); } - scriptEngine->stop(); + scriptEngine->stop(false); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; } @@ -459,7 +462,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL } auto scriptEngine = getScriptEngine(scriptUrl); - if (scriptEngine) { + if (scriptEngine && !scriptEngine->isStopping()) { return scriptEngine; } @@ -484,19 +487,21 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL return scriptEngine; } -ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { +ScriptEngine* ScriptEngines::getScriptEngineInternal(const QUrl& rawScriptURL) { ScriptEngine* result = nullptr; - { - QReadLocker lock(&_scriptEnginesHashLock); - const QUrl scriptURL = normalizeScriptURL(rawScriptURL); - auto it = _scriptEnginesHash.find(scriptURL); - if (it != _scriptEnginesHash.end() && !it.value()->isStopping()) { - result = it.value(); - } + const QUrl scriptURL = normalizeScriptURL(rawScriptURL); + auto it = _scriptEnginesHash.find(scriptURL); + if (it != _scriptEnginesHash.end()) { + result = it.value(); } return result; } +ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { + QReadLocker lock(&_scriptEnginesHashLock); + return getScriptEngineInternal(rawScriptURL); +} + // FIXME - change to new version of ScriptCache loading notification void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { UserActivityLogger::getInstance().loadedScript(rawScriptURL); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index a9c273b2a7..6d887ce95f 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -87,6 +87,7 @@ protected: void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); void launchScriptEngine(ScriptEngine* engine); + ScriptEngine* getScriptEngineInternal(const QUrl& rawScriptURL); QReadWriteLock _scriptEnginesHashLock; QHash _scriptEnginesHash; From 66bdbf910d2e6cd27aba1e23e5bb8ebf00a3177c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 May 2016 11:55:04 -0700 Subject: [PATCH 252/264] optionally include the metaverse session ID as an http header --- interface/src/DiscoverabilityManager.cpp | 4 ++++ libraries/networking/src/AccountManager.cpp | 13 +++++++++++-- libraries/networking/src/AccountManager.h | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 83f87f82ba..24256fdf39 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -127,6 +127,10 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply if (!dataObject.isEmpty()) { _sessionID = dataObject[SESSION_ID_KEY].toString(); + + // give that session ID to the account manager + auto accountManager = DependencyManager::get(); + accountManager->setSessionID(_sessionID); } } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 9080e3cc53..46e72170e5 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "AccountManager.h" + #include #include @@ -26,13 +28,13 @@ #include +#include "NetworkLogging.h" #include "NodeList.h" #include "udt/PacketHeaders.h" #include "RSAKeypairGenerator.h" #include "SharedUtil.h" +#include "UserActivityLogger.h" -#include "AccountManager.h" -#include "NetworkLogging.h" const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false; @@ -216,6 +218,13 @@ void AccountManager::sendRequest(const QString& path, networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + // if we're allowed to send usage data, include whatever the current session ID is with this request + auto& activityLogger = UserActivityLogger::getInstance(); + if (activityLogger.isEnabled()) { + static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID"; + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), _sessionID.toString().toLocal8Bit()); + } + QUrl requestURL = _authURL; if (path.startsWith("/")) { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 89a2240bbb..4803d2625f 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -86,6 +86,8 @@ public: static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; } + public slots: void requestAccessToken(const QString& login, const QString& password); @@ -136,6 +138,8 @@ private: bool _isWaitingForKeypairResponse { false }; QByteArray _pendingPrivateKey; + + QUuid _sessionID; }; #endif // hifi_AccountManager_h From dc7d6d470ddc6ba5866131dc04a32c1cb7de5bee Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 25 May 2016 12:21:50 -0700 Subject: [PATCH 253/264] Don't reset head when we come back. --- interface/src/avatar/MyAvatar.cpp | 6 ++++-- interface/src/avatar/MyAvatar.h | 2 +- interface/src/ui/OverlayConductor.cpp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 04224d6fa9..358b3d72ba 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -234,7 +234,7 @@ QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { return AvatarData::toByteArray(cullSmallChanges, sendAll); } -void MyAvatar::reset(bool andRecenter, bool andReload) { +void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "reset", Q_ARG(bool, andRecenter)); @@ -247,7 +247,9 @@ void MyAvatar::reset(bool andRecenter, bool andReload) { if (andReload) { _skeletonModel->reset(); } - getHead()->reset(); + if (andHead) { // which drives camera in desktop + getHead()->reset(); + } setThrust(glm::vec3(0.0f)); if (andRecenter) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 135e9f6417..f709f79542 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -94,7 +94,7 @@ public: AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } - Q_INVOKABLE void reset(bool andRecenter = false, bool andReload = true); + Q_INVOKABLE void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); void update(float deltaTime); virtual void postUpdate(float deltaTime) override; void preDisplaySide(RenderArgs* renderArgs); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index cb211fd918..83d729779c 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -93,7 +93,7 @@ void OverlayConductor::updateMode() { _mode = FLAT; // Seems appropriate to let things reset, below, after the following. // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. qApp->getActiveDisplayPlugin()->resetSensors(); - myAvatar->reset(true, false); + myAvatar->reset(true, false, false); } if (_wantsOverlays) { setEnabled(!nowDriving, false); From 49769f7d2969626c515ff554ef9aa1ed716081e6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 25 May 2016 13:39:13 -0700 Subject: [PATCH 254/264] trying again -- frantic clicking on reload no longer appears to wedge things --- assignment-client/src/Agent.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 9 ++---- libraries/script-engine/src/ScriptEngine.h | 2 +- libraries/script-engine/src/ScriptEngines.cpp | 29 ++++++++----------- libraries/script-engine/src/ScriptEngines.h | 1 - 6 files changed, 18 insertions(+), 27 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 327f6de695..65e193dec6 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -476,7 +476,7 @@ void Agent::aboutToFinish() { setIsAvatar(false);// will stop timers for sending identity packets if (_scriptEngine) { - _scriptEngine->stop(false); + _scriptEngine->stop(); } // our entity tree is going to go away so tell that to the EntityScriptingInterface diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index bc3c6b2441..56f6438e70 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -114,7 +114,7 @@ void EntityTreeRenderer::clear() { // Unload and stop the engine here (instead of in its deleter) to // avoid marshalling unload signals back to this thread _entitiesScriptEngine->unloadAllEntityScripts(); - _entitiesScriptEngine->stop(false); + _entitiesScriptEngine->stop(); } if (_wantScripts && !_shuttingDown) { diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 61ab8c0c63..a5e3be8a43 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -290,7 +290,7 @@ void ScriptEngine::waitTillDoneRunning() { assert(workerThread != QThread::currentThread()); // Engine should be stopped already, but be defensive - stop(false); + stop(); auto startedWaiting = usecTimestampNow(); while (workerThread->isRunning()) { @@ -944,12 +944,9 @@ void ScriptEngine::stopAllTimersForEntityScript(const EntityItemID& entityID) { void ScriptEngine::stop(bool marshal) { _isStopping = true; // this can be done on any thread - // marshal us over to the correct thread if (marshal) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "stop"); - return; - } + QMetaObject::invokeMethod(this, "stop"); + return; } if (!_isFinished) { _isFinished = true; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 4d39365626..1077dce686 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -84,7 +84,7 @@ public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts - Q_INVOKABLE void stop(bool marshal); // this can be called from any thread + Q_INVOKABLE void stop(bool marshal = false); // Stop any evaluating scripts and wait for the scripting thread to finish. void waitTillDoneRunning(); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 6575a12c99..29c223f4b3 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -160,7 +160,7 @@ void ScriptEngines::shutdownScripting() { scriptEngine->disconnect(this); // Gracefully stop the engine's scripting thread - scriptEngine->stop(false); + scriptEngine->stop(); // We need to wait for the engine to be done running before we proceed, because we don't // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing @@ -359,10 +359,7 @@ QStringList ScriptEngines::getRunningScripts() { QList urls = _scriptEnginesHash.keys(); QStringList result; for (auto url : urls) { - ScriptEngine* engine = getScriptEngineInternal(url); - if (engine) { - result.append(url.toString()); - } + result.append(url.toString()); } return result; } @@ -383,7 +380,7 @@ void ScriptEngines::stopAllScripts(bool restart) { // Stop and possibly restart all currently running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { - if (it.value()->isFinished()) { + if (it.value()->isFinished() || it.value()->isStopping()) { continue; } if (restart && it.value()->isUserLoaded()) { @@ -414,7 +411,7 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { reloadScript(scriptName); }); } - scriptEngine->stop(false); + scriptEngine->stop(); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; } @@ -487,21 +484,19 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL return scriptEngine; } -ScriptEngine* ScriptEngines::getScriptEngineInternal(const QUrl& rawScriptURL) { +ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { ScriptEngine* result = nullptr; - const QUrl scriptURL = normalizeScriptURL(rawScriptURL); - auto it = _scriptEnginesHash.find(scriptURL); - if (it != _scriptEnginesHash.end()) { - result = it.value(); + { + QReadLocker lock(&_scriptEnginesHashLock); + const QUrl scriptURL = normalizeScriptURL(rawScriptURL); + auto it = _scriptEnginesHash.find(scriptURL); + if (it != _scriptEnginesHash.end()) { + result = it.value(); + } } return result; } -ScriptEngine* ScriptEngines::getScriptEngine(const QUrl& rawScriptURL) { - QReadLocker lock(&_scriptEnginesHashLock); - return getScriptEngineInternal(rawScriptURL); -} - // FIXME - change to new version of ScriptCache loading notification void ScriptEngines::onScriptEngineLoaded(const QString& rawScriptURL) { UserActivityLogger::getInstance().loadedScript(rawScriptURL); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 6d887ce95f..a9c273b2a7 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -87,7 +87,6 @@ protected: void onScriptEngineLoaded(const QString& scriptFilename); void onScriptEngineError(const QString& scriptFilename); void launchScriptEngine(ScriptEngine* engine); - ScriptEngine* getScriptEngineInternal(const QUrl& rawScriptURL); QReadWriteLock _scriptEnginesHashLock; QHash _scriptEnginesHash; From b16e701ea3d6a6287ba175240596d6cbbc5c50eb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 25 May 2016 13:53:28 -0700 Subject: [PATCH 255/264] Fix broken protocol for shapes --- libraries/entities/src/ShapeEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index c39a477a35..84208cc6f1 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -155,7 +155,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_COLOR, getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); } // This value specifes how the shape should be treated by physics calculations. From 991da9e248338898b7ac582a5acaa81fd9d9e83e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 26 May 2016 09:51:55 -0700 Subject: [PATCH 256/264] fix cut-paste error in settings. --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 358b3d72ba..babbcfadbe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -859,7 +859,7 @@ void MyAvatar::loadData() { setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setSnapTurn(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); + setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); settings.endGroup(); From 2c02c963d4f06b009fb54a456d6fff67e9803a69 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 26 May 2016 12:22:39 -0700 Subject: [PATCH 257/264] Moving shape definition to a shared location --- libraries/render-utils/src/GeometryCache.cpp | 240 ++----------------- libraries/render-utils/src/GeometryCache.h | 9 +- libraries/shared/src/shared/Shapes.cpp | 186 ++++++++++++++ libraries/shared/src/shared/Shapes.h | 79 ++++++ 4 files changed, 284 insertions(+), 230 deletions(-) create mode 100644 libraries/shared/src/shared/Shapes.cpp create mode 100644 libraries/shared/src/shared/Shapes.h diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 02aca4216e..1154f27ee0 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "TextureCache.h" #include "RenderUtilsLogging.h" @@ -54,7 +55,7 @@ static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32); -void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) { +void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { vertexBuffer->append(vertices); _positionView = gpu::BufferView(vertexBuffer, 0, @@ -63,7 +64,7 @@ void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, c vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); } -void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices) { +void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices) { _indices = indexBuffer; if (!indices.empty()) { _indexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; @@ -126,90 +127,19 @@ size_t GeometryCache::getCubeTriangleCount() { return getShapeTriangleCount(Cube); } -using Index = uint32_t; using IndexPair = uint64_t; using IndexPairs = std::unordered_set; -template -using Face = std::array; - -template -using FaceVector = std::vector>; - -template -struct Solid { - VertexVector vertices; - FaceVector faces; - - Solid& fitDimension(float newMaxDimension) { - float maxDimension = 0; - for (const auto& vertex : vertices) { - maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z)); - } - float multiplier = newMaxDimension / maxDimension; - for (auto& vertex : vertices) { - vertex *= multiplier; - } - return *this; - } - - vec3 getFaceNormal(size_t faceIndex) const { - vec3 result; - const auto& face = faces[faceIndex]; - for (size_t i = 0; i < N; ++i) { - result += vertices[face[i]]; - } - result /= N; - return glm::normalize(result); - } -}; - -template -static size_t triangulatedFaceTriangleCount() { - return N - 2; -} - -template -static size_t triangulatedFaceIndexCount() { - return triangulatedFaceTriangleCount() * VERTICES_PER_TRIANGLE; -} - -static IndexPair indexToken(Index a, Index b) { +static IndexPair indexToken(geometry::Index a, geometry::Index b) { if (a > b) { std::swap(a, b); } return (((IndexPair)a) << 32) | ((IndexPair)b); } -static Solid<3> tesselate(Solid<3> solid, int count) { - float length = glm::length(solid.vertices[0]); - for (int i = 0; i < count; ++i) { - Solid<3> result { solid.vertices, {} }; - result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3); - for (size_t f = 0; f < solid.faces.size(); ++f) { - Index baseVertex = (Index)result.vertices.size(); - const Face<3>& oldFace = solid.faces[f]; - const vec3& a = solid.vertices[oldFace[0]]; - const vec3& b = solid.vertices[oldFace[1]]; - const vec3& c = solid.vertices[oldFace[2]]; - vec3 ab = glm::normalize(a + b) * length; - vec3 bc = glm::normalize(b + c) * length; - vec3 ca = glm::normalize(c + a) * length; - result.vertices.push_back(ab); - result.vertices.push_back(bc); - result.vertices.push_back(ca); - result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } }); - result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } }); - result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } }); - result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); - } - solid = result; - } - return solid; -} - template -void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { +void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; @@ -259,7 +189,8 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const Solid& shape, } template -void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { +void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { + using namespace geometry; Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; @@ -303,147 +234,6 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const Solid& shape shapeData.setupIndices(indexBuffer, solidIndices, wireIndices); } -// The golden ratio -static const float PHI = 1.61803398874f; - -static const Solid<3>& tetrahedron() { - static const auto A = vec3(1, 1, 1); - static const auto B = vec3(1, -1, -1); - static const auto C = vec3(-1, 1, -1); - static const auto D = vec3(-1, -1, 1); - static const Solid<3> TETRAHEDRON = Solid<3>{ - { A, B, C, D }, - FaceVector<3>{ - Face<3> { { 0, 1, 2 } }, - Face<3> { { 3, 1, 0 } }, - Face<3> { { 2, 3, 0 } }, - Face<3> { { 2, 1, 3 } }, - } - }.fitDimension(0.5f); - return TETRAHEDRON; -} - -static const Solid<4>& cube() { - static const auto A = vec3(1, 1, 1); - static const auto B = vec3(-1, 1, 1); - static const auto C = vec3(-1, 1, -1); - static const auto D = vec3(1, 1, -1); - static const Solid<4> CUBE = Solid<4>{ - { A, B, C, D, -A, -B, -C, -D }, - FaceVector<4>{ - Face<4> { { 3, 2, 1, 0 } }, - Face<4> { { 0, 1, 7, 6 } }, - Face<4> { { 1, 2, 4, 7 } }, - Face<4> { { 2, 3, 5, 4 } }, - Face<4> { { 3, 0, 6, 5 } }, - Face<4> { { 4, 5, 6, 7 } }, - } - }.fitDimension(0.5f); - return CUBE; -} - -static const Solid<3>& octahedron() { - static const auto A = vec3(0, 1, 0); - static const auto B = vec3(0, -1, 0); - static const auto C = vec3(0, 0, 1); - static const auto D = vec3(0, 0, -1); - static const auto E = vec3(1, 0, 0); - static const auto F = vec3(-1, 0, 0); - static const Solid<3> OCTAHEDRON = Solid<3>{ - { A, B, C, D, E, F}, - FaceVector<3> { - Face<3> { { 0, 2, 4, } }, - Face<3> { { 0, 4, 3, } }, - Face<3> { { 0, 3, 5, } }, - Face<3> { { 0, 5, 2, } }, - Face<3> { { 1, 4, 2, } }, - Face<3> { { 1, 3, 4, } }, - Face<3> { { 1, 5, 3, } }, - Face<3> { { 1, 2, 5, } }, - } - }.fitDimension(0.5f); - return OCTAHEDRON; -} - -static const Solid<5>& dodecahedron() { - static const float P = PHI; - static const float IP = 1.0f / PHI; - static const vec3 A = vec3(IP, P, 0); - static const vec3 B = vec3(-IP, P, 0); - static const vec3 C = vec3(-1, 1, 1); - static const vec3 D = vec3(0, IP, P); - static const vec3 E = vec3(1, 1, 1); - static const vec3 F = vec3(1, 1, -1); - static const vec3 G = vec3(-1, 1, -1); - static const vec3 H = vec3(-P, 0, IP); - static const vec3 I = vec3(0, -IP, P); - static const vec3 J = vec3(P, 0, IP); - - static const Solid<5> DODECAHEDRON = Solid<5>{ - { - A, B, C, D, E, F, G, H, I, J, - -A, -B, -C, -D, -E, -F, -G, -H, -I, -J, - }, - FaceVector<5> { - Face<5> { { 0, 1, 2, 3, 4 } }, - Face<5> { { 0, 5, 18, 6, 1 } }, - Face<5> { { 1, 6, 19, 7, 2 } }, - Face<5> { { 2, 7, 15, 8, 3 } }, - Face<5> { { 3, 8, 16, 9, 4 } }, - Face<5> { { 4, 9, 17, 5, 0 } }, - Face<5> { { 14, 13, 12, 11, 10 } }, - Face<5> { { 11, 16, 8, 15, 10 } }, - Face<5> { { 12, 17, 9, 16, 11 } }, - Face<5> { { 13, 18, 5, 17, 12 } }, - Face<5> { { 14, 19, 6, 18, 13 } }, - Face<5> { { 10, 15, 7, 19, 14 } }, - } - }.fitDimension(0.5f); - return DODECAHEDRON; -} - -static const Solid<3>& icosahedron() { - static const float N = 1.0f / PHI; - static const float P = 1.0f; - static const auto A = vec3(N, P, 0); - static const auto B = vec3(-N, P, 0); - static const auto C = vec3(0, N, P); - static const auto D = vec3(P, 0, N); - static const auto E = vec3(P, 0, -N); - static const auto F = vec3(0, N, -P); - - static const Solid<3> ICOSAHEDRON = Solid<3> { - { - A, B, C, D, E, F, - -A, -B, -C, -D, -E, -F, - }, - FaceVector<3> { - Face<3> { { 1, 2, 0 } }, - Face<3> { { 2, 3, 0 } }, - Face<3> { { 3, 4, 0 } }, - Face<3> { { 4, 5, 0 } }, - Face<3> { { 5, 1, 0 } }, - - Face<3> { { 1, 10, 2 } }, - Face<3> { { 11, 2, 10 } }, - Face<3> { { 2, 11, 3 } }, - Face<3> { { 7, 3, 11 } }, - Face<3> { { 3, 7, 4 } }, - Face<3> { { 8, 4, 7 } }, - Face<3> { { 4, 8, 5 } }, - Face<3> { { 9, 5, 8 } }, - Face<3> { { 5, 9, 1 } }, - Face<3> { { 10, 1, 9 } }, - - Face<3> { { 8, 7, 6 } }, - Face<3> { { 9, 8, 6 } }, - Face<3> { { 10, 9, 6 } }, - Face<3> { { 11, 10, 6 } }, - Face<3> { { 7, 11, 6 } }, - } - }.fitDimension(0.5f); - return ICOSAHEDRON; -} // FIXME solids need per-face vertices, but smooth shaded // components do not. Find a way to support using draw elements @@ -451,24 +241,24 @@ static const Solid<3>& icosahedron() { // Maybe special case cone and cylinder since they combine flat // and smooth shading void GeometryCache::buildShapes() { + using namespace geometry; auto vertexBuffer = std::make_shared(); auto indexBuffer = std::make_shared(); // Cube - setupFlatShape(_shapes[Cube], cube(), _shapeVertices, _shapeIndices); + setupFlatShape(_shapes[Cube], geometry::cube(), _shapeVertices, _shapeIndices); // Tetrahedron - setupFlatShape(_shapes[Tetrahedron], tetrahedron(), _shapeVertices, _shapeIndices); + setupFlatShape(_shapes[Tetrahedron], geometry::tetrahedron(), _shapeVertices, _shapeIndices); // Icosahedron - setupFlatShape(_shapes[Icosahedron], icosahedron(), _shapeVertices, _shapeIndices); + setupFlatShape(_shapes[Icosahedron], geometry::icosahedron(), _shapeVertices, _shapeIndices); // Octahedron - setupFlatShape(_shapes[Octahedron], octahedron(), _shapeVertices, _shapeIndices); + setupFlatShape(_shapes[Octahedron], geometry::octahedron(), _shapeVertices, _shapeIndices); // Dodecahedron - setupFlatShape(_shapes[Dodecahedron], dodecahedron(), _shapeVertices, _shapeIndices); + setupFlatShape(_shapes[Dodecahedron], geometry::dodecahedron(), _shapeVertices, _shapeIndices); // Sphere // FIXME this uses way more vertices than required. Should find a way to calculate the indices // using shared vertices for better vertex caching - Solid<3> sphere = icosahedron(); - sphere = tesselate(sphere, ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); + auto sphere = geometry::tesselate(geometry::icosahedron(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); sphere.fitDimension(1.0f); setupSmoothShape(_shapes[Sphere], sphere, _shapeVertices, _shapeIndices); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index a2f79de029..c46a9bb084 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -21,6 +21,8 @@ #include +#include + #include #include @@ -121,9 +123,6 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) { seed); } -using IndexVector = std::vector; -using VertexVector = std::vector; - /// Stores cached geometry. class GeometryCache : public Dependency { SINGLETON_DEPENDENCY @@ -297,8 +296,8 @@ public: gpu::BufferView _normalView; gpu::BufferPointer _indices; - void setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices); - void setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices); + void setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices); + void setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices); void setupBatch(gpu::Batch& batch) const; void draw(gpu::Batch& batch) const; void drawWire(gpu::Batch& batch) const; diff --git a/libraries/shared/src/shared/Shapes.cpp b/libraries/shared/src/shared/Shapes.cpp new file mode 100644 index 0000000000..dabdf21202 --- /dev/null +++ b/libraries/shared/src/shared/Shapes.cpp @@ -0,0 +1,186 @@ +// +// Created by Bradley Austin Davis on 2016/05/26 +// Copyright 2013-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 +// + +#include "Shapes.h" + +namespace geometry { + +using glm::vec3; + +// The golden ratio +static const float PHI = 1.61803398874f; + +Solid<3> tesselate(const Solid<3>& solid_, int count) { + Solid<3> solid = solid_; + float length = glm::length(solid.vertices[0]); + for (int i = 0; i < count; ++i) { + Solid<3> result { solid.vertices, {} }; + result.vertices.reserve(solid.vertices.size() + solid.faces.size() * 3); + for (size_t f = 0; f < solid.faces.size(); ++f) { + Index baseVertex = (Index)result.vertices.size(); + const Face<3>& oldFace = solid.faces[f]; + const vec3& a = solid.vertices[oldFace[0]]; + const vec3& b = solid.vertices[oldFace[1]]; + const vec3& c = solid.vertices[oldFace[2]]; + vec3 ab = glm::normalize(a + b) * length; + vec3 bc = glm::normalize(b + c) * length; + vec3 ca = glm::normalize(c + a) * length; + result.vertices.push_back(ab); + result.vertices.push_back(bc); + result.vertices.push_back(ca); + result.faces.push_back(Face<3>{ { oldFace[0], baseVertex, baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, oldFace[1], baseVertex + 1 } }); + result.faces.push_back(Face<3>{ { baseVertex + 1, oldFace[2], baseVertex + 2 } }); + result.faces.push_back(Face<3>{ { baseVertex, baseVertex + 1, baseVertex + 2 } }); + } + solid = result; + } + return solid; +} + +const Solid<3>& tetrahedron() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(1, -1, -1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(-1, -1, 1); + static const Solid<3> TETRAHEDRON = Solid<3>{ + { A, B, C, D }, + FaceVector<3>{ + Face<3> { { 0, 1, 2 } }, + Face<3> { { 3, 1, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 2, 1, 3 } }, + } + }.fitDimension(0.5f); + return TETRAHEDRON; +} + +const Solid<4>& cube() { + static const auto A = vec3(1, 1, 1); + static const auto B = vec3(-1, 1, 1); + static const auto C = vec3(-1, 1, -1); + static const auto D = vec3(1, 1, -1); + static const Solid<4> CUBE = Solid<4>{ + { A, B, C, D, -A, -B, -C, -D }, + FaceVector<4>{ + Face<4> { { 3, 2, 1, 0 } }, + Face<4> { { 0, 1, 7, 6 } }, + Face<4> { { 1, 2, 4, 7 } }, + Face<4> { { 2, 3, 5, 4 } }, + Face<4> { { 3, 0, 6, 5 } }, + Face<4> { { 4, 5, 6, 7 } }, + } + }.fitDimension(0.5f); + return CUBE; +} + +const Solid<3>& octahedron() { + static const auto A = vec3(0, 1, 0); + static const auto B = vec3(0, -1, 0); + static const auto C = vec3(0, 0, 1); + static const auto D = vec3(0, 0, -1); + static const auto E = vec3(1, 0, 0); + static const auto F = vec3(-1, 0, 0); + static const Solid<3> OCTAHEDRON = Solid<3>{ + { A, B, C, D, E, F}, + FaceVector<3> { + Face<3> { { 0, 2, 4, } }, + Face<3> { { 0, 4, 3, } }, + Face<3> { { 0, 3, 5, } }, + Face<3> { { 0, 5, 2, } }, + Face<3> { { 1, 4, 2, } }, + Face<3> { { 1, 3, 4, } }, + Face<3> { { 1, 5, 3, } }, + Face<3> { { 1, 2, 5, } }, + } + }.fitDimension(0.5f); + return OCTAHEDRON; +} + +const Solid<5>& dodecahedron() { + static const float P = PHI; + static const float IP = 1.0f / PHI; + static const vec3 A = vec3(IP, P, 0); + static const vec3 B = vec3(-IP, P, 0); + static const vec3 C = vec3(-1, 1, 1); + static const vec3 D = vec3(0, IP, P); + static const vec3 E = vec3(1, 1, 1); + static const vec3 F = vec3(1, 1, -1); + static const vec3 G = vec3(-1, 1, -1); + static const vec3 H = vec3(-P, 0, IP); + static const vec3 I = vec3(0, -IP, P); + static const vec3 J = vec3(P, 0, IP); + + static const Solid<5> DODECAHEDRON = Solid<5>{ + { + A, B, C, D, E, F, G, H, I, J, + -A, -B, -C, -D, -E, -F, -G, -H, -I, -J, + }, + FaceVector<5> { + Face<5> { { 0, 1, 2, 3, 4 } }, + Face<5> { { 0, 5, 18, 6, 1 } }, + Face<5> { { 1, 6, 19, 7, 2 } }, + Face<5> { { 2, 7, 15, 8, 3 } }, + Face<5> { { 3, 8, 16, 9, 4 } }, + Face<5> { { 4, 9, 17, 5, 0 } }, + Face<5> { { 14, 13, 12, 11, 10 } }, + Face<5> { { 11, 16, 8, 15, 10 } }, + Face<5> { { 12, 17, 9, 16, 11 } }, + Face<5> { { 13, 18, 5, 17, 12 } }, + Face<5> { { 14, 19, 6, 18, 13 } }, + Face<5> { { 10, 15, 7, 19, 14 } }, + } + }.fitDimension(0.5f); + return DODECAHEDRON; +} + +const Solid<3>& icosahedron() { + static const float N = 1.0f / PHI; + static const float P = 1.0f; + static const auto A = vec3(N, P, 0); + static const auto B = vec3(-N, P, 0); + static const auto C = vec3(0, N, P); + static const auto D = vec3(P, 0, N); + static const auto E = vec3(P, 0, -N); + static const auto F = vec3(0, N, -P); + + static const Solid<3> ICOSAHEDRON = Solid<3> { + { + A, B, C, D, E, F, + -A, -B, -C, -D, -E, -F, + }, + FaceVector<3> { + Face<3> { { 1, 2, 0 } }, + Face<3> { { 2, 3, 0 } }, + Face<3> { { 3, 4, 0 } }, + Face<3> { { 4, 5, 0 } }, + Face<3> { { 5, 1, 0 } }, + + Face<3> { { 1, 10, 2 } }, + Face<3> { { 11, 2, 10 } }, + Face<3> { { 2, 11, 3 } }, + Face<3> { { 7, 3, 11 } }, + Face<3> { { 3, 7, 4 } }, + Face<3> { { 8, 4, 7 } }, + Face<3> { { 4, 8, 5 } }, + Face<3> { { 9, 5, 8 } }, + Face<3> { { 5, 9, 1 } }, + Face<3> { { 10, 1, 9 } }, + + Face<3> { { 8, 7, 6 } }, + Face<3> { { 9, 8, 6 } }, + Face<3> { { 10, 9, 6 } }, + Face<3> { { 11, 10, 6 } }, + Face<3> { { 7, 11, 6 } }, + } + }.fitDimension(0.5f); + return ICOSAHEDRON; +} + +} + diff --git a/libraries/shared/src/shared/Shapes.h b/libraries/shared/src/shared/Shapes.h new file mode 100644 index 0000000000..2d7827d046 --- /dev/null +++ b/libraries/shared/src/shared/Shapes.h @@ -0,0 +1,79 @@ +// +// Created by Bradley Austin Davis on 2016/05/26 +// Copyright 2013-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 +// + +#pragma once +#ifndef hifi_shared_shapes +#define hifi_shared_shapes + +#include + +#include +#include + +#include + +namespace geometry { + + using Index = uint32_t; + using Vec = glm::vec3; + using VertexVector = std::vector; + using IndexVector = std::vector; + + template + using Face = std::array; + + template + using FaceVector = std::vector>; + + template + struct Solid { + VertexVector vertices; + FaceVector faces; + + Solid& fitDimension(float newMaxDimension) { + float maxDimension = 0; + for (const auto& vertex : vertices) { + maxDimension = std::max(maxDimension, std::max(std::max(vertex.x, vertex.y), vertex.z)); + } + float multiplier = newMaxDimension / maxDimension; + for (auto& vertex : vertices) { + vertex *= multiplier; + } + return *this; + } + + Vec getFaceNormal(size_t faceIndex) const { + vec3 result; + const auto& face = faces[faceIndex]; + for (size_t i = 0; i < N; ++i) { + result += vertices[face[i]]; + } + result /= N; + return glm::normalize(result); + } + }; + + template + size_t triangulatedFaceTriangleCount() { + return N - 2; + } + + template + size_t triangulatedFaceIndexCount() { + return triangulatedFaceTriangleCount() * VERTICES_PER_TRIANGLE; + } + + Solid<3> tesselate(const Solid<3>& solid, int count); + const Solid<3>& tetrahedron(); + const Solid<4>& cube(); + const Solid<3>& octahedron(); + const Solid<5>& dodecahedron(); + const Solid<3>& icosahedron(); +} + +#endif From b4d14f06d8dfe73ba2064366a074e89a832d74e5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 26 May 2016 14:35:17 -0700 Subject: [PATCH 258/264] Bugfix for avatar LOD If an avatar was LOD'ed out, it would never become visible again, because the bounds of the skeleton model were never again. This ensures that the model bound is updated, even when the avatar is LOD'ed away. --- interface/src/avatar/Avatar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ccda77a44d..3e22448386 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -303,6 +303,9 @@ void Avatar::simulate(float deltaTime) { head->setScale(getUniformScale()); head->simulate(deltaTime, false, !_shouldAnimate); } + } else { + // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. + _skeletonModel->simulate(deltaTime, false); } // update animation for display name fade in/out From 1ec421350d68101e6b93f01aa9ef33d5035eedd9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 26 May 2016 13:16:20 -0700 Subject: [PATCH 259/264] send the UUID session ID without curly braces --- libraries/networking/src/AccountManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 46e72170e5..bac031885f 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -222,7 +222,8 @@ void AccountManager::sendRequest(const QString& path, auto& activityLogger = UserActivityLogger::getInstance(); if (activityLogger.isEnabled()) { static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID"; - networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), _sessionID.toString().toLocal8Bit()); + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(), + uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); } QUrl requestURL = _authURL; From 6a962d7aab95b8742b7bac005cae9dbd716bc859 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 26 May 2016 12:42:35 -0700 Subject: [PATCH 260/264] Unix build fix --- libraries/shared/src/shared/Shapes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/shared/Shapes.h b/libraries/shared/src/shared/Shapes.h index 2d7827d046..3486a0a663 100644 --- a/libraries/shared/src/shared/Shapes.h +++ b/libraries/shared/src/shared/Shapes.h @@ -48,7 +48,7 @@ namespace geometry { } Vec getFaceNormal(size_t faceIndex) const { - vec3 result; + Vec result; const auto& face = faces[faceIndex]; for (size_t i = 0; i < N; ++i) { result += vertices[face[i]]; @@ -65,7 +65,7 @@ namespace geometry { template size_t triangulatedFaceIndexCount() { - return triangulatedFaceTriangleCount() * VERTICES_PER_TRIANGLE; + return triangulatedFaceTriangleCount() * 3; } Solid<3> tesselate(const Solid<3>& solid, int count); From 5b6660e7ea23e0561fc709821cf93fef7e457c43 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 27 May 2016 10:35:46 -0700 Subject: [PATCH 261/264] Default preference to false. --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f709f79542..d3da32e0ed 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -398,7 +398,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; - bool _clearOverlayWhenDriving { true }; + bool _clearOverlayWhenDriving { false }; // cache of the current HMD sensor position and orientation // in sensor space. From 0ec7eae58ca7191531d7464ceebca40ddf5895d4 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 27 May 2016 16:59:42 -0700 Subject: [PATCH 262/264] fix shapes to property polymorph and persist --- .../entities/src/EntityItemProperties.cpp | 19 ++++++-- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + scripts/system/html/entityProperties.html | 48 ++++++------------- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f273507d0d..ba39727ff9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -314,6 +314,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); + CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape); + changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); @@ -435,6 +437,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Sphere) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Sphere")); } + if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); + } // FIXME - it seems like ParticleEffect should also support this if (_type == EntityTypes::Model || _type == EntityTypes::Zone) { @@ -1144,7 +1149,11 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); } - if (properties.getType() == EntityTypes::Shape) { + // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE + // when encoding/decoding edits because otherwise they can't polymorph to other shape types + if (properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); @@ -1211,7 +1220,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem } else { packetData->discardSubTree(); } - return success; } @@ -1434,7 +1442,12 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_WIDTHS, QVector, setStrokeWidths); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); } - if (properties.getType() == EntityTypes::Shape) { + + // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE + // when encoding/decoding edits because otherwise they can't polymorph to other shape types + if (properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 92e5b52f75..db743f81e4 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_MORE_SHAPES; + return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS; 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 97398c6744..320635379d 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -179,6 +179,7 @@ const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56; const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57; const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58; const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59; +const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60; enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 5c87f753d9..2a82d8fa74 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -557,7 +557,6 @@ } properties = data.selections[0].properties; - elID.innerHTML = properties.id; elType.innerHTML = properties.type; @@ -675,6 +674,9 @@ for (var i = 0; i < elShapeSections.length; i++) { elShapeSections[i].style.display = 'table'; } + elShape.value = properties.shape; + setDropdownText(elShape); + } else { for (var i = 0; i < elShapeSections.length; i++) { console.log("Hiding shape section " + elShapeSections[i]) @@ -1349,6 +1351,17 @@

+
@@ -1362,39 +1375,6 @@
-
- M -
- -
- - -
-
- - -
- - From b8c80e222286a16ef56a5ecfcf7c95aa04a9918e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 31 May 2016 20:39:16 -0700 Subject: [PATCH 263/264] Revert "refresh API info during re-connect - case 570" This reverts commit e8d7f9614aed57bad25f940c82282c25b91ed92e. --- libraries/networking/src/AddressManager.cpp | 44 +++------------------ libraries/networking/src/AddressManager.h | 9 +---- libraries/networking/src/DomainHandler.cpp | 18 ++++++--- libraries/networking/src/DomainHandler.h | 10 ++--- libraries/networking/src/NodeList.cpp | 14 ++----- 5 files changed, 28 insertions(+), 67 deletions(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 80989acd2c..1a452999e4 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -144,21 +144,12 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // 4. domain network address (IP or dns resolvable hostname) // use our regex'ed helpers to figure out what we're supposed to do with this - if (handleUsername(lookupUrl.authority())) { - // handled a username for lookup - - // in case we're failing to connect to where we thought this user was - // store their username as previous lookup so we can refresh their location via API - _previousLookup = lookupUrl; - } else { + if (!handleUsername(lookupUrl.authority())) { // we're assuming this is either a network address or global place name // check if it is a network address first bool hostChanged; if (handleNetworkAddress(lookupUrl.host() - + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { - - // a network address lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); + + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { // If the host changed then we have already saved to history if (hostChanged) { @@ -174,16 +165,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ - // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info - _previousLookup = lookupUrl; - // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { - // store this place name as the previous lookup in case we fail to connect and want to refresh API info - _previousLookup = lookupUrl; - // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger); @@ -195,13 +180,9 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); - // a path lookup clears the previous lookup since we don't expect to re-attempt it - _previousLookup.clear(); - // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); - return true; } @@ -295,7 +276,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const qCDebug(networking) << "Possible domain change required to connect to" << domainHostname << "on" << domainPort; - emit possibleDomainChangeRequired(domainHostname, domainPort, domainID); + emit possibleDomainChangeRequired(domainHostname, domainPort); } else { QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString(); @@ -334,10 +315,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty()) { - // make sure we don't re-handle an overriden path if this was a refresh of info from API - if (trigger != LookupTrigger::AttemptedRefresh) { - handlePath(overridePath, trigger); - } + handlePath(overridePath, trigger); } else { // take the path that came back const QString PLACE_PATH_KEY = "path"; @@ -620,7 +598,7 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); - emit possibleDomainChangeRequired(hostname, port, QUuid()); + emit possibleDomainChangeRequired(hostname, port); return hostChanged; } @@ -640,13 +618,6 @@ void AddressManager::goToUser(const QString& username) { QByteArray(), nullptr, requestParams); } -void AddressManager::refreshPreviousLookup() { - // if we have a non-empty previous lookup, fire it again now (but don't re-store it in the history) - if (!_previousLookup.isEmpty()) { - handleUrl(_previousLookup, LookupTrigger::AttemptedRefresh); - } -} - void AddressManager::copyAddress() { QApplication::clipboard()->setText(currentAddress().toString()); } @@ -658,10 +629,7 @@ void AddressManager::copyPath() { void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // if we're cold starting and this is called for the first address (from settings) we don't do anything - if (trigger != LookupTrigger::StartupFromSettings - && trigger != LookupTrigger::DomainPathResponse - && trigger != LookupTrigger::AttemptedRefresh) { - + if (trigger != LookupTrigger::StartupFromSettings && trigger != LookupTrigger::DomainPathResponse) { if (trigger == LookupTrigger::Back) { // we're about to push to the forward stack // if it's currently empty emit our signal to say that going forward is now possible diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index a3aaee3ba2..643924ff5c 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -48,8 +48,7 @@ public: Forward, StartupFromSettings, DomainPathResponse, - Internal, - AttemptedRefresh + Internal }; bool isConnected(); @@ -90,8 +89,6 @@ public slots: void goToUser(const QString& username); - void refreshPreviousLookup(); - void storeCurrentAddress(); void copyAddress(); @@ -102,7 +99,7 @@ signals: void lookupResultIsOffline(); void lookupResultIsNotFound(); - void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort, const QUuid& domainID); + void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort); void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); void locationChangeRequired(const glm::vec3& newPosition, @@ -155,8 +152,6 @@ private: quint64 _lastBackPush = 0; QString _newHostLookupPath; - - QUrl _previousLookup; }; #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 1efcfc7f27..4f85296f03 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -28,8 +28,16 @@ DomainHandler::DomainHandler(QObject* parent) : QObject(parent), + _uuid(), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), + _assignmentUUID(), + _connectionToken(), + _iceDomainID(), + _iceClientID(), + _iceServerSockAddr(), _icePeer(this), + _isConnected(false), + _settingsObject(), _settingsTimer(this) { _sockAddr.setObjectName("DomainServer"); @@ -97,7 +105,7 @@ void DomainHandler::hardReset() { softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; - _pendingDomainID = QUuid(); + _iceDomainID = QUuid(); _iceServerSockAddr = HifiSockAddr(); _hostname = QString(); _sockAddr.clear(); @@ -131,7 +139,7 @@ void DomainHandler::setUUID(const QUuid& uuid) { } } -void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const QUuid& domainID) { +void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { if (hostname != _hostname || _sockAddr.getPort() != port) { // re-set the domain info so that auth information is reloaded @@ -163,8 +171,6 @@ void DomainHandler::setSocketAndID(const QString& hostname, quint16 port, const // grab the port by reading the string after the colon _sockAddr.setPort(port); } - - _pendingDomainID = domainID; } void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) { @@ -175,7 +181,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); - _pendingDomainID = id; + _iceDomainID = id; HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; replaceableSockAddr->~HifiSockAddr(); @@ -337,7 +343,7 @@ void DomainHandler::processICEResponsePacket(QSharedPointer mes DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); - if (_icePeer.getUUID() != _pendingDomainID) { + if (_icePeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; _icePeer.reset(); } else { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 226186f1d0..bcee7668d1 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -58,8 +58,8 @@ public: const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - - const QUuid& getPendingDomainID() const { return _pendingDomainID; } + + const QUuid& getICEDomainID() const { return _iceDomainID; } const QUuid& getICEClientID() const { return _iceClientID; } @@ -94,7 +94,7 @@ public: }; public slots: - void setSocketAndID(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT, const QUuid& id = QUuid()); + void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); void processSettingsPacketList(QSharedPointer packetList); @@ -136,11 +136,11 @@ private: HifiSockAddr _sockAddr; QUuid _assignmentUUID; QUuid _connectionToken; - QUuid _pendingDomainID; // ID of domain being connected to, via ICE or direct connection + QUuid _iceDomainID; QUuid _iceClientID; HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; - bool _isConnected { false }; + bool _isConnected; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 082200fccc..16a4083b08 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -50,7 +50,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // handle domain change signals from AddressManager connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired, - &_domainHandler, &DomainHandler::setSocketAndID); + &_domainHandler, &DomainHandler::setHostnameAndPort); connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, &_domainHandler, &DomainHandler::setIceServerHostnameAndID); @@ -355,14 +355,6 @@ void NodeList::sendDomainServerCheckIn() { // increment the count of un-replied check-ins _numNoReplyDomainCheckIns++; } - - if (!_publicSockAddr.isNull() && !_domainHandler.isConnected() && !_domainHandler.getPendingDomainID().isNull()) { - // if we aren't connected to the domain-server, and we have an ID - // (that we presume belongs to a domain in the HF Metaverse) - // we request connection information for the domain every so often to make sure what we have is up to date - - DependencyManager::get()->refreshPreviousLookup(); - } } void NodeList::handleDSPathQuery(const QString& newPath) { @@ -470,7 +462,7 @@ void NodeList::handleICEConnectionToDomainServer() { LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), - _domainHandler.getPendingDomainID()); + _domainHandler.getICEDomainID()); } } @@ -483,7 +475,7 @@ void NodeList::pingPunchForDomainServer() { if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getPendingDomainID()); + << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat From 3119b654dc9b0053c5c1bdee7668246ded0fcb87 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 31 May 2016 20:50:12 -0700 Subject: [PATCH 264/264] additional revert related change --- libraries/networking/src/AddressManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1a452999e4..1b7ed11cce 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -362,7 +362,7 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { if (errorReply.error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it - _previousLookup.clear(); + //_previousLookup.clear(); emit lookupResultIsNotFound(); }