From 45ec7fe3e115b85dbcd4c6bd2b4dd4df09c81063 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 9 Jan 2019 12:03:35 -0800 Subject: [PATCH 1/4] working on ignorePickIntersection and web entities --- libraries/entities/src/EntityItem.cpp | 17 +++++ libraries/entities/src/EntityItem.h | 4 + .../entities/src/EntityItemProperties.cpp | 74 +++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 26 ++++--- libraries/entities/src/EntityPropertyFlags.h | 4 + libraries/entities/src/EntityTree.cpp | 3 +- libraries/entities/src/EntityTreeElement.cpp | 8 ++ libraries/entities/src/WebEntityItem.cpp | 3 +- libraries/entities/src/WebEntityItem.h | 5 +- libraries/octree/src/OctreePacketData.h | 2 + libraries/shared/src/WebInputMode.cpp | 25 +++++++ libraries/shared/src/WebInputMode.h | 39 ++++++++++ 12 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 libraries/shared/src/WebInputMode.cpp create mode 100644 libraries/shared/src/WebInputMode.h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index e0749af4ba..90fc1b18e4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -97,6 +97,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire requestedProperties += PROP_RENDER_LAYER; requestedProperties += PROP_PRIMITIVE_MODE; + requestedProperties += PROP_IGNORE_PICK_INTERSECTION; withReadLock([&] { requestedProperties += _grabProperties.getEntityProperties(params); }); @@ -280,6 +281,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)getRenderLayer()); APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode()); + APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, getIgnorePickIntersection()); withReadLock([&] { _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -845,6 +847,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // READ_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire READ_ENTITY_PROPERTY(PROP_RENDER_LAYER, RenderLayer, setRenderLayer); READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); + READ_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection); withWriteLock([&] { int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, @@ -1318,6 +1321,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera); COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderLayer, getRenderLayer); COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ignorePickIntersection, getIgnorePickIntersection); withReadLock([&] { _grabProperties.getProperties(properties); }); @@ -1464,6 +1468,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderLayer, setRenderLayer); SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ignorePickIntersection, setIgnorePickIntersection); withWriteLock([&] { bool grabPropertiesChanged = _grabProperties.setProperties(properties); somethingChanged |= grabPropertiesChanged; @@ -2981,6 +2986,18 @@ void EntityItem::setPrimitiveMode(PrimitiveMode value) { } } +bool EntityItem::getIgnorePickIntersection() const { + return resultWithReadLock([&] { + return _ignorePickIntersection; + }); +} + +void EntityItem::setIgnorePickIntersection(bool value) { + withWriteLock([&] { + _ignorePickIntersection = value; + }); +} + bool EntityItem::getCanCastShadow() const { bool result; withReadLock([&] { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index cac4192cd5..ec7ad78313 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -299,6 +299,9 @@ public: PrimitiveMode getPrimitiveMode() const; void setPrimitiveMode(PrimitiveMode value); + bool getIgnorePickIntersection() const; + void setIgnorePickIntersection(bool value); + bool getCanCastShadow() const; void setCanCastShadow(bool value); @@ -630,6 +633,7 @@ protected: RenderLayer _renderLayer { RenderLayer::WORLD }; PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; + bool _ignorePickIntersection { false }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 194ba22b05..3ae3a647cf 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -393,6 +393,32 @@ void EntityItemProperties::setPrimitiveModeFromString(const QString& primitiveMo } } +QHash stringToWebInputModeLookup; + +void addWebInputMode(WebInputMode mode) { + stringToWebInputModeLookup[WebInputModeHelpers::getNameForWebInputMode(mode)] = mode; +} + +void buildStringToWebInputModeLookup() { + addWebInputMode(WebInputMode::TOUCH); + addWebInputMode(WebInputMode::MOUSE); +} + +QString EntityItemProperties::getInputModeAsString() const { + return WebInputModeHelpers::getNameForWebInputMode(_inputMode); +} + +void EntityItemProperties::setInputModeFromString(const QString& webInputMode) { + if (stringToWebInputModeLookup.empty()) { + buildStringToWebInputModeLookup(); + } + auto webInputModeItr = stringToWebInputModeLookup.find(webInputMode.toLower()); + if (webInputModeItr != stringToWebInputModeLookup.end()) { + _inputMode = webInputModeItr.value(); + _inputModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -419,6 +445,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); CHECK_PROPERTY_CHANGE(PROP_RENDER_LAYER, renderLayer); CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode); + CHECK_PROPERTY_CHANGE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection); changedProperties += _grab.getChangedProperties(); // Physics @@ -573,6 +600,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { // Web CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_DPI, dpi); + CHECK_PROPERTY_CHANGE(PROP_SCRIPT_URL, scriptURL); + CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS); + CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode); // Polyline CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); @@ -658,6 +688,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If true then the entity is rendered. * @property {RenderLayer} renderLayer="world" - In which layer this entity renders. * @property {PrimitiveMode} primitiveMode="solid" - How this entity's geometry is rendered. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the entity. * * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. @@ -1259,6 +1290,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter * (multiply by 1 / 0.0254 = 39.3701) then multiply dimensions.x and dimensions.y by that value * you get the resolution in pixels. + * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. + * @property {number} maxFPS=10 - The maximum update rate for the Web content, in frames/second. + * @property {WebInputMode} inputMode="touch" - The user input mode to use. * @example Create a Web entity displaying at 1920 x 1080 resolution. * var METERS_TO_INCHES = 39.3701; * var entity = Entities.addEntity({ @@ -1467,6 +1501,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_RENDER_LAYER, renderLayer, getRenderLayerAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IGNORE_PICK_INTERSECTION, ignorePickIntersection); _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); // Physics @@ -1656,6 +1691,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString()); } // PolyVoxel only @@ -1851,6 +1889,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(renderLayer, RenderLayer); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(ignorePickIntersection, bool, setIgnorePickIntersection); _grab.copyFromScriptValue(object, _defaultSettings); // Physics @@ -2010,6 +2049,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool // Web COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI); + COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptURL, QString, setScriptURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode); // Polyline COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); @@ -2113,6 +2155,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera); COPY_PROPERTY_IF_CHANGED(renderLayer); COPY_PROPERTY_IF_CHANGED(primitiveMode); + COPY_PROPERTY_IF_CHANGED(ignorePickIntersection); _grab.merge(other._grab); // Physics @@ -2267,6 +2310,9 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { // Web COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(dpi); + COPY_PROPERTY_IF_CHANGED(scriptURL); + COPY_PROPERTY_IF_CHANGED(maxFPS); + COPY_PROPERTY_IF_CHANGED(inputMode); // Polyline COPY_PROPERTY_IF_CHANGED(linePoints); @@ -2387,6 +2433,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); ADD_PROPERTY_TO_MAP(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer); ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode); + ADD_PROPERTY_TO_MAP(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool); { // Grab ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); @@ -2637,6 +2684,9 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr // Web ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); + ADD_PROPERTY_TO_MAP(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString); + ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t); + ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode); // Polyline ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -2819,6 +2869,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)properties.getRenderLayer()); APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode()); + APPEND_ENTITY_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, properties.getIgnorePickIntersection()); _staticGrab.setProperties(properties); _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -3002,6 +3053,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (properties.getType() == EntityTypes::Web) { APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); + APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL()); + APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS()); + APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode()); } if (properties.getType() == EntityTypes::Line) { @@ -3263,6 +3317,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_LAYER, RenderLayer, setRenderLayer); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IGNORE_PICK_INTERSECTION, bool, setIgnorePickIntersection); properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); // Physics @@ -3435,6 +3490,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Web) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode); } if (properties.getType() == EntityTypes::Line) { @@ -3648,6 +3706,7 @@ void EntityItemProperties::markAllChanged() { _isVisibleInSecondaryCameraChanged = true; _renderLayerChanged = true; _primitiveModeChanged = true; + _ignorePickIntersectionChanged = true; _grab.markAllChanged(); // Physics @@ -3795,6 +3854,9 @@ void EntityItemProperties::markAllChanged() { // Web _sourceUrlChanged = true; _dpiChanged = true; + _scriptURLChanged = true; + _maxFPSChanged = true; + _inputModeChanged = true; // Polyline _linePointsChanged = true; @@ -4027,6 +4089,9 @@ QList EntityItemProperties::listChangedProperties() { if (primitiveModeChanged()) { out += "primitiveMode"; } + if (ignorePickIntersectionChanged()) { + out += "ignorePickIntersection"; + } getGrab().listChangedProperties(out); // Physics @@ -4406,6 +4471,15 @@ QList EntityItemProperties::listChangedProperties() { if (dpiChanged()) { out += "dpi"; } + if (scriptURLChanged()) { + out += "scriptURL"; + } + if (maxFPSChanged()) { + out += "maxFPS"; + } + if (inputModeChanged()) { + out += "inputMode"; + } // Polyline if (linePointsChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 89cc87b8df..92df22183a 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -30,29 +30,33 @@ #include #include -#include "AnimationPropertyGroup.h" #include "EntityItemID.h" #include "EntityItemPropertiesDefaults.h" #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" #include "EntityPropertyFlags.h" #include "EntityPsuedoPropertyFlags.h" -#include "LightEntityItem.h" -#include "LineEntityItem.h" -#include "ParticleEffectEntityItem.h" -#include "PolyVoxEntityItem.h" #include "SimulationOwner.h" + +#include "TextEntityItem.h" +#include "WebEntityItem.h" +#include "ParticleEffectEntityItem.h" +#include "LineEntityItem.h" +#include "PolyVoxEntityItem.h" +#include "GridEntityItem.h" +#include "LightEntityItem.h" +#include "ZoneEntityItem.h" + +#include "AnimationPropertyGroup.h" #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" #include "BloomPropertyGroup.h" -#include "TextEntityItem.h" -#include "ZoneEntityItem.h" -#include "GridEntityItem.h" #include "MaterialMappingMode.h" #include "BillboardMode.h" #include "RenderLayer.h" #include "PrimitiveMode.h" +#include "WebInputMode.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -167,6 +171,7 @@ public: DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); DEFINE_PROPERTY_REF_ENUM(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer, RenderLayer::WORLD); DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID); + DEFINE_PROPERTY(PROP_IGNORE_PICK_INTERSECTION, IgnorePickIntersection, ignorePickIntersection, bool, false); DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); // Physics @@ -319,8 +324,11 @@ public: DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); // Web - DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); + DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, WebEntityItem::DEFAULT_SOURCE_URL); DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI); + DEFINE_PROPERTY_REF(PROP_SCRIPT_URL, ScriptURL, scriptURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS); + DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH); // Polyline DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index a3d0d937cb..5c136c4820 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -41,6 +41,7 @@ enum EntityPropertyList { PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire PROP_RENDER_LAYER, PROP_PRIMITIVE_MODE, + PROP_IGNORE_PICK_INTERSECTION, // Grab PROP_GRAB_GRABBABLE, PROP_GRAB_KINEMATIC, @@ -287,6 +288,9 @@ enum EntityPropertyList { // Web PROP_SOURCE_URL = PROP_DERIVED_0, PROP_DPI = PROP_DERIVED_1, + PROP_SCRIPT_URL = PROP_DERIVED_2, + PROP_MAX_FPS = PROP_DERIVED_3, + PROP_INPUT_MODE = PROP_DERIVED_4, // Polyline PROP_LINE_POINTS = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0957b226e9..3f04850710 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2669,7 +2669,6 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) { bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); - bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); if (map.contains("Id")) { _persistID = map["Id"].toUuid(); @@ -2744,7 +2743,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { } // Fix for older content not containing mode fields in the zones - if (needsConversion && (properties.getType() == EntityTypes::EntityType::Zone)) { + if (contentVersion < (int)EntityVersion::ZoneLightInheritModes && (properties.getType() == EntityTypes::EntityType::Zone)) { // The legacy version had no keylight mode - this is set to on properties.setKeyLightMode(COMPONENT_MODE_ENABLED); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 2ece6835ea..22bddaa1c6 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -187,6 +187,10 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori // only called if we do intersect our bounding cube, but find if we actually intersect with entities... EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { + if (entity->getIgnorePickIntersection()) { + return; + } + // use simple line-sphere for broadphase check // (this is faster and more likely to cull results than the filter check below so we do it first) bool success; @@ -327,6 +331,10 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3 // only called if we do intersect our bounding cube, but find if we actually intersect with entities... EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { + if (entity->getIgnorePickIntersection()) { + return; + } + // use simple line-sphere for broadphase check // (this is faster and more likely to cull results than the filter check below so we do it first) bool success; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index d20d36cd71..38ecbb03df 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -21,7 +21,8 @@ #include "EntityTree.h" #include "EntityTreeElement.h" -const QString WebEntityItem::DEFAULT_SOURCE_URL("http://www.google.com"); +const QString WebEntityItem::DEFAULT_SOURCE_URL = "http://www.google.com"; +const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10; EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new WebEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 10d06e6f4a..f1d1bbb313 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -13,8 +13,6 @@ class WebEntityItem : public EntityItem { public: - static const QString DEFAULT_SOURCE_URL; - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); WebEntityItem(const EntityItemID& entityItemID); @@ -54,12 +52,15 @@ public: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + static const QString DEFAULT_SOURCE_URL; virtual void setSourceUrl(const QString& value); QString getSourceUrl() const; void setDPI(uint16_t value); uint16_t getDPI() const; + static const uint8_t DEFAULT_MAX_FPS; + protected: QString _sourceUrl; uint16_t _dpi; diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index debc6786f1..1dbb564fe9 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -37,6 +37,7 @@ #include "BillboardMode.h" #include "RenderLayer.h" #include "PrimitiveMode.h" +#include "WebInputMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -267,6 +268,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, RenderLayer& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, WebInputMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); diff --git a/libraries/shared/src/WebInputMode.cpp b/libraries/shared/src/WebInputMode.cpp new file mode 100644 index 0000000000..7b480424dc --- /dev/null +++ b/libraries/shared/src/WebInputMode.cpp @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 1/9/19 +// Copyright 2019 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 "WebInputMode.h" + +const char* webInputModeNames[] = { + "none", + "yaw", + "full" +}; + +static const size_t WEB_INPUT_MODE_NAMES = (sizeof(webInputModeNames) / sizeof(webInputModeNames[0])); + +QString WebInputModeHelpers::getNameForWebInputMode(WebInputMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)WEB_INPUT_MODE_NAMES)) { + mode = (WebInputMode)0; + } + + return webInputModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/WebInputMode.h b/libraries/shared/src/WebInputMode.h new file mode 100644 index 0000000000..d97ccef519 --- /dev/null +++ b/libraries/shared/src/WebInputMode.h @@ -0,0 +1,39 @@ +// +// Created by Sam Gondelman on 1/9/19. +// Copyright 2019 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_WebInputMode_h +#define hifi_WebInputMode_h + +#include "QString" + +/**jsdoc + *

Controls how the web surface processed PointerEvents

+ * + * + * + * + * + * + * + * + *
ValueDescription
touchEvents are processed as touch events.
mouseEvents are processed as mouse events.
+ * @typedef {string} WebInputMode + */ + +enum class WebInputMode { + TOUCH = 0, + MOUSE, +}; + +class WebInputModeHelpers { +public: + static QString getNameForWebInputMode(WebInputMode mode); +}; + +#endif // hifi_WebInputMode_h + From 4b67a79561b1b6e4db2dc1c539163a9cd4d490ec Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 10 Jan 2019 09:25:21 -0800 Subject: [PATCH 2/4] more web entity wip --- .../{Web3DOverlay.qml => Web3DSurface.qml} | 0 .../{Web3DOverlay.qml => Web3DSurface.qml} | 0 .../qml/controls/+android/WebEntityView.qml | 76 ----- .../resources/qml/controls/WebEntityView.qml | 18 -- interface/resources/qml/controlsUit/Key.qml | 2 +- interface/src/Application.cpp | 89 +++++- interface/src/ui/overlays/Web3DOverlay.cpp | 122 +------ interface/src/ui/overlays/Web3DOverlay.h | 8 +- .../src/RenderableWebEntityItem.cpp | 299 +++++++++++------- .../src/RenderableWebEntityItem.h | 67 +++- .../entities/src/EntityItemProperties.cpp | 8 + libraries/entities/src/ShapeEntityItem.cpp | 6 + libraries/entities/src/ShapeEntityItem.h | 5 +- libraries/entities/src/WebEntityItem.cpp | 121 ++++++- libraries/entities/src/WebEntityItem.h | 22 +- libraries/networking/src/udt/PacketHeaders.h | 1 + libraries/qml/src/qml/impl/SharedObject.cpp | 3 + libraries/ui/src/ui/OffscreenQmlSurface.h | 2 +- 18 files changed, 488 insertions(+), 361 deletions(-) rename interface/resources/qml/+android/{Web3DOverlay.qml => Web3DSurface.qml} (100%) rename interface/resources/qml/{Web3DOverlay.qml => Web3DSurface.qml} (100%) delete mode 100644 interface/resources/qml/controls/+android/WebEntityView.qml delete mode 100644 interface/resources/qml/controls/WebEntityView.qml diff --git a/interface/resources/qml/+android/Web3DOverlay.qml b/interface/resources/qml/+android/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/+android/Web3DOverlay.qml rename to interface/resources/qml/+android/Web3DSurface.qml diff --git a/interface/resources/qml/Web3DOverlay.qml b/interface/resources/qml/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/Web3DOverlay.qml rename to interface/resources/qml/Web3DSurface.qml diff --git a/interface/resources/qml/controls/+android/WebEntityView.qml b/interface/resources/qml/controls/+android/WebEntityView.qml deleted file mode 100644 index 848077cea0..0000000000 --- a/interface/resources/qml/controls/+android/WebEntityView.qml +++ /dev/null @@ -1,76 +0,0 @@ -// -// Web3DOverlay.qml -// -// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 -// 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 QtGraphicalEffects 1.0 - -Item { - - property string url - RadialGradient { - anchors.fill: parent - gradient: Gradient { - GradientStop { position: 0.0; color: "#262626" } - GradientStop { position: 1.0; color: "#000000" } - } - } - - function shortUrl(url) { - var hostBegin = url.indexOf("://"); - if (hostBegin > -1) { - url = url.substring(hostBegin + 3); - } - - var portBegin = url.indexOf(":"); - if (portBegin > -1) { - url = url.substring(0, portBegin); - } - - var pathBegin = url.indexOf("/"); - if (pathBegin > -1) { - url = url.substring(0, pathBegin); - } - - if (url.length > 45) { - url = url.substring(0, 45); - } - - return url; - } - - Text { - id: urlText - text: shortUrl(url) - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.fill: parent - anchors.rightMargin: 10 - anchors.leftMargin: 10 - font.family: "Cairo" - font.weight: Font.DemiBold - font.pointSize: 48 - fontSizeMode: Text.Fit - color: "#FFFFFF" - minimumPixelSize: 5 - } - - Image { - id: hand - source: "../../../icons/hand.svg" - width: 300 - height: 300 - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.bottomMargin: 100 - anchors.rightMargin: 100 - } - - -} diff --git a/interface/resources/qml/controls/WebEntityView.qml b/interface/resources/qml/controls/WebEntityView.qml deleted file mode 100644 index 3bd6aad053..0000000000 --- a/interface/resources/qml/controls/WebEntityView.qml +++ /dev/null @@ -1,18 +0,0 @@ -// -// WebEntityView.qml -// -// Created by Kunal Gosar on 16 March 2017 -// Copyright 2017 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 "." - -WebView { - viewProfile: FileTypeProfile; - - urlTag: "noDownload=true"; -} diff --git a/interface/resources/qml/controlsUit/Key.qml b/interface/resources/qml/controlsUit/Key.qml index dd77fc92dc..b8d95acf1c 100644 --- a/interface/resources/qml/controlsUit/Key.qml +++ b/interface/resources/qml/controlsUit/Key.qml @@ -58,7 +58,7 @@ Item { keyItem.state = "mouseOver"; var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); - var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); + var pointerID = QmlSurface.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); if (Pointers.isLeftHand(pointerID)) { Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d306c77cce..e2e288cd36 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -151,6 +151,7 @@ #include #include #include +#include #include #include "recording/ClipCache.h" @@ -2316,6 +2317,92 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; }); + render::entities::WebEntityRenderer::setInitializeWebSurfaceOperator([](QSharedPointer webSurface) { + webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); + webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); + webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + }); + render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + bool isTablet = url == TabletScriptingInterface::QML; + if (htmlContent) { + // FIXME use the surface cache instead of explicit creation + webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); + }); + webSurface->load(render::entities::WebEntityRenderer::QML, [url](QQmlContext* context, QObject* item) { + item->setProperty("url", url); + }); +#if 0 + // This doesn't work for some reason + webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); + cachedWebSurface = true; + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url, webSurface](QQmlContext* surfaceContext) { + webSurface->getRootItem()->setProperty("url", url); + }); +#endif + } else { + webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); + }); + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url](QQmlContext* surfaceContext) { + //setupQmlSurface(isTablet, url == OVERLAY_LOGIN_DIALOG.toString()); + }); + webSurface->load(url); + cachedWebSurface = false; + } + const uint8_t DEFAULT_MAX_FPS = 10; + const uint8_t TABLET_FPS = 90; + webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS); + }); + render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + QQuickItem* rootItem = webSurface->getRootItem(); + if (rootItem && rootItem->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); + } + } + + // Fix for crash in QtWebEngineCore when rapidly switching domains + // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. + if (rootItem) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); + } + + webSurface->pause(); + + for (auto& connection : connections) { + QObject::disconnect(connection); + } + connections.clear(); + + // If the web surface was fetched out of the cache, release it back into the cache + if (cachedWebSurface) { + // If it's going back into the cache make sure to explicitly set the URL to a blank page + // in order to stop any resource consumption or audio related to the page. + if (rootItem) { + rootItem->setProperty("url", "about:blank"); + } + auto offscreenCache = DependencyManager::get(); + // FIXME prevents crash on shutdown, but we shoudln't have to do this check + if (offscreenCache) { + offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface); + } + cachedWebSurface = false; + } + webSurface.reset(); + }); + // Preload Tablet sounds DependencyManager::get()->preloadSounds(); DependencyManager::get()->createKeyboard(); @@ -3012,7 +3099,7 @@ void Application::initializeUi() { }); offscreenSurfaceCache->reserve(TabletScriptingInterface::QML, 1); - offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2); + offscreenSurfaceCache->reserve(render::entities::WebEntityRenderer::QML, 2); #endif flushMenuUpdates(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ec6b62e237..3fef4ca61a 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -64,20 +64,13 @@ #include "AboutUtil.h" #include "ResourceRequestObserver.h" +#include + static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; -const QString Web3DOverlay::QML = "Web3DOverlay.qml"; - -static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - }); -}; Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); @@ -87,34 +80,23 @@ Web3DOverlay::Web3DOverlay() { _geometryId = DependencyManager::get()->allocateID(); connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); - connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); //need to be intialized before Tablet 1st open - _webSurface = DependencyManager::get()->acquire(QML); _cachedWebSurface = true; - _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + render::entities::WebEntityRenderer::initializeWebSurface(_webSurface); } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : Billboard3DOverlay(Web3DOverlay), _url(Web3DOverlay->_url), _scriptURL(Web3DOverlay->_scriptURL), - _dpi(Web3DOverlay->_dpi), - _showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight) + _dpi(Web3DOverlay->_dpi) { _geometryId = DependencyManager::get()->allocateID(); } Web3DOverlay::~Web3DOverlay() { - disconnect(this, &Web3DOverlay::requestWebSurface, this, nullptr); - disconnect(this, &Web3DOverlay::releaseWebSurface, this, nullptr); - disconnect(this, &Web3DOverlay::resizeWebSurface, this, nullptr); - destroyWebSurface(); auto geometryCache = DependencyManager::get(); if (geometryCache) { @@ -128,81 +110,22 @@ void Web3DOverlay::rebuildWebSurface() { } void Web3DOverlay::destroyWebSurface() { - if (!_webSurface) { - return; + if (_webSurface) { + render::entities::WebEntityRenderer::releaseWebSurface(_webSurface, _cachedWebSurface, _connections); } - - QQuickItem* rootItem = _webSurface->getRootItem(); - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - if (tabletScriptingInterface) { - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } - } - - // Fix for crash in QtWebEngineCore when rapidly switching domains - // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - // stop loading - QMetaObject::invokeMethod(rootItem, "stop"); - } - - _webSurface->pause(); - - QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); - QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); - - // If the web surface was fetched out of the cache, release it back into the cache - if (_cachedWebSurface) { - // If it's going back into the cache make sure to explicitly set the URL to a blank page - // in order to stop any resource consumption or audio related to the page. - if (rootItem) { - rootItem->setProperty("url", "about:blank"); - } - auto offscreenCache = DependencyManager::get(); - // FIXME prevents crash on shutdown, but we shoudln't have to do this check - if (offscreenCache) { - offscreenCache->release(QML, _webSurface); - } - _cachedWebSurface = false; - } - _webSurface.reset(); } void Web3DOverlay::buildWebSurface() { if (_webSurface) { return; } - // FIXME the context save here is most likely unecessary since the QML surfaces now render - // off the main thread, and all GL context work is done off the main thread (I *think*) - gl::withSavedContext([&] { - // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces - // and the current rendering load) - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - if (isWebContent()) { - _webSurface = DependencyManager::get()->acquire(QML); - _cachedWebSurface = true; - _webSurface->getRootItem()->setProperty("url", _url); - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); - } else { - _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); - connect(_webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [this](QQmlContext* surfaceContext) { - setupQmlSurface(_url == TabletScriptingInterface::QML, _url == OVERLAY_LOGIN_DIALOG.toString()); - }); - _webSurface->load(_url); - _cachedWebSurface = false; - } - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); - onResizeWebSurface(); - _webSurface->resume(); - }); + render::entities::WebEntityRenderer::acquireWebSurface(_url, isWebContent(), _webSurface, _cachedWebSurface); + onResizeWebSurface(); + _webSurface->resume(); - QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); + _connections.push_back(QObject::connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent)); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived)); } void Web3DOverlay::update(float deltatime) { @@ -272,19 +195,16 @@ void Web3DOverlay::setupQmlSurface(bool isTablet, bool isLoginDialog) { _webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); _webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); - - // Override min fps for tablet UI, for silky smooth scrolling - setMaxFPS(90); } } void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { + // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces and the current rendering load) _desiredMaxFPS = maxFPS; if (_webSurface) { _webSurface->setMaxFps(_desiredMaxFPS); @@ -305,14 +225,6 @@ void Web3DOverlay::onResizeWebSurface() { _webSurface->resize(QSize(dims.x, dims.y)); } -unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { - if (_webSurface) { - return _webSurface->deviceIdByTouchPoint(x, y); - } else { - return PointerEvent::INVALID_POINTER_ID; - } -} - void Web3DOverlay::render(RenderArgs* args) { if (!_renderVisible || !getParentVisible()) { return; @@ -506,11 +418,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { _desiredMaxFPS = maxFPS.toInt(); } - auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"]; - if (showKeyboardFocusHighlight.isValid()) { - _showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool(); - } - auto inputModeValue = properties["inputMode"]; if (inputModeValue.isValid()) { QString inputModeStr = inputModeValue.toString(); @@ -573,8 +480,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. - * @property {boolean} showKeyboardFocusHighlight=true - If true, the Web overlay is highlighted when it has - * keyboard focus. * @property {string} inputMode=Touch - The user input mode to use - either "Touch" or "Mouse". */ QVariant Web3DOverlay::getProperty(const QString& property) { @@ -590,9 +495,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "maxFPS") { return _desiredMaxFPS; } - if (property == "showKeyboardFocusHighlight") { - return _showKeyboardFocusHighlight; - } if (property == "inputMode") { if (_inputMode == Mouse) { diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 548ad7abe0..03bfcdfa98 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -22,8 +22,6 @@ class Web3DOverlay : public Billboard3DOverlay { using Parent = Billboard3DOverlay; public: - - static const QString QML; static QString const TYPE; virtual QString getType() const override { return TYPE; } @@ -63,8 +61,6 @@ public: void destroyWebSurface(); void onResizeWebSurface(); - Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); - public slots: void emitScriptEvent(const QVariant& scriptMessage); @@ -73,7 +69,6 @@ signals: void webEventReceived(const QVariant& message); void resizeWebSurface(); void requestWebSurface(); - void releaseWebSurface(); protected: Transform evalRenderTransform() override; @@ -91,7 +86,6 @@ private: QString _scriptURL; float _dpi { 30.0f }; int _geometryId { 0 }; - bool _showKeyboardFocusHighlight { true }; QTouchDevice _touchDevice; @@ -99,6 +93,8 @@ private: uint8_t _currentMaxFPS { 0 }; bool _mayNeedResize { false }; + + std::vector _connections; }; #endif // hifi_Web3DOverlay_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 2942de0ba4..cb2885316c 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -30,19 +30,24 @@ using namespace render; using namespace render::entities; -static const QString WEB_ENTITY_QML = "controls/WebEntityView.qml"; +const QString WebEntityRenderer::QML = "Web3DSurface.qml"; +std::function)> WebEntityRenderer::_initializeWebSurfaceOperator = nullptr; +std::function&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr; +std::function&, bool&, std::vector&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr; + +static int MAX_WINDOW_SIZE = 4096; const float METERS_TO_INCHES = 39.3701f; -static uint32_t _currentWebCount{ 0 }; -// Don't allow more than 20 concurrent web views -static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; +static float OPAQUE_ALPHA_THRESHOLD = 0.99f; + // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; -static int MAX_WINDOW_SIZE = 4096; -static float OPAQUE_ALPHA_THRESHOLD = 0.99f; -static int DEFAULT_MAX_FPS = 10; -static int YOUTUBE_MAX_FPS = 30; +static uint8_t YOUTUBE_MAX_FPS = 30; + +// Don't allow more than 20 concurrent web views +static uint32_t _currentWebCount { 0 }; +static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; static QTouchDevice _touchDevice; static const char* URL_PROPERTY = "url"; @@ -71,13 +76,19 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e _touchDevice.setMaximumTouchPoints(4); }); _geometryId = DependencyManager::get()->allocateID(); + _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); _texture->setSource(__FUNCTION__); + + // need to be intialized early + _cachedWebSurface = true; + WebEntityRenderer::initializeWebSurface(_webSurface); + _timer.setInterval(MSECS_PER_SECOND); connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout); } -void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) { +WebEntityRenderer::~WebEntityRenderer() { destroyWebSurface(); auto geometryCache = DependencyManager::get(); @@ -86,6 +97,11 @@ void WebEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity) } } +bool WebEntityRenderer::isTransparent() const { + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f; +} + bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (_contextPosition != entity->getWorldPosition()) { return true; @@ -101,11 +117,31 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } } - if (_lastSourceUrl != entity->getSourceUrl()) { + if (_color != entity->getColor()) { return true; } - if (_lastDPI != entity->getDPI()) { + if (_alpha != entity->getAlpha()) { + return true; + } + + if (_sourceURL != entity->getSourceUrl()) { + return true; + } + + if (_dpi != entity->getDPI()) { + return true; + } + + if (_scriptURL != entity->getScriptURL()) { + return true; + } + + if (_maxFPS != entity->getMaxFPS()) { + return true; + } + + if (_inputMode != entity->getInputMode()) { return true; } @@ -113,35 +149,26 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } bool WebEntityRenderer::needsRenderUpdate() const { - { - QSharedPointer webSurface; - withReadLock([&] { - webSurface = _webSurface; - }); - if (!webSurface) { - // If we have rendered recently, and there is no web surface, we're going to create one - return true; - } + if (resultWithReadLock([&] { + // If we have rendered recently, and there is no web surface, we're going to create one + return !_webSurface; + })) { + return true; } return Parent::needsRenderUpdate(); } void WebEntityRenderer::onTimeout() { - bool needsCheck = resultWithReadLock([&] { + uint64_t lastRenderTime; + if (!resultWithReadLock([&] { + lastRenderTime = _lastRenderTime; return (_lastRenderTime != 0 && (bool)_webSurface); - }); - - if (!needsCheck) { + })) { return; } - uint64_t interval; - withReadLock([&] { - interval = usecTimestampNow() - _lastRenderTime; - }); - - if (interval > MAX_NO_RENDER_INTERVAL) { + if (usecTimestampNow() - lastRenderTime > MAX_NO_RENDER_INTERVAL) { destroyWebSurface(); } } @@ -154,9 +181,9 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto newSourceUrl = entity->getSourceUrl(); auto newContentType = getContentType(newSourceUrl); - auto currentContentType = ContentType::NoContent; + ContentType currentContentType; withReadLock([&] { - urlChanged = _lastSourceUrl != newSourceUrl; + urlChanged = _sourceURL != newSourceUrl; currentContentType = _contentType; }); @@ -168,7 +195,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } withWriteLock([&] { - _lastSourceUrl = newSourceUrl; + _sourceURL = newSourceUrl; _contentType = newContentType; }); } @@ -176,6 +203,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { + _inputMode = entity->getInputMode(); + _dpi = entity->getDPI(); + _color = entity->getColor(); + _alpha = entity->getAlpha(); + if (_contentType == ContentType::NoContent) { return; } @@ -187,19 +219,39 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } if (urlChanged && _contentType == ContentType::HtmlContent) { - _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _sourceURL); + } + + { + auto scriptURL = entity->getScriptURL(); + if (_scriptURL != scriptURL) { + _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); + _scriptURL = scriptURL; + } + } + + { + auto maxFPS = entity->getMaxFPS(); + if (_maxFPS != maxFPS) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity + if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(_maxFPS); + } + _maxFPS = maxFPS; + } + } + + if (_contextPosition != entity->getWorldPosition()) { + _contextPosition = entity->getWorldPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); } void* key = (void*)this; AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { withWriteLock([&] { - if (_contextPosition != entity->getWorldPosition()) { - // update globalPosition - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } - - _lastDPI = entity->getDPI(); glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); @@ -212,19 +264,11 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } void WebEntityRenderer::doRender(RenderArgs* args) { + PerformanceTimer perfTimer("WebEntityRenderer::render"); withWriteLock([&] { _lastRenderTime = usecTimestampNow(); }); -#ifdef WANT_EXTRA_DEBUGGING - { - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well - glm::vec4 cubeColor{ 1.0f, 0.0f, 0.0f, 1.0f }; - DependencyManager::get()->renderWireCube(batch, 1.0f, cubeColor); - } -#endif - // Try to update the texture { QSharedPointer webSurface; @@ -242,20 +286,21 @@ void WebEntityRenderer::doRender(RenderArgs* args) { } } - PerformanceTimer perfTimer("WebEntityRenderer::render"); static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); gpu::Batch& batch = *args->_batch; + glm::vec4 color; withReadLock([&] { + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + color = glm::vec4(toGlm(_color), _alpha * fadeRatio); batch.setModelTransform(_renderTransform); }); batch.setResourceTexture(0, _texture); - float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; // Turn off jitter for these entities batch.pushProjectionJitter(); - DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); - DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); + DependencyManager::get()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD); + DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); batch.popProjectionJitter(); batch.setResourceTexture(0, nullptr); } @@ -264,89 +309,49 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } -static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - }); -}; - bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { + if (_webSurface && !_webSurface->getRootItem()) { + // We're waiting on the root item + return false; + } + if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; return false; } ++_currentWebCount; - - // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); - // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces - // and the current rendering load) - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) { - // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. - surfaceContext->setContextProperty("desktop", QVariant()); - // Let us interact with the keyboard - surfaceContext->setContextProperty("tabletInterface", DependencyManager::get().data()); - }); - - // forward web events to EntityScriptingInterface - auto entities = DependencyManager::get(); - const EntityItemID entityItemID = entity->getID(); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, [=](const QVariant& message) { - emit entities->webEventReceived(entityItemID, message); - }); - - if (_contentType == ContentType::HtmlContent) { - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the - // web entity - if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty(URL_PROPERTY, _lastSourceUrl); - }); - } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl); - } + WebEntityRenderer::acquireWebSurface(_sourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); _fadeStartTime = usecTimestampNow(); _webSurface->resume(); + _connections.push_back(QObject::connect(this, &WebEntityRenderer::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent)); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &WebEntityRenderer::webEventReceived)); + const EntityItemID entityItemID = entity->getID(); + _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) { + emit DependencyManager::get()->webEventReceived(entityItemID, message); + })); + return _webSurface->getRootItem(); } void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; - ContentType contentType{ ContentType::NoContent }; + ContentType contentType = ContentType::NoContent; withWriteLock([&] { webSurface.swap(_webSurface); - std::swap(contentType, _contentType); + _contentType = contentType; }); if (webSurface) { --_currentWebCount; - QQuickItem* rootItem = webSurface->getRootItem(); - - // Fix for crash in QtWebEngineCore when rapidly switching domains - // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem && contentType == ContentType::HtmlContent) { - // stop loading - QMetaObject::invokeMethod(rootItem, "stop"); - } - - webSurface->pause(); - webSurface.reset(); + WebEntityRenderer::releaseWebSurface(webSurface, _cachedWebSurface, _connections); } } glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) const { glm::vec2 dims = glm::vec2(entity->getScaledDimensions()); - dims *= METERS_TO_INCHES * _lastDPI; + dims *= METERS_TO_INCHES * _dpi; // ensure no side is never larger then MAX_WINDOW_SIZE float max = (dims.x > dims.y) ? dims.x : dims.y; @@ -358,29 +363,83 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con } void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { - if (_webSurface) { + if (_inputMode == WebInputMode::MOUSE) { + handlePointerEvent(event); + } else if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverBeginEvent(webEvent, _touchDevice); } } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { - if (_webSurface) { + if (_inputMode == WebInputMode::MOUSE) { + PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), + event.getButton(), event.getButtons(), event.getKeyboardModifiers()); + handlePointerEvent(endEvent); + // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. + PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); + handlePointerEvent(endMoveEvent); + } else if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverEndEvent(webEvent, _touchDevice); } } void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { + if (_inputMode == WebInputMode::TOUCH) { + handlePointerEventAsTouch(event); + } else { + handlePointerEventAsMouse(event); + } +} + +void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) { if (_webSurface) { PointerEvent webEvent = event; - webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->handlePointerEvent(webEvent, _touchDevice); } } +void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { + if (!_webSurface) { + return; + } + + glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); + QPointF windowPoint(windowPos.x, windowPos.y); + + Qt::MouseButtons buttons = Qt::NoButton; + if (event.getButtons() & PointerEvent::PrimaryButton) { + buttons |= Qt::LeftButton; + } + + Qt::MouseButton button = Qt::NoButton; + if (event.getButton() == PointerEvent::PrimaryButton) { + button = Qt::LeftButton; + } + + QEvent::Type type; + switch (event.getType()) { + case PointerEvent::Press: + type = QEvent::MouseButtonPress; + break; + case PointerEvent::Release: + type = QEvent::MouseButtonRelease; + break; + case PointerEvent::Move: + type = QEvent::MouseMove; + break; + default: + return; + } + + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); +} + void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { if (_webSurface) { _webSurface->setProxyWindow(proxyWindow); @@ -394,8 +453,6 @@ QObject* WebEntityRenderer::getEventHandler() { return _webSurface->getEventHandler(); } -bool WebEntityRenderer::isTransparent() const { - float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - return fadeRatio < OPAQUE_ALPHA_THRESHOLD; -} - +void WebEntityRenderer::emitScriptEvent(const QVariant& message) { + QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 12640f697d..737b99de43 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -24,13 +24,35 @@ class WebEntityRenderer : public TypedEntityRenderer { public: WebEntityRenderer(const EntityItemPointer& entity); + ~WebEntityRenderer(); Q_INVOKABLE void hoverEnterEntity(const PointerEvent& event); Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event); Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); + static const QString QML; + static void setInitializeWebSurfaceOperator(std::function)> initializeWebSurfaceOperator) { _initializeWebSurfaceOperator = initializeWebSurfaceOperator; } + static void initializeWebSurface(QSharedPointer webSurface) { + if (_initializeWebSurfaceOperator) { + _initializeWebSurfaceOperator(webSurface); + } + } + + static void setAcquireWebSurfaceOperator(std::function&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; } + static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + if (_acquireWebSurfaceOperator) { + _acquireWebSurfaceOperator(url, htmlContent, webSurface, cachedWebSurface); + } + } + + static void setReleaseWebSurfaceOperator(std::function&, bool&, std::vector&)> releaseWebSurfaceOperator) { _releaseWebSurfaceOperator = releaseWebSurfaceOperator; } + static void releaseWebSurface(QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + if (_releaseWebSurfaceOperator) { + _releaseWebSurfaceOperator(webSurface, cachedWebSurface, connections); + } + } + protected: - virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; @@ -39,9 +61,13 @@ protected: virtual bool wantsHandControllerPointerEvents() const override { return true; } virtual bool wantsKeyboardFocus() const override { return true; } + virtual void setProxyWindow(QWindow* proxyWindow) override; virtual QObject* getEventHandler() override; + void handlePointerEventAsTouch(const PointerEvent& event); + void handlePointerEventAsMouse(const PointerEvent& event); + private: void onTimeout(); bool buildWebSurface(const TypedEntityPointer& entity); @@ -49,30 +75,47 @@ private: bool hasWebSurface(); glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; - int _geometryId{ 0 }; enum class ContentType { NoContent, HtmlContent, QmlContent }; - static ContentType getContentType(const QString& urlString); + ContentType _contentType { ContentType::NoContent }; - ContentType _contentType{ ContentType::NoContent }; QSharedPointer _webSurface; - glm::vec3 _contextPosition; + bool _cachedWebSurface { false }; gpu::TexturePointer _texture; - QString _lastSourceUrl; - uint16_t _lastDPI; + + glm::u8vec3 _color; + float _alpha { 1.0f }; + + QString _sourceURL; + uint16_t _dpi; + QString _scriptURL; + uint8_t _maxFPS; + WebInputMode _inputMode; + + glm::vec3 _contextPosition; + QTimer _timer; uint64_t _lastRenderTime { 0 }; + + std::vector _connections; + + static std::function)> _initializeWebSurfaceOperator; + static std::function&, bool&)> _acquireWebSurfaceOperator; + static std::function&, bool&, std::vector&)> _releaseWebSurfaceOperator; + +public slots: + void emitScriptEvent(const QVariant& scriptMessage); + +signals: + void scriptEventReceived(const QVariant& message); + void webEventReceived(const QVariant& message); }; -} } // namespace - -#if 0 - virtual void emitScriptEvent(const QVariant& message) override; -#endif +} } #endif // hifi_RenderableWebEntityItem_h diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3ae3a647cf..dfdc7933a9 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1285,6 +1285,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * The entity has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. * @typedef {object} Entities.EntityProperties-Web * @property {Vec3} dimensions=0.1,0.1,0.01 - The dimensions of the entity. + * @property {Color} color=255,255,255 - The color of the web surface. + * @property {number} alpha=1 - The alpha of the web surface. * @property {string} sourceUrl="" - The URL of the Web page to display. This value does not change as you or others navigate * on the Web entity. * @property {number} dpi=30 - The resolution to display the page at, in dots per inch. If you convert this to dots per meter @@ -1689,6 +1691,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Web only if (_type == EntityTypes::Web) { + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DPI, dpi); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_URL, scriptURL); @@ -3051,6 +3056,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy } if (properties.getType() == EntityTypes::Web) { + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, properties.getScriptURL()); diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 88612c8be6..7a7e211d0d 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -232,6 +232,12 @@ void ShapeEntityItem::setAlpha(float alpha) { }); } +float ShapeEntityItem::getAlpha() const { + return resultWithReadLock([&] { + return _alpha; + }); +} + void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index c89a8934f8..6e36e15a84 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -74,7 +74,7 @@ public: void setShape(const entity::Shape& shape); void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); } - float getAlpha() const { return _alpha; }; + float getAlpha() const; void setAlpha(float alpha); glm::u8vec3 getColor() const; @@ -102,9 +102,8 @@ public: std::shared_ptr getMaterial() { return _material; } protected: - - float _alpha { 1.0f }; glm::u8vec3 _color; + float _alpha { 1.0f }; entity::Shape _shape { entity::Shape::Sphere }; //! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 38ecbb03df..c0cdb081a2 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -32,20 +32,25 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Web; - _dpi = ENTITY_ITEM_DEFAULT_DPI; } -const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; - void WebEntityItem::setUnscaledDimensions(const glm::vec3& value) { // NOTE: Web Entities always have a "depth" of 1cm. + const float WEB_ENTITY_ITEM_FIXED_DEPTH = 0.01f; EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, WEB_ENTITY_ITEM_FIXED_DEPTH)); } EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(sourceUrl, getSourceUrl); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dpi, getDPI); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptURL, getScriptURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); return properties; } @@ -53,8 +58,14 @@ bool WebEntityItem::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); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(sourceUrl, setSourceUrl); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dpi, setDPI); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptURL, setScriptURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); if (somethingChanged) { bool wantDebug = false; @@ -78,16 +89,28 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i int bytesRead = 0; const unsigned char* dataAt = data; + READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY(PROP_DPI, uint16_t, setDPI); + READ_ENTITY_PROPERTY(PROP_SCRIPT_URL, QString, setScriptURL); + READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS); + READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); return bytesRead; } EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + requestedProperties += PROP_SOURCE_URL; requestedProperties += PROP_DPI; + requestedProperties += PROP_SCRIPT_URL; + requestedProperties += PROP_MAX_FPS; + requestedProperties += PROP_INPUT_MODE; return requestedProperties; } @@ -100,8 +123,14 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, _sourceUrl); - APPEND_ENTITY_PROPERTY(PROP_DPI, _dpi); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, getSourceUrl()); + APPEND_ENTITY_PROPERTY(PROP_DPI, getDPI()); + APPEND_ENTITY_PROPERTY(PROP_SCRIPT_URL, getScriptURL()); + APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS()); + APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); } bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -158,6 +187,30 @@ bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, co } } +void WebEntityItem::setColor(const glm::u8vec3& value) { + withWriteLock([&] { + _color = value; + }); +} + +glm::u8vec3 WebEntityItem::getColor() const { + return resultWithReadLock([&] { + return _color; + }); +} + +void WebEntityItem::setAlpha(float alpha) { + withWriteLock([&] { + _alpha = alpha; + }); +} + +float WebEntityItem::getAlpha() const { + return resultWithReadLock([&] { + return _alpha; + }); +} + void WebEntityItem::setSourceUrl(const QString& value) { withWriteLock([&] { if (_sourceUrl != value) { @@ -173,17 +226,63 @@ void WebEntityItem::setSourceUrl(const QString& value) { } QString WebEntityItem::getSourceUrl() const { - QString result; - withReadLock([&] { - result = _sourceUrl; + return resultWithReadLock([&] { + return _sourceUrl; }); - return result; } void WebEntityItem::setDPI(uint16_t value) { - _dpi = value; + withWriteLock([&] { + _dpi = value; + }); } uint16_t WebEntityItem::getDPI() const { - return _dpi; + return resultWithReadLock([&] { + return _dpi; + }); } + +void WebEntityItem::setScriptURL(const QString& value) { + withWriteLock([&] { + if (_scriptURL != value) { + auto newURL = QUrl::fromUserInput(value); + + if (newURL.isValid()) { + _scriptURL = newURL.toDisplayString(); + } else { + qCDebug(entities) << "Clearing web entity source URL since" << value << "cannot be parsed to a valid URL."; + } + } + }); +} + +QString WebEntityItem::getScriptURL() const { + return resultWithReadLock([&] { + return _scriptURL; + }); +} + +void WebEntityItem::setMaxFPS(uint8_t value) { + withWriteLock([&] { + _maxFPS = value; + }); +} + +uint8_t WebEntityItem::getMaxFPS() const { + return resultWithReadLock([&] { + return _maxFPS; + }); +} + +void WebEntityItem::setInputMode(const WebInputMode& value) { + withWriteLock([&] { + _inputMode = value; + }); +} + +WebInputMode WebEntityItem::getInputMode() const { + return resultWithReadLock([&] { + return _inputMode; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index f1d1bbb313..c566bcb5f7 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -52,18 +52,38 @@ public: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + glm::u8vec3 getColor() const; + void setColor(const glm::u8vec3& value); + + float getAlpha() const; + void setAlpha(float alpha); + static const QString DEFAULT_SOURCE_URL; - virtual void setSourceUrl(const QString& value); + void setSourceUrl(const QString& value); QString getSourceUrl() const; void setDPI(uint16_t value); uint16_t getDPI() const; + void setScriptURL(const QString& value); + QString getScriptURL() const; + static const uint8_t DEFAULT_MAX_FPS; + void setMaxFPS(uint8_t value); + uint8_t getMaxFPS() const; + + void setInputMode(const WebInputMode& value); + WebInputMode getInputMode() const; protected: + glm::u8vec3 _color; + float _alpha { 1.0f }; + QString _sourceUrl; uint16_t _dpi; + QString _scriptURL; + uint8_t _maxFPS; + WebInputMode _inputMode; }; #endif // hifi_WebEntityItem_h diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9417809287..38e45f0206 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -257,6 +257,7 @@ enum class EntityVersion : PacketVersion { UpdatedPolyLines, FixProtocolVersionBumpMismatch, MigrateOverlayRenderProperties, + MissingWebEntityProperties, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 5bcca0821f..a064be79bd 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -500,6 +500,9 @@ void SharedObject::onTimer() { } { + if (_maxFps == 0) { + return; + } auto minRenderInterval = USECS_PER_SECOND / _maxFps; auto lastInterval = usecTimestampNow() - _lastRenderTime; // Don't exceed the framerate limit diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index b95a8f117d..b8c6808afa 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -35,7 +35,7 @@ public: Q_INVOKABLE void synthesizeKeyPress(QString key, QObject* targetOverride = nullptr); Q_INVOKABLE void lowerKeyboard(); PointerEvent::EventType choosePointerEventType(QEvent::Type type); - unsigned int deviceIdByTouchPoint(qreal x, qreal y); + Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); signals: void focusObjectChanged(QObject* newFocus); From 6fd34bf1f0021e7901a163f21c42b247507e1373 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 11 Jan 2019 14:21:03 -0800 Subject: [PATCH 3/4] setup qml surface contexts --- interface/src/Application.cpp | 99 ++++++++++++++----- interface/src/Application.h | 2 + interface/src/ui/overlays/Web3DOverlay.cpp | 69 +------------ interface/src/ui/overlays/Web3DOverlay.h | 3 +- .../src/RenderableWebEntityItem.cpp | 60 ++++++----- .../src/RenderableWebEntityItem.h | 14 +-- .../qml/src/qml/impl/RenderEventHandler.cpp | 29 +++--- .../qml/src/qml/impl/RenderEventHandler.h | 2 + libraries/shared/src/WebInputMode.cpp | 5 +- 9 files changed, 142 insertions(+), 141 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e2e288cd36..3d0e135fed 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2317,35 +2317,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; }); - render::entities::WebEntityRenderer::setInitializeWebSurfaceOperator([](QSharedPointer webSurface) { - webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); - webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); - webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); - webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - }); render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { bool isTablet = url == TabletScriptingInterface::QML; if (htmlContent) { - // FIXME use the surface cache instead of explicit creation - webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - }); - }); - webSurface->load(render::entities::WebEntityRenderer::QML, [url](QQmlContext* context, QObject* item) { - item->setProperty("url", url); - }); -#if 0 - // This doesn't work for some reason webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); cachedWebSurface = true; - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url, webSurface](QQmlContext* surfaceContext) { - webSurface->getRootItem()->setProperty("url", url); - }); -#endif + auto rootItemLoadedFunctor = [url, webSurface] { + webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, url); + }; + if (webSurface->getRootItem()) { + rootItemLoadedFunctor(); + } else { + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + } } else { + // FIXME: the tablet should use the OffscreenQmlSurfaceCache webSurface = QSharedPointer(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) { AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown @@ -2353,9 +2339,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo delete webSurface; }); }); - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, [url](QQmlContext* surfaceContext) { - //setupQmlSurface(isTablet, url == OVERLAY_LOGIN_DIALOG.toString()); - }); + auto rootItemLoadedFunctor = [webSurface, url, isTablet] { + Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == OVERLAY_LOGIN_DIALOG.toString()); + }; + if (webSurface->getRootItem()) { + rootItemLoadedFunctor(); + } else { + connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + } webSurface->load(url); cachedWebSurface = false; } @@ -2394,7 +2385,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo rootItem->setProperty("url", "about:blank"); } auto offscreenCache = DependencyManager::get(); - // FIXME prevents crash on shutdown, but we shoudln't have to do this check if (offscreenCache) { offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface); } @@ -3239,6 +3229,61 @@ void Application::onDesktopRootItemCreated(QQuickItem* rootItem) { #endif } +void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) { + surfaceContext->setContextProperty("Users", DependencyManager::get().data()); + surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); + surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get().data()); + surfaceContext->setContextProperty("Preferences", DependencyManager::get().data()); + surfaceContext->setContextProperty("Vec3", new Vec3()); + surfaceContext->setContextProperty("Quat", new Quat()); + surfaceContext->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); + surfaceContext->setContextProperty("Entities", DependencyManager::get().data()); + surfaceContext->setContextProperty("Snapshot", DependencyManager::get().data()); + + if (setAdditionalContextProperties) { + auto tabletScriptingInterface = DependencyManager::get(); + auto flags = tabletScriptingInterface->getFlags(); + + surfaceContext->setContextProperty("offscreenFlags", flags); + surfaceContext->setContextProperty("AddressManager", DependencyManager::get().data()); + + surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); + surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); + surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); + + surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + + // in Qt 5.10.0 there is already an "Audio" object in the QML context + // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" + surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); + + surfaceContext->setContextProperty("AudioStats", DependencyManager::get()->getStats().data()); + surfaceContext->setContextProperty("fileDialogHelper", new FileDialogHelper()); + surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); + surfaceContext->setContextProperty("Assets", DependencyManager::get().data()); + surfaceContext->setContextProperty("LODManager", DependencyManager::get().data()); + surfaceContext->setContextProperty("OctreeStats", DependencyManager::get().data()); + surfaceContext->setContextProperty("DCModel", DependencyManager::get().data()); + surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); + surfaceContext->setContextProperty("AvatarList", DependencyManager::get().data()); + surfaceContext->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); + surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get().data()); + surfaceContext->setContextProperty("SoundCache", DependencyManager::get().data()); + surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); + surfaceContext->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); + surfaceContext->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); + surfaceContext->setContextProperty("Controller", DependencyManager::get().data()); + surfaceContext->setContextProperty("Pointers", DependencyManager::get().data()); + surfaceContext->setContextProperty("Window", DependencyManager::get().data()); + surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); + surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); + surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); + } +} + void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("updateCamera"); diff --git a/interface/src/Application.h b/interface/src/Application.h index dc30c3c22c..2f5ef38412 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -589,6 +589,8 @@ private: void maybeToggleMenuVisible(QMouseEvent* event) const; void toggleTabletUI(bool shouldOpen = false) const; + static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties); + MainWindow* _window; QElapsedTimer& _sessionRunTimer; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 3fef4ca61a..de7ed45819 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -82,9 +82,8 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); - //need to be intialized before Tablet 1st open - _cachedWebSurface = true; - render::entities::WebEntityRenderer::initializeWebSurface(_webSurface); + render::entities::WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); + _webSurface->resume(); } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : @@ -145,64 +144,6 @@ bool Web3DOverlay::isWebContent() const { return false; } -void Web3DOverlay::setupQmlSurface(bool isTablet, bool isLoginDialog) { - _webSurface->getSurfaceContext()->setContextProperty("Users", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Preferences", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Vec3", new Vec3()); - _webSurface->getSurfaceContext()->setContextProperty("Quat", new Quat()); - _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); - _webSurface->getSurfaceContext()->setContextProperty("Entities", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Snapshot", DependencyManager::get().data()); - - if (isTablet || isLoginDialog) { - _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data()); - } - if (isTablet) { - auto tabletScriptingInterface = DependencyManager::get(); - auto flags = tabletScriptingInterface->getFlags(); - - _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); - _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - - _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED - _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); - - // in Qt 5.10.0 there is already an "Audio" object in the QML context - // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" - _webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); - - _webSurface->getSurfaceContext()->setContextProperty("AudioStats", DependencyManager::get()->getStats().data()); - _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); - _webSurface->getSurfaceContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get()); - _webSurface->getSurfaceContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Assets", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("LODManager", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("DCModel", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("AvatarInputs", AvatarInputs::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); - _webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); - _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); - _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); - _webSurface->getSurfaceContext()->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); - _webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); - } -} - void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces and the current rendering load) _desiredMaxFPS = maxFPS; @@ -514,14 +455,14 @@ void Web3DOverlay::setURL(const QString& url) { if (wasWebContent && isWebContent()) { // If we're just targeting a new web URL, then switch to that without messing around // with the underlying QML - AbstractViewStateInterface::instance()->postLambdaEvent([this, url] { - _webSurface->getRootItem()->setProperty("url", _url); + AbstractViewStateInterface::instance()->postLambdaEvent([this] { + _webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, _url); _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); }); } else { // If we're switching to or from web content, or between different QML content // we need to destroy and rebuild the entire QML surface - AbstractViewStateInterface::instance()->postLambdaEvent([this, url] { + AbstractViewStateInterface::instance()->postLambdaEvent([this] { rebuildWebSurface(); }); } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 03bfcdfa98..632b9cda3f 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -74,13 +74,12 @@ protected: Transform evalRenderTransform() override; private: - void setupQmlSurface(bool isTablet, bool isLoginDialog); void rebuildWebSurface(); bool isWebContent() const; InputMode _inputMode { Touch }; QSharedPointer _webSurface; - bool _cachedWebSurface{ false }; + bool _cachedWebSurface { false }; gpu::TexturePointer _texture; QString _url; QString _scriptURL; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cb2885316c..c45f09757b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -31,8 +31,8 @@ using namespace render; using namespace render::entities; const QString WebEntityRenderer::QML = "Web3DSurface.qml"; +const char* WebEntityRenderer::URL_PROPERTY = "url"; -std::function)> WebEntityRenderer::_initializeWebSurfaceOperator = nullptr; std::function&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr; std::function&, bool&, std::vector&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr; @@ -50,7 +50,6 @@ static uint32_t _currentWebCount { 0 }; static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; static QTouchDevice _touchDevice; -static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -80,9 +79,14 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); _texture->setSource(__FUNCTION__); - // need to be intialized early - _cachedWebSurface = true; - WebEntityRenderer::initializeWebSurface(_webSurface); + if (_currentWebCount < MAX_CONCURRENT_WEB_VIEWS) { + _currentWebCount++; + WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); + _contentType = ContentType::HtmlContent; + _fadeStartTime = usecTimestampNow(); + _webSurface->resume(); + qDebug() << "boop" << this << _webSurface << _webSurface->getRootItem(); + } _timer.setInterval(MSECS_PER_SECOND); connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout); @@ -149,9 +153,8 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe } bool WebEntityRenderer::needsRenderUpdate() const { - if (resultWithReadLock([&] { - // If we have rendered recently, and there is no web surface, we're going to create one - return !_webSurface; + if (resultWithReadLock([this] { + return _prevHasWebSurface != hasWebSurface() || _needsURLUpdate; })) { return true; } @@ -188,16 +191,21 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene }); if (urlChanged) { - if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { - destroyWebSurface(); - // If we destroyed the surface, the URL change will be implicitly handled by the re-creation - urlChanged = false; - } - withWriteLock([&] { + _needsURLUpdate = true; _sourceURL = newSourceUrl; _contentType = newContentType; }); + + if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { + qDebug() << "boop2" << this << _webSurface << _webSurface->getRootItem(); + destroyWebSurface(); + // If we destroyed the surface, the URL change will be implicitly handled by the re-creation + urlChanged = false; + withWriteLock([&] { + _needsURLUpdate = false; + }); + } } } @@ -209,17 +217,28 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); if (_contentType == ContentType::NoContent) { + _prevHasWebSurface = false; return; } // This work must be done on the main thread // If we couldn't create a new web surface, exit - if (!hasWebSurface() && !buildWebSurface(entity)) { + if (!_webSurface) { + qDebug() << "boop3" << this << _webSurface << _webSurface->getRootItem(); + buildWebSurface(entity); + } + + _prevHasWebSurface = hasWebSurface(); + if (!_prevHasWebSurface) { + qDebug() << "boop4" << this << _webSurface << _webSurface->getRootItem(); return; } - if (urlChanged && _contentType == ContentType::HtmlContent) { + qDebug() << "boop6" << this << _webSurface << _webSurface->getRootItem(); + if (_needsURLUpdate && _contentType == ContentType::HtmlContent) { + qDebug() << "boop7" << this << _webSurface << _webSurface->getRootItem(); _webSurface->getRootItem()->setProperty(URL_PROPERTY, _sourceURL); + _needsURLUpdate = false; } { @@ -252,7 +271,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene void* key = (void*)this; AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { withWriteLock([&] { - glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); updateModelTransformAndBound(); @@ -305,16 +323,11 @@ void WebEntityRenderer::doRender(RenderArgs* args) { batch.setResourceTexture(0, nullptr); } -bool WebEntityRenderer::hasWebSurface() { +bool WebEntityRenderer::hasWebSurface() const { return (bool)_webSurface && _webSurface->getRootItem(); } bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { - if (_webSurface && !_webSurface->getRootItem()) { - // We're waiting on the root item - return false; - } - if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; return false; @@ -366,6 +379,7 @@ void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (_inputMode == WebInputMode::MOUSE) { handlePointerEvent(event); } else if (_webSurface) { + qDebug() << "boop5" << this << _webSurface << _webSurface->getRootItem(); PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); _webSurface->hoverBeginEvent(webEvent, _touchDevice); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 737b99de43..545a6c878a 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -31,12 +31,7 @@ public: Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); static const QString QML; - static void setInitializeWebSurfaceOperator(std::function)> initializeWebSurfaceOperator) { _initializeWebSurfaceOperator = initializeWebSurfaceOperator; } - static void initializeWebSurface(QSharedPointer webSurface) { - if (_initializeWebSurfaceOperator) { - _initializeWebSurfaceOperator(webSurface); - } - } + static const char* URL_PROPERTY; static void setAcquireWebSurfaceOperator(std::function&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; } static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { @@ -72,7 +67,7 @@ private: void onTimeout(); bool buildWebSurface(const TypedEntityPointer& entity); void destroyWebSurface(); - bool hasWebSurface(); + bool hasWebSurface() const; glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; int _geometryId{ 0 }; @@ -84,7 +79,9 @@ private: static ContentType getContentType(const QString& urlString); ContentType _contentType { ContentType::NoContent }; - QSharedPointer _webSurface; + QSharedPointer _webSurface { nullptr }; + bool _prevHasWebSurface { false }; + bool _needsURLUpdate { false }; bool _cachedWebSurface { false }; gpu::TexturePointer _texture; @@ -104,7 +101,6 @@ private: std::vector _connections; - static std::function)> _initializeWebSurfaceOperator; static std::function&, bool&)> _acquireWebSurfaceOperator; static std::function&, bool&, std::vector&)> _releaseWebSurfaceOperator; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 46fc3490a7..97c222337b 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -66,6 +66,7 @@ void RenderEventHandler::onInitalize() { qFatal("Unable to make QML rendering context current on render thread"); } _shared->initializeRenderControl(_canvas.getContext()); + _initialized = true; } void RenderEventHandler::resize() { @@ -150,22 +151,24 @@ void RenderEventHandler::onRender() { } void RenderEventHandler::onQuit() { - if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) { - qFatal("QML rendering context not current on render thread"); - } + if (_initialized) { + if (_canvas.getContext() != QOpenGLContextWrapper::currentContext()) { + qFatal("QML rendering context not current on render thread"); + } - if (_depthStencil) { - glDeleteRenderbuffers(1, &_depthStencil); - _depthStencil = 0; - } + if (_depthStencil) { + glDeleteRenderbuffers(1, &_depthStencil); + _depthStencil = 0; + } - if (_fbo) { - glDeleteFramebuffers(1, &_fbo); - _fbo = 0; - } + if (_fbo) { + glDeleteFramebuffers(1, &_fbo); + _fbo = 0; + } - _shared->shutdownRendering(_canvas, _currentSize); - _canvas.doneCurrent(); + _shared->shutdownRendering(_canvas, _currentSize); + _canvas.doneCurrent(); + } _canvas.moveToThreadWithContext(qApp->thread()); moveToThread(qApp->thread()); QThread::currentThread()->quit(); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.h b/libraries/qml/src/qml/impl/RenderEventHandler.h index 1e2f9945f3..86046c3721 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.h +++ b/libraries/qml/src/qml/impl/RenderEventHandler.h @@ -53,6 +53,8 @@ private: uint32_t _fbo{ 0 }; uint32_t _depthStencil{ 0 }; + + bool _initialized { false }; }; }}} // namespace hifi::qml::impl diff --git a/libraries/shared/src/WebInputMode.cpp b/libraries/shared/src/WebInputMode.cpp index 7b480424dc..292fb50763 100644 --- a/libraries/shared/src/WebInputMode.cpp +++ b/libraries/shared/src/WebInputMode.cpp @@ -9,9 +9,8 @@ #include "WebInputMode.h" const char* webInputModeNames[] = { - "none", - "yaw", - "full" + "touch", + "mouse" }; static const size_t WEB_INPUT_MODE_NAMES = (sizeof(webInputModeNames) / sizeof(webInputModeNames[0])); From 1d2270e9c70435b19223d0313129115f97248ca1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 14 Jan 2019 15:40:37 -0800 Subject: [PATCH 4/4] web entity fixes --- interface/src/Application.cpp | 8 +- interface/src/ui/overlays/Web3DOverlay.cpp | 9 +- interface/src/ui/overlays/Web3DOverlay.h | 4 +- .../src/RenderableWebEntityItem.cpp | 134 +++++++----------- .../src/RenderableWebEntityItem.h | 5 +- .../entities/src/EntityItemProperties.cpp | 3 + 6 files changed, 69 insertions(+), 94 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3d0e135fed..e016b4afc7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2317,7 +2317,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; }); - render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { + render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([this](const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { bool isTablet = url == TabletScriptingInterface::QML; if (htmlContent) { webSurface = DependencyManager::get()->acquire(render::entities::WebEntityRenderer::QML); @@ -2328,7 +2328,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (webSurface->getRootItem()) { rootItemLoadedFunctor(); } else { - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); } } else { // FIXME: the tablet should use the OffscreenQmlSurfaceCache @@ -2345,7 +2345,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (webSurface->getRootItem()) { rootItemLoadedFunctor(); } else { - connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); + QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor); } webSurface->load(url); cachedWebSurface = false; @@ -2354,7 +2354,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const uint8_t TABLET_FPS = 90; webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS); }); - render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { + render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { QQuickItem* rootItem = webSurface->getRootItem(); if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index de7ed45819..eb61ca7281 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -82,8 +82,7 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface); connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); - render::entities::WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); - _webSurface->resume(); + buildWebSurface(true); } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : @@ -114,12 +113,12 @@ void Web3DOverlay::destroyWebSurface() { } } -void Web3DOverlay::buildWebSurface() { +void Web3DOverlay::buildWebSurface(bool overrideWeb) { if (_webSurface) { return; } - render::entities::WebEntityRenderer::acquireWebSurface(_url, isWebContent(), _webSurface, _cachedWebSurface); + render::entities::WebEntityRenderer::acquireWebSurface(_url, overrideWeb || isWebContent(), _webSurface, _cachedWebSurface); onResizeWebSurface(); _webSurface->resume(); @@ -172,7 +171,7 @@ void Web3DOverlay::render(RenderArgs* args) { } if (!_webSurface) { - emit requestWebSurface(); + emit requestWebSurface(false); return; } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 632b9cda3f..4265c35699 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -57,7 +57,7 @@ public: Mouse }; - void buildWebSurface(); + void buildWebSurface(bool overrideWeb = false); void destroyWebSurface(); void onResizeWebSurface(); @@ -68,7 +68,7 @@ signals: void scriptEventReceived(const QVariant& message); void webEventReceived(const QVariant& message); void resizeWebSurface(); - void requestWebSurface(); + void requestWebSurface(bool overrideWeb); protected: Transform evalRenderTransform() override; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index c45f09757b..38d1320aa2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -79,14 +79,8 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e _texture = gpu::Texture::createExternal(OffscreenQmlSurface::getDiscardLambda()); _texture->setSource(__FUNCTION__); - if (_currentWebCount < MAX_CONCURRENT_WEB_VIEWS) { - _currentWebCount++; - WebEntityRenderer::acquireWebSurface("", true, _webSurface, _cachedWebSurface); - _contentType = ContentType::HtmlContent; - _fadeStartTime = usecTimestampNow(); - _webSurface->resume(); - qDebug() << "boop" << this << _webSurface << _webSurface->getRootItem(); - } + _contentType = ContentType::HtmlContent; + buildWebSurface(entity, ""); _timer.setInterval(MSECS_PER_SECOND); connect(&_timer, &QTimer::timeout, this, &WebEntityRenderer::onTimeout); @@ -154,7 +148,7 @@ bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointe bool WebEntityRenderer::needsRenderUpdate() const { if (resultWithReadLock([this] { - return _prevHasWebSurface != hasWebSurface() || _needsURLUpdate; + return !_webSurface; })) { return true; } @@ -181,30 +175,22 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene // destroy the existing surface (because surfaces don't support changing the root // object, so subsequent loads of content just overlap the existing content bool urlChanged = false; + auto newSourceURL = entity->getSourceUrl(); { - auto newSourceUrl = entity->getSourceUrl(); - auto newContentType = getContentType(newSourceUrl); + auto newContentType = getContentType(newSourceURL); ContentType currentContentType; withReadLock([&] { - urlChanged = _sourceURL != newSourceUrl; + urlChanged = _sourceURL != newSourceURL; currentContentType = _contentType; }); if (urlChanged) { withWriteLock([&] { - _needsURLUpdate = true; - _sourceURL = newSourceUrl; _contentType = newContentType; }); if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { - qDebug() << "boop2" << this << _webSurface << _webSurface->getRootItem(); destroyWebSurface(); - // If we destroyed the surface, the URL change will be implicitly handled by the re-creation - urlChanged = false; - withWriteLock([&] { - _needsURLUpdate = false; - }); } } } @@ -217,67 +203,63 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); if (_contentType == ContentType::NoContent) { - _prevHasWebSurface = false; return; } // This work must be done on the main thread - // If we couldn't create a new web surface, exit if (!_webSurface) { - qDebug() << "boop3" << this << _webSurface << _webSurface->getRootItem(); - buildWebSurface(entity); + buildWebSurface(entity, newSourceURL); } - _prevHasWebSurface = hasWebSurface(); - if (!_prevHasWebSurface) { - qDebug() << "boop4" << this << _webSurface << _webSurface->getRootItem(); - return; - } - - qDebug() << "boop6" << this << _webSurface << _webSurface->getRootItem(); - if (_needsURLUpdate && _contentType == ContentType::HtmlContent) { - qDebug() << "boop7" << this << _webSurface << _webSurface->getRootItem(); - _webSurface->getRootItem()->setProperty(URL_PROPERTY, _sourceURL); - _needsURLUpdate = false; - } - - { - auto scriptURL = entity->getScriptURL(); - if (_scriptURL != scriptURL) { - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); - _scriptURL = scriptURL; - } - } - - { - auto maxFPS = entity->getMaxFPS(); - if (_maxFPS != maxFPS) { - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity - if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(_maxFPS); + if (_webSurface && _webSurface->getRootItem()) { + if (_webSurface->getRootItem()) { + if (_contentType == ContentType::HtmlContent && urlChanged) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); + _sourceURL = newSourceURL; + } + + { + auto scriptURL = entity->getScriptURL(); + if (_scriptURL != scriptURL) { + _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); + _scriptURL = scriptURL; + } + } + + { + auto maxFPS = entity->getMaxFPS(); + if (_maxFPS != maxFPS) { + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity + if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); + } else { + _webSurface->setMaxFps(maxFPS); + } + _maxFPS = maxFPS; + } + } + + { + auto contextPosition = entity->getWorldPosition(); + if (_contextPosition != contextPosition) { + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(contextPosition)); + _contextPosition = contextPosition; + } } - _maxFPS = maxFPS; } - } - if (_contextPosition != entity->getWorldPosition()) { - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } - - void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { - withWriteLock([&] { - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); - updateModelTransformAndBound(); - _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getScaledDimensions()); + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + withWriteLock([&] { + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + _renderTransform.postScale(entity->getScaledDimensions()); + }); }); - }); + } }); } @@ -323,18 +305,14 @@ void WebEntityRenderer::doRender(RenderArgs* args) { batch.setResourceTexture(0, nullptr); } -bool WebEntityRenderer::hasWebSurface() const { - return (bool)_webSurface && _webSurface->getRootItem(); -} - -bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { +void WebEntityRenderer::buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; - return false; + return; } ++_currentWebCount; - WebEntityRenderer::acquireWebSurface(_sourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); + WebEntityRenderer::acquireWebSurface(newSourceURL, _contentType == ContentType::HtmlContent, _webSurface, _cachedWebSurface); _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -344,8 +322,6 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _connections.push_back(QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, [entityItemID](const QVariant& message) { emit DependencyManager::get()->webEventReceived(entityItemID, message); })); - - return _webSurface->getRootItem(); } void WebEntityRenderer::destroyWebSurface() { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 545a6c878a..4affb5819d 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -65,9 +65,8 @@ protected: private: void onTimeout(); - bool buildWebSurface(const TypedEntityPointer& entity); + void buildWebSurface(const EntityItemPointer& entity, const QString& newSourceURL); void destroyWebSurface(); - bool hasWebSurface() const; glm::vec2 getWindowSize(const TypedEntityPointer& entity) const; int _geometryId{ 0 }; @@ -80,8 +79,6 @@ private: ContentType _contentType { ContentType::NoContent }; QSharedPointer _webSurface { nullptr }; - bool _prevHasWebSurface { false }; - bool _needsURLUpdate { false }; bool _cachedWebSurface { false }; gpu::TexturePointer _texture; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index dfdc7933a9..c2928d8cd0 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3496,6 +3496,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int } if (properties.getType() == EntityTypes::Web) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_URL, QString, setScriptURL);