diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 614f7bd9fe..bad60643ec 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -663,14 +663,6 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { _rig->restoreRoleAnimation(role); } -void MyAvatar::prefetchAnimation(const QString& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "prefetchAnimation", Q_ARG(const QString&, url)); - return; - } - _rig->prefetchAnimation(url); -} - void MyAvatar::saveData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e320c0e3de..fee1a9add3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -143,9 +143,6 @@ public: // remove an animation role override and return to the standard animation. Q_INVOKABLE void restoreRoleAnimation(const QString& role); - // prefetch animation - Q_INVOKABLE void prefetchAnimation(const QString& url); - // Adds handler(animStateDictionaryIn) => animStateDictionaryOut, which will be invoked just before each animGraph state update. // The handler will be called with an animStateDictionaryIn that has all those properties specified by the (possibly empty) // propertiesList argument. However for debugging, if the properties argument is null, all internal animGraph state is provided. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b3f5e30d40..a7115199a2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -152,14 +152,6 @@ void Rig::restoreRoleAnimation(const QString& role) { } } -void Rig::prefetchAnimation(const QString& url) { - - // This will begin loading the NetworkGeometry for the given URL. - // which should speed us up if we request it later via overrideAnimation. - auto clipNode = std::make_shared("prefetch", url, 0, 0, 1.0, false, false); - _prefetchedAnimations.push_back(clipNode); -} - void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 0ce6f6639e..363006d48c 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -94,7 +94,6 @@ public: QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); - void prefetchAnimation(const QString& url); void initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset); void reset(const FBXGeometry& geometry); @@ -319,7 +318,6 @@ protected: SimpleMovingAverage _averageLateralSpeed { 10 }; std::map _origRoleAnimations; - std::vector _prefetchedAnimations; bool _lastEnableInverseKinematics { true }; bool _enableInverseKinematics { true }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 8e46521bfc..013385a169 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -380,7 +380,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE); + _ambientTexture = textureCache->getTexture(zone->getKeyLightProperties().getAmbientURL(), NetworkTexture::CUBE_TEXTURE); _pendingAmbientTexture = true; if (_ambientTexture && _ambientTexture->isLoaded()) { @@ -410,7 +410,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); + _skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), NetworkTexture::CUBE_TEXTURE); _pendingSkyboxTexture = true; if (_skyboxTexture && _skyboxTexture->isLoaded()) { diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index ba59d66cf4..c634326a31 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -54,6 +54,9 @@ public: virtual bool lifetimeIsOver() { return false; } virtual quint64 getExpires() { return 0; } + virtual bool isMine() { return _isMine; } + virtual void setIsMine(bool value) { _isMine = value; } + bool locallyAddedButNotYetReceived = false; virtual bool shouldSuppressLocationEdits() { return false; } @@ -89,6 +92,7 @@ protected: QUuid _id; EntityActionType _type; bool _active { false }; + bool _isMine { false }; // did this interface create / edit this action? }; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2d1bbf2f88..dc017f81e6 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -658,6 +658,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef if (_simulationOwner.set(newSimOwner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; somethingChanged = true; + // recompute weOwnSimulation so that if this is the packet that tells use we are the owner, + // we ignore the physics changes from this packet. + weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } } { // When we own the simulation we don't accept updates to the entity's transform/velocities @@ -1702,6 +1705,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI success = action->updateArguments(arguments); if (success) { + action->setIsMine(true); serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; } else { @@ -1808,7 +1812,9 @@ void EntityItem::deserializeActionsInternal() { EntityActionPointer action = _objectActions[actionID]; // TODO: make sure types match? there isn't currently a way to // change the type of an existing action. - action->deserialize(serializedAction); + if (!action->isMine()) { + action->deserialize(serializedAction); + } action->locallyAddedButNotYetReceived = false; updated << actionID; } else { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8213316b7b..9a4539ea9f 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -830,6 +830,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, if (!action) { return false; } + action->setIsMine(true); success = entity->addAction(simulation, action); entity->grabSimulationOwnership(); return false; // Physics will cause a packet to be sent, so don't send from here. diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index ba6f32b51f..4538a1bb43 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -49,7 +49,7 @@ public: const rgbColor& getColor() const { return _color; } xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } - glm::vec3 getColorRGB() const { return ColorUtils::toLinearVec3(toGlm(getXColor())); } + glm::vec3 getColorRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(getXColor())); } static const xColor DEFAULT_COLOR; void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } @@ -62,17 +62,17 @@ public: bool _isColorStartInitialized = false; void setColorStart(const xColor& colorStart) { _colorStart = colorStart; _isColorStartInitialized = true; } xColor getColorStart() const { return _isColorStartInitialized ? _colorStart : getXColor(); } - glm::vec3 getColorStartRGB() const { return _isColorStartInitialized ? ColorUtils::toLinearVec3(toGlm(_colorStart)) : getColorRGB(); } + glm::vec3 getColorStartRGB() const { return _isColorStartInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_colorStart)) : getColorRGB(); } bool _isColorFinishInitialized = false; void setColorFinish(const xColor& colorFinish) { _colorFinish = colorFinish; _isColorFinishInitialized = true; } xColor getColorFinish() const { return _isColorFinishInitialized ? _colorFinish : getXColor(); } - glm::vec3 getColorFinishRGB() const { return _isColorStartInitialized ? ColorUtils::toLinearVec3(toGlm(_colorFinish)) : getColorRGB(); } + glm::vec3 getColorFinishRGB() const { return _isColorStartInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_colorFinish)) : getColorRGB(); } static const xColor DEFAULT_COLOR_SPREAD; void setColorSpread(const xColor& colorSpread) { _colorSpread = colorSpread; } xColor getColorSpread() const { return _colorSpread; } - glm::vec3 getColorSpreadRGB() const { return ColorUtils::toLinearVec3(toGlm(_colorSpread)); } + glm::vec3 getColorSpreadRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(_colorSpread)); } static const float MAXIMUM_ALPHA; static const float MINIMUM_ALPHA; diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index d4d9ba7b81..4330b34b90 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -11,9 +11,14 @@ <@if not GPU_COLOR_SLH@> <@def GPU_COLOR_SLH@> +float sRGBFloatToLinear(float value) { + const float SRGB_ELBOW = 0.04045; + + return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4); +} + vec3 colorToLinearRGB(vec3 srgb) { - const float GAMMA_22 = 2.2; - return pow(srgb, vec3(GAMMA_22)); + return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b)); } vec4 colorToLinearRGBA(vec4 srgba) { diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 7c603919fd..b7a8380f78 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -11,6 +11,7 @@ using namespace gpu; const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA }; +const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 9c08e45bec..6b2bc4b93e 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -245,6 +245,7 @@ public: } static const Element COLOR_RGBA_32; + static const Element COLOR_SRGBA_32; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; static const Element VEC2F_XY; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 9f10f2e2b6..2362926c84 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -18,6 +18,8 @@ #include "GPULogging.h" #include "Context.h" +#include "ColorUtils.h" + using namespace gpu; static int TexturePointerMetaTypeId = qRegisterMetaType(); @@ -637,18 +639,6 @@ void SphericalHarmonics::assignPreset(int p) { } } - - -glm::vec3 sRGBToLinear(glm::vec3& color) { - const float GAMMA_CORRECTION = 2.2f; - return glm::pow(color, glm::vec3(GAMMA_CORRECTION)); -} - -glm::vec3 linearTosRGB(glm::vec3& color) { - const float GAMMA_CORRECTION_INV = 1.0f / 2.2f; - return glm::pow(color, glm::vec3(GAMMA_CORRECTION_INV)); -} - // Originial code for the Spherical Harmonics taken from "Sun and Black Cat- Igor Dykhta (igor dykhta email) � 2007-2014 " void sphericalHarmonicsAdd(float * result, int order, const float * inputA, const float * inputB) { const int numCoeff = order * order; @@ -803,7 +793,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT); // Gamma correct - clr = sRGBToLinear(clr); + clr = ColorUtils::sRGBToLinearVec3(clr); // scale color and add to previously accumulated coefficients sphericalHarmonicsScale(shBuffB.data(), order, diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 6dd1d97d7f..e4fb5c97f8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -237,13 +237,14 @@ ModelCache::ModelCache() { QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - const GeometryExtra* geometryExtra = static_cast(extra); - Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); } else { - resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl); + const GeometryExtra* geometryExtra = static_cast(extra); + auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); + auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); + resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl); } return QSharedPointer(resource, &Resource::deleter); @@ -424,7 +425,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur { _textures = Textures(MapChannel::NUM_MAP_CHANNELS); if (!material.albedoTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); _albedoTransform = material.albedoTexture.transform; map->setTextureTransform(_albedoTransform); @@ -441,39 +442,39 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur if (!material.normalTexture.filename.isEmpty()) { - auto type = (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE); + auto type = (material.normalTexture.isBumpmap ? NetworkTexture::BUMP_TEXTURE : NetworkTexture::NORMAL_TEXTURE); auto map = fetchTextureMap(textureBaseUrl, material.normalTexture, type, MapChannel::NORMAL_MAP); setTextureMap(MapChannel::NORMAL_MAP, map); } if (!material.roughnessTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } else if (!material.glossTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, NetworkTexture::GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } if (!material.metallicTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } else if (!material.specularTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, NetworkTexture::SPECULAR_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } if (!material.occlusionTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); setTextureMap(MapChannel::OCCLUSION_MAP, map); } if (!material.emissiveTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); setTextureMap(MapChannel::EMISSIVE_MAP, map); } if (!material.lightmapTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); _lightmapTransform = material.lightmapTexture.transform; _lightmapParams = material.lightmapParams; map->setTextureTransform(_lightmapTransform); @@ -495,7 +496,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!albedoName.isEmpty()) { auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); + auto map = fetchTextureMap(url, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); map->setTextureTransform(_albedoTransform); // when reassigning the albedo texture we also check for the alpha channel used as opacity map->setUseAlphaChannel(true); @@ -504,39 +505,39 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!normalName.isEmpty()) { auto url = textureMap.contains(normalName) ? textureMap[normalName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, NORMAL_TEXTURE, MapChannel::NORMAL_MAP); + auto map = fetchTextureMap(url, NetworkTexture::NORMAL_TEXTURE, MapChannel::NORMAL_MAP); setTextureMap(MapChannel::NORMAL_MAP, map); } if (!roughnessName.isEmpty()) { auto url = textureMap.contains(roughnessName) ? textureMap[roughnessName].toUrl() : QUrl(); // FIXME: If passing a gloss map instead of a roughmap how do we know? - auto map = fetchTextureMap(url, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(url, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } if (!metallicName.isEmpty()) { auto url = textureMap.contains(metallicName) ? textureMap[metallicName].toUrl() : QUrl(); // FIXME: If passing a specular map instead of a metallic how do we know? - auto map = fetchTextureMap(url, METALLIC_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(url, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } if (!occlusionName.isEmpty()) { auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + auto map = fetchTextureMap(url, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); setTextureMap(MapChannel::OCCLUSION_MAP, map); } if (!emissiveName.isEmpty()) { auto url = textureMap.contains(emissiveName) ? textureMap[emissiveName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); + auto map = fetchTextureMap(url, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); setTextureMap(MapChannel::EMISSIVE_MAP, map); } if (!lightmapName.isEmpty()) { auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); + auto map = fetchTextureMap(url, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); map->setTextureTransform(_lightmapTransform); map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); setTextureMap(MapChannel::LIGHTMAP_MAP, map); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 88e89a57a9..bf47f293e8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -170,6 +170,8 @@ protected: const bool& isOriginal() const { return _isOriginal; } private: + using TextureType = NetworkTexture::Type; + // Helpers for the ctors QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture); model::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 7d18151f2c..2aaddace88 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -35,6 +35,16 @@ TextureCache::TextureCache() { const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; setUnusedResourceCacheSize(TEXTURE_DEFAULT_UNUSED_MAX_SIZE); setObjectName("TextureCache"); + + // Expose enum Type to JS/QML via properties + // Despite being one-off, this should be fine, because TextureCache is a SINGLETON_DEPENDENCY + QObject* type = new QObject(this); + type->setObjectName("TextureType"); + setProperty("Type", QVariant::fromValue(type)); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + type->setProperty(metaEnum.key(i), metaEnum.value(i)); + } } TextureCache::~TextureCache() { @@ -145,60 +155,68 @@ const gpu::TexturePointer& TextureCache::getNormalFittingTexture() { /// Extra data for creating textures. class TextureExtra { public: - TextureType type; + NetworkTexture::Type type; const QByteArray& content; }; -NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type, const QByteArray& content) { +ScriptableResource* TextureCache::prefetch(const QUrl& url, int type) { + auto byteArray = QByteArray(); + TextureExtra extra = { (Type)type, byteArray }; + return ResourceCache::prefetch(url, &extra); +} + +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const QByteArray& content) { TextureExtra extra = { type, content }; return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); } -TextureCache::TextureLoaderFunc getTextureLoaderForType(TextureType type) { +NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type type) { + using Type = NetworkTexture; + switch (type) { - case ALBEDO_TEXTURE: { + case Type::ALBEDO_TEXTURE: { return model::TextureUsage::createAlbedoTextureFromImage; break; } - case EMISSIVE_TEXTURE: { + case Type::EMISSIVE_TEXTURE: { return model::TextureUsage::createEmissiveTextureFromImage; break; } - case LIGHTMAP_TEXTURE: { + case Type::LIGHTMAP_TEXTURE: { return model::TextureUsage::createLightmapTextureFromImage; break; } - case CUBE_TEXTURE: { + case Type::CUBE_TEXTURE: { return model::TextureUsage::createCubeTextureFromImage; break; } - case BUMP_TEXTURE: { + case Type::BUMP_TEXTURE: { return model::TextureUsage::createNormalTextureFromBumpImage; break; } - case NORMAL_TEXTURE: { + case Type::NORMAL_TEXTURE: { return model::TextureUsage::createNormalTextureFromNormalImage; break; } - case ROUGHNESS_TEXTURE: { + case Type::ROUGHNESS_TEXTURE: { return model::TextureUsage::createRoughnessTextureFromImage; break; } - case GLOSS_TEXTURE: { + case Type::GLOSS_TEXTURE: { return model::TextureUsage::createRoughnessTextureFromGlossImage; break; } - case SPECULAR_TEXTURE: { + case Type::SPECULAR_TEXTURE: { return model::TextureUsage::createMetallicTextureFromImage; break; } - case CUSTOM_TEXTURE: { + case Type::CUSTOM_TEXTURE: { Q_ASSERT(false); - return TextureCache::TextureLoaderFunc(); + return NetworkTexture::TextureLoaderFunc(); break; } - case DEFAULT_TEXTURE: + case Type::DEFAULT_TEXTURE: default: { return model::TextureUsage::create2DTextureFromImage; break; @@ -207,7 +225,7 @@ TextureCache::TextureLoaderFunc getTextureLoaderForType(TextureType type) { } /// Returns a texture version of an image file -gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureType type) { +gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type) { QImage image = QImage(path); auto loader = getTextureLoaderForType(type); return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString())); @@ -216,11 +234,13 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureTy QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); - return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), + auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE; + auto content = textureExtra ? textureExtra->content : QByteArray(); + return QSharedPointer(new NetworkTexture(url, type, content), &Resource::deleter); } -NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : +NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content) : Resource(url, !content.isEmpty()), _type(type) { diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index c614a7ceb3..8fd0b12369 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -25,79 +26,6 @@ namespace gpu { class Batch; } -class NetworkTexture; - -typedef QSharedPointer NetworkTexturePointer; - -enum TextureType { - DEFAULT_TEXTURE, - ALBEDO_TEXTURE, - NORMAL_TEXTURE, - BUMP_TEXTURE, - SPECULAR_TEXTURE, - METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey - ROUGHNESS_TEXTURE, - GLOSS_TEXTURE, - EMISSIVE_TEXTURE, - CUBE_TEXTURE, - OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE, - CUSTOM_TEXTURE -}; - -/// Stores cached textures, including render-to-texture targets. -class TextureCache : public ResourceCache, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture - /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and - /// the second, a set of random unit vectors to be used as noise gradients. - const gpu::TexturePointer& getPermutationNormalTexture(); - - /// Returns an opaque white texture (useful for a default). - const gpu::TexturePointer& getWhiteTexture(); - - /// Returns an opaque gray texture (useful for a default). - const gpu::TexturePointer& getGrayTexture(); - - /// Returns the a pale blue texture (useful for a normal map). - const gpu::TexturePointer& getBlueTexture(); - - /// Returns the a black texture (useful for a default). - const gpu::TexturePointer& getBlackTexture(); - - // Returns a map used to compress the normals through a fitting scale algorithm - const gpu::TexturePointer& getNormalFittingTexture(); - - /// Returns a texture version of an image file - static gpu::TexturePointer getImageTexture(const QString& path, TextureType type = DEFAULT_TEXTURE); - - /// Loads a texture from the specified URL. - NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, - const QByteArray& content = QByteArray()); - - typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); - - typedef std::function TextureLoaderFunc; -protected: - - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); - -private: - TextureCache(); - virtual ~TextureCache(); - friend class DilatableNetworkTexture; - - gpu::TexturePointer _permutationNormalTexture; - gpu::TexturePointer _whiteTexture; - gpu::TexturePointer _grayTexture; - gpu::TexturePointer _blueTexture; - gpu::TexturePointer _blackTexture; - gpu::TexturePointer _normalFittingTexture; -}; /// A simple object wrapper for an OpenGL texture. class Texture { @@ -107,15 +35,31 @@ public: }; /// A texture loaded from the network. - class NetworkTexture : public Resource, public Texture { Q_OBJECT public: - - typedef TextureCache::TextureLoaderFunc TextureLoaderFunc; - - NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); + enum Type { + DEFAULT_TEXTURE, + ALBEDO_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + CUSTOM_TEXTURE + }; + Q_ENUM(Type) + + typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); + using TextureLoaderFunc = std::function; + + NetworkTexture(const QUrl& url, Type type, const QByteArray& content); NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content); int getOriginalWidth() const { return _originalWidth; } @@ -138,12 +82,69 @@ protected: Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); private: - TextureType _type; - TextureLoaderFunc _textureLoader; + Type _type; + TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } }; int _originalWidth { 0 }; int _originalHeight { 0 }; int _width { 0 }; int _height { 0 }; }; +using NetworkTexturePointer = QSharedPointer; + +/// Stores cached textures, including render-to-texture targets. +class TextureCache : public ResourceCache, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + + using Type = NetworkTexture::Type; + +public: + /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture + /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and + /// the second, a set of random unit vectors to be used as noise gradients. + const gpu::TexturePointer& getPermutationNormalTexture(); + + /// Returns an opaque white texture (useful for a default). + const gpu::TexturePointer& getWhiteTexture(); + + /// Returns an opaque gray texture (useful for a default). + const gpu::TexturePointer& getGrayTexture(); + + /// Returns the a pale blue texture (useful for a normal map). + const gpu::TexturePointer& getBlueTexture(); + + /// Returns the a black texture (useful for a default). + const gpu::TexturePointer& getBlackTexture(); + + // Returns a map used to compress the normals through a fitting scale algorithm + const gpu::TexturePointer& getNormalFittingTexture(); + + /// Returns a texture version of an image file + static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE); + + /// Loads a texture from the specified URL. + NetworkTexturePointer getTexture(const QUrl& url, Type type = Type::DEFAULT_TEXTURE, + const QByteArray& content = QByteArray()); + +protected: + // Overload ResourceCache::prefetch to allow specifying texture type for loads + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type); + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, const void* extra); + +private: + TextureCache(); + virtual ~TextureCache(); + friend class DilatableNetworkTexture; + + gpu::TexturePointer _permutationNormalTexture; + gpu::TexturePointer _whiteTexture; + gpu::TexturePointer _grayTexture; + gpu::TexturePointer _blueTexture; + gpu::TexturePointer _blackTexture; + gpu::TexturePointer _normalFittingTexture; +}; + #endif // hifi_TextureCache_h diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 80804e3cf5..7cc4691d63 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -109,10 +109,15 @@ SphericalHarmonics getLightAmbientSphere(Light l) { return l._ambientSphere; } +bool getLightHasAmbientMap(Light l) { + return l._control.x > 0; +} + float getLightAmbientMapNumMips(Light l) { return l._control.x; } + <@if GPU_FEATURE_PROFILE == GPU_CORE @> uniform lightBuffer { Light light; diff --git a/libraries/model/src/model/Material.cpp b/libraries/model/src/model/Material.cpp index 1ea407122f..d700a191c4 100755 --- a/libraries/model/src/model/Material.cpp +++ b/libraries/model/src/model/Material.cpp @@ -65,7 +65,7 @@ Material::~Material() { void Material::setEmissive(const Color& emissive, bool isSRGB) { _key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f)))); _schemaBuffer.edit()._key = (uint32) _key._flags.to_ulong(); - _schemaBuffer.edit()._emissive = (isSRGB ? ColorUtils::toLinearVec3(emissive) : emissive); + _schemaBuffer.edit()._emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive); } void Material::setOpacity(float opacity) { @@ -77,7 +77,7 @@ void Material::setOpacity(float opacity) { void Material::setAlbedo(const Color& albedo, bool isSRGB) { _key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._albedo = (isSRGB ? ColorUtils::toLinearVec3(albedo) : albedo); + _schemaBuffer.edit()._albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } void Material::setRoughness(float roughness) { @@ -89,7 +89,7 @@ void Material::setRoughness(float roughness) { void Material::setFresnel(const Color& fresnel, bool isSRGB) { //_key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); - _schemaBuffer.edit()._fresnel = (isSRGB ? ColorUtils::toLinearVec3(fresnel) : fresnel); + _schemaBuffer.edit()._fresnel = (isSRGB ? ColorUtils::sRGBToLinearVec3(fresnel) : fresnel); } void Material::setMetallic(float metallic) { diff --git a/libraries/model/src/model/Material.h b/libraries/model/src/model/Material.h index 4cfb2120ee..cf6b48f257 100755 --- a/libraries/model/src/model/Material.h +++ b/libraries/model/src/model/Material.h @@ -245,16 +245,16 @@ public: const MaterialKey& getKey() const { return _key; } void setEmissive(const Color& emissive, bool isSRGB = true); - Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._emissive) : _schemaBuffer.get()._emissive); } + Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._emissive) : _schemaBuffer.get()._emissive); } void setOpacity(float opacity); float getOpacity() const { return _schemaBuffer.get()._opacity; } void setAlbedo(const Color& albedo, bool isSRGB = true); - Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._albedo) : _schemaBuffer.get()._albedo); } + Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._albedo) : _schemaBuffer.get()._albedo); } void setFresnel(const Color& fresnel, bool isSRGB = true); - Color getFresnel(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get()._fresnel) : _schemaBuffer.get()._fresnel); } + Color getFresnel(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._fresnel) : _schemaBuffer.get()._fresnel); } void setMetallic(float metallic); float getMetallic() const { return _schemaBuffer.get()._metallic; } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index c0382a5748..4cc8b1d4f0 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -119,6 +119,92 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { return highestResource; } +ScriptableResource::ScriptableResource(const QUrl& url) : + QObject(nullptr), + _url(url) { } + +void ScriptableResource::release() { + disconnectHelper(); + _resource.reset(); +} + +bool ScriptableResource::isInScript() const { + return _resource && _resource->isInScript(); +} + +void ScriptableResource::setInScript(bool isInScript) { + if (_resource) { + _resource->setInScript(isInScript); + } +} + +void ScriptableResource::loadingChanged() { + emit stateChanged(LOADING); +} + +void ScriptableResource::loadedChanged() { + emit stateChanged(LOADED); +} + +void ScriptableResource::finished(bool success) { + disconnectHelper(); + + emit stateChanged(success ? FINISHED : FAILED); +} + +void ScriptableResource::disconnectHelper() { + if (_progressConnection) { + disconnect(_progressConnection); + } + if (_loadingConnection) { + disconnect(_loadingConnection); + } + if (_loadedConnection) { + disconnect(_loadedConnection); + } + if (_finishedConnection) { + disconnect(_finishedConnection); + } +} + +ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { + ScriptableResource* result = nullptr; + + if (QThread::currentThread() != thread()) { + // Must be called in thread to ensure getResource returns a valid pointer + QMetaObject::invokeMethod(this, "prefetch", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(ScriptableResource*, result), + Q_ARG(QUrl, url), Q_ARG(void*, extra)); + return result; + } + + result = new ScriptableResource(url); + + auto resource = getResource(url, QUrl(), false, extra); + result->_resource = resource; + result->setObjectName(url.toString()); + + result->_resource = resource; + if (resource->isLoaded()) { + result->finished(!resource->_failedToLoad); + } else { + result->_progressConnection = connect( + resource.data(), &Resource::onProgress, + result, &ScriptableResource::progressChanged); + result->_loadingConnection = connect( + resource.data(), &Resource::loading, + result, &ScriptableResource::loadingChanged); + result->_loadedConnection = connect( + resource.data(), &Resource::loaded, + result, &ScriptableResource::loadedChanged); + result->_finishedConnection = connect( + resource.data(), &Resource::finished, + result, &ScriptableResource::finished); + } + + return result; +} + ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { auto nodeList = DependencyManager::get(); if (nodeList) { @@ -219,7 +305,7 @@ QVariantList ResourceCache::getResourceList() { return list; } - + void ResourceCache::setRequestLimit(int limit) { _requestLimit = limit; @@ -272,6 +358,7 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); resource->setSelf(resource); resource->setCache(this); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); _resources.insert(url, resource); @@ -357,8 +444,13 @@ void ResourceCache::removeResource(const QUrl& url, qint64 size) { _totalResourcesSize -= size; } -void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize) { - _totalResourcesSize += (newSize - oldSize); +void ResourceCache::updateTotalSize(const qint64& deltaSize) { + _totalResourcesSize += deltaSize; + + // Sanity checks + assert(_totalResourcesSize >= 0); + assert(_totalResourcesSize < (1024 * BYTES_PER_GIGABYTES)); + emit dirty(); } @@ -543,7 +635,7 @@ void Resource::finishedLoading(bool success) { } void Resource::setSize(const qint64& bytes) { - QMetaObject::invokeMethod(_cache.data(), "updateTotalSize", Q_ARG(qint64, _bytes), Q_ARG(qint64, bytes)); + emit updateSize(bytes - _bytes); _bytes = bytes; } @@ -569,8 +661,11 @@ void Resource::makeRequest() { } qCDebug(networking).noquote() << "Starting request for:" << _url.toDisplayString(); + emit loading(); + + connect(_request, &ResourceRequest::progress, this, &Resource::onProgress); + connect(this, &Resource::onProgress, this, &Resource::handleDownloadProgress); - connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); _bytesReceived = _bytesTotal = _bytes = 0; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index ed3dbf69b6..b81c69c079 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -24,9 +24,12 @@ #include #include #include + #include #include +#include + #include #include "ResourceManager.h" @@ -50,7 +53,7 @@ static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES; static const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES; #endif static const qint64 MIN_UNUSED_MAX_SIZE = 0; -static const qint64 MAX_UNUSED_MAX_SIZE = 10 * BYTES_PER_GIGABYTES; +static const qint64 MAX_UNUSED_MAX_SIZE = MAXIMUM_CACHE_SIZE; // We need to make sure that these items are available for all instances of // ResourceCache derived classes. Since we can't count on the ordering of @@ -78,6 +81,61 @@ private: QList> _loadingRequests; }; +/// Wrapper to expose resources to JS/QML +class ScriptableResource : public QObject { + Q_OBJECT + Q_PROPERTY(QUrl url READ getUrl) + Q_PROPERTY(int state READ getState NOTIFY stateChanged) + +public: + enum State { + QUEUED, + LOADING, + LOADED, + FINISHED, + FAILED, + }; + Q_ENUM(State) + + ScriptableResource(const QUrl& url); + virtual ~ScriptableResource() = default; + + Q_INVOKABLE void release(); + + const QUrl& getUrl() const { return _url; } + int getState() const { return (int)_state; } + const QSharedPointer& getResource() const { return _resource; } + + bool isInScript() const; + void setInScript(bool isInScript); + +signals: + void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); + void stateChanged(int state); + +private slots: + void loadingChanged(); + void loadedChanged(); + void finished(bool success); + +private: + void disconnectHelper(); + + friend class ResourceCache; + + // Holds a ref to the resource to keep it in scope + QSharedPointer _resource; + + QMetaObject::Connection _progressConnection; + QMetaObject::Connection _loadingConnection; + QMetaObject::Connection _loadedConnection; + QMetaObject::Connection _finishedConnection; + + QUrl _url; + State _state{ QUEUED }; +}; + +Q_DECLARE_METATYPE(ScriptableResource*); /// Base class for resource caches. class ResourceCache : public QObject { @@ -121,12 +179,23 @@ public slots: void checkAsynchronousGets(); protected slots: - void updateTotalSize(const qint64& oldSize, const qint64& newSize); + void updateTotalSize(const qint64& deltaSize); + + // Prefetches a resource to be held by the QScriptEngine. + // Left as a protected member so subclasses can overload prefetch + // and delegate to it (see TextureCache::prefetch(const QUrl&, int). + ScriptableResource* prefetch(const QUrl& url, void* extra); private slots: void clearATPAssets(); protected: + // Prefetches a resource to be held by the QScriptEngine. + // Pointers created through this method should be owned by the caller, + // which should be a QScriptEngine with ScriptableResource registered, so that + // the QScriptEngine will delete the pointer when it is garbage collected. + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } + /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested @@ -231,6 +300,9 @@ public: const QUrl& getURL() const { return _url; } signals: + /// Fired when the resource begins downloading. + void loading(); + /// Fired when the resource has been downloaded. /// This can be used instead of downloadFinished to access data before it is processed. void loaded(const QByteArray request); @@ -244,6 +316,12 @@ signals: /// Fired when the resource is refreshed. void onRefresh(); + /// Fired on progress updates. + void onProgress(uint64_t bytesReceived, uint64_t bytesTotal); + + /// Fired when the size changes (through setSize). + void updateSize(qint64 deltaSize); + protected slots: void attemptRequest(); @@ -280,21 +358,26 @@ private slots: void handleReplyFinished(); private: + friend class ResourceCache; + friend class ScriptableResource; + void setLRUKey(int lruKey) { _lruKey = lruKey; } void makeRequest(); void retry(); void reinsert(); + + bool isInScript() const { return _isInScript; } + void setInScript(bool isInScript) { _isInScript = isInScript; } - friend class ResourceCache; - - ResourceRequest* _request = nullptr; - int _lruKey = 0; - QTimer* _replyTimer = nullptr; - qint64 _bytesReceived = 0; - qint64 _bytesTotal = 0; - qint64 _bytes = 0; - int _attempts = 0; + ResourceRequest* _request{ nullptr }; + int _lruKey{ 0 }; + QTimer* _replyTimer{ nullptr }; + qint64 _bytesReceived{ 0 }; + qint64 _bytesTotal{ 0 }; + qint64 _bytes{ 0 }; + int _attempts{ 0 }; + bool _isInScript{ false }; }; uint qHash(const QPointer& value, uint seed = 0); diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index c87aa1cee2..7608c8ae08 100755 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -25,6 +25,43 @@ vec4 evalSkyboxLight(vec3 direction, float lod) { } <@endfunc@> +<@func declareEvalGlobalSpecularIrradiance(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere)@> + +vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) { + return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); +} + +<@if supportAmbientMap@> + <$declareSkyboxMap()$> +<@endif@> + +vec3 evalGlobalSpecularIrradiance(Light light, vec3 fragEyeDir, vec3 fragNormal, float roughness, vec3 fresnel, float obscurance) { + vec3 direction = -reflect(fragEyeDir, fragNormal); + vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); + vec3 specularLight; + <@if supportIfAmbientMapElseAmbientSphere@> + if (getLightHasAmbientMap(light)) + <@endif@> + <@if supportAmbientMap@> + { + float levels = getLightAmbientMapNumMips(light); + float lod = min(floor((roughness) * levels), levels); + specularLight = evalSkyboxLight(direction, lod).xyz; + } + <@endif@> + <@if supportIfAmbientMapElseAmbientSphere@> + else + <@endif@> + <@if supportAmbientSphere@> + { + specularLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz; + } + <@endif@> + + return specularLight * ambientFresnel * getLightAmbientIntensity(light); +} +<@endfunc@> + <@func prepareGlobalLight()@> // prepareGlobalLight @@ -45,11 +82,6 @@ vec4 evalSkyboxLight(vec3 direction, float lod) { color += emissive; <@endfunc@> -<@func declareAmbientFresnel()@> -vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) { - return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5); -} -<@endfunc@> <@func declareEvalAmbientGlobalColor()@> vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { @@ -60,7 +92,8 @@ vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obsc <@endfunc@> <@func declareEvalAmbientSphereGlobalColor()@> -<$declareAmbientFresnel()$> +<$declareEvalGlobalSpecularIrradiance(1, 0, 0)$> + vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { <$prepareGlobalLight()$> @@ -69,18 +102,15 @@ vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, floa color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); // Specular highlight from ambient - vec3 direction = -reflect(fragEyeDir, fragNormal); - vec3 skyboxLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz; - vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); - color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light); + vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); + color += specularLighting; return color; } <@endfunc@> <@func declareEvalSkyboxGlobalColor()@> -<$declareSkyboxMap()$> -<$declareAmbientFresnel()$> +<$declareEvalGlobalSpecularIrradiance(0, 1, 0)$> vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) { <$prepareGlobalLight()$> @@ -89,13 +119,8 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); // Specular highlight from ambient - vec3 direction = -reflect(fragEyeDir, fragNormal); - - float levels = getLightAmbientMapNumMips(light); - float lod = min(floor((roughness) * levels), levels); - vec4 skyboxLight = evalSkyboxLight(direction, lod); - vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness); - color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light); + vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); + color += specularLighting; return color; } @@ -125,4 +150,26 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur } <@endfunc@> + + +<@func declareEvalGlobalLightingAlphaBlended()@> + +<$declareEvalGlobalSpecularIrradiance(1, 1, 1)$> + +vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness, float opacity) { + <$prepareGlobalLight()$> + + // Diffuse from ambient + color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light); + + // Specular highlight from ambient + vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance); + color += specularLighting / opacity; + + return color; +} + +<@endfunc@> + + <@endif@> diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 82a92f572e..550baa6946 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -506,9 +506,9 @@ GeometryCache::GeometryCache() : std::make_shared(getSimplePipeline(), nullptr, [](const render::ShapePipeline&, gpu::Batch& batch) { // Set the defaults needed for a simple program - batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); - batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING, DependencyManager::get()->getNormalFittingTexture()); } ); @@ -1736,11 +1736,11 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool cul // If not textured, set a default albedo map if (!textured) { - batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); } // Set a default normal map - batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING, DependencyManager::get()->getNormalFittingTexture()); } @@ -1758,7 +1758,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool culled _emissiveShader = gpu::Shader::createProgram(VS, PSEmissive); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::NORMAL_FITTING_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING)); gpu::Shader::makeProgram(*_simpleShader, slotBindings); gpu::Shader::makeProgram(*_emissiveShader, slotBindings); }); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 6571674e44..03c1ac9eda 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -138,8 +138,8 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat auto textureCache = DependencyManager::get(); - batch.setUniformBuffer(ShapePipeline::Slot::MATERIAL_BUFFER, _drawMaterial->getSchemaBuffer()); - batch.setUniformBuffer(ShapePipeline::Slot::TEXMAPARRAY_BUFFER, _drawMaterial->getTexMapArrayBuffer()); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, _drawMaterial->getSchemaBuffer()); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, _drawMaterial->getTexMapArrayBuffer()); auto materialKey = _drawMaterial->getKey(); auto textureMaps = _drawMaterial->getTextureMaps(); @@ -148,68 +148,68 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat if (materialKey.isAlbedoMap()) { auto albedoMap = textureMaps[model::MaterialKey::ALBEDO_MAP]; if (albedoMap && albedoMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, albedoMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, albedoMap->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getGrayTexture()); + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture()); } // Roughness map if (materialKey.isRoughnessMap()) { auto roughnessMap = textureMaps[model::MaterialKey::ROUGHNESS_MAP]; if (roughnessMap && roughnessMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, roughnessMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, roughnessMap->getTextureView()); // texcoord are assumed to be the same has albedo } else { - batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture()); } // Normal map if (materialKey.isNormalMap()) { auto normalMap = textureMaps[model::MaterialKey::NORMAL_MAP]; if (normalMap && normalMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, normalMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, normalMap->getTextureView()); // texcoord are assumed to be the same has albedo } else { - batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, textureCache->getBlueTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, nullptr); } // Metallic map if (materialKey.isMetallicMap()) { auto specularMap = textureMaps[model::MaterialKey::METALLIC_MAP]; if (specularMap && specularMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, specularMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, specularMap->getTextureView()); // texcoord are assumed to be the same has albedo } else { - batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, textureCache->getBlackTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, nullptr); } // Occlusion map if (materialKey.isOcclusionMap()) { auto specularMap = textureMaps[model::MaterialKey::OCCLUSION_MAP]; if (specularMap && specularMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, specularMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, specularMap->getTextureView()); // texcoord are assumed to be the same has albedo } else { - batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, textureCache->getWhiteTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, nullptr); } // Emissive / Lightmap @@ -217,20 +217,20 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat auto lightmapMap = textureMaps[model::MaterialKey::LIGHTMAP_MAP]; if (lightmapMap && lightmapMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, lightmapMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, lightmapMap->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getGrayTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture()); } } else if (materialKey.isEmissiveMap()) { auto emissiveMap = textureMaps[model::MaterialKey::EMISSIVE_MAP]; if (emissiveMap && emissiveMap->isDefined()) { - batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, emissiveMap->getTextureView()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, emissiveMap->getTextureView()); } else { - batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getBlackTexture()); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture()); } } else { - batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, nullptr); + batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, nullptr); } } @@ -474,9 +474,9 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline: Transform transform; if (state.clusterBuffer) { if (canCauterize && _model->getCauterizeBones()) { - batch.setUniformBuffer(ShapePipeline::Slot::SKINNING_BUFFER, state.cauterizedClusterBuffer); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.cauterizedClusterBuffer); } else { - batch.setUniformBuffer(ShapePipeline::Slot::SKINNING_BUFFER, state.clusterBuffer); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer); } } else { if (canCauterize && _model->getCauterizeBones()) { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index bca8f3791f..ada5a66c39 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -75,16 +75,16 @@ gpu::BufferView getDefaultMaterialBuffer() { void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) { // Set a default albedo map - batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO, DependencyManager::get()->getWhiteTexture()); // Set a default normal map - batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP, + batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING, DependencyManager::get()->getNormalFittingTexture()); // Set a default material if (pipeline.locations->materialBufferUnit >= 0) { static const gpu::BufferView OPAQUE_SCHEMA_BUFFER = getDefaultMaterialBuffer(); - batch.setUniformBuffer(ShapePipeline::Slot::MATERIAL_BUFFER, OPAQUE_SCHEMA_BUFFER); + batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, OPAQUE_SCHEMA_BUFFER); } } @@ -94,7 +94,7 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) { if (pipeline.locations->lightBufferUnit >= 0) { DependencyManager::get()->setupKeyLightBatch(batch, pipeline.locations->lightBufferUnit, - -1); + pipeline.locations->lightAmbientMapUnit); } } diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 12a7b9299f..1a807c5703 100755 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -15,7 +15,8 @@ <@include model/Material.slh@> <@include DeferredGlobalLight.slh@> -<$declareEvalAmbientSphereGlobalColor()$> + +<$declareEvalGlobalLightingAlphaBlended()$> <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> @@ -58,7 +59,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); - _fragColor = vec4(evalAmbientSphereGlobalColor( + _fragColor = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, 1.0, @@ -67,6 +68,6 @@ void main(void) { albedo, metallic, emissive, - roughness), + roughness, opacity), opacity); } diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index e7604544bd..3607aa5803 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -363,7 +363,7 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT)); // need the gamma corrected color here - glm::vec4 lrgba = glm::vec4(ColorUtils::toLinearVec3(glm::vec3(*color)), color->a); + glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color); batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba); batch.setInputFormat(_format); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index b09d4b1356..a16847fddf 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -51,17 +51,18 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter) { gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::SKINNING_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::MATERIAL_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::TEXMAPARRAY_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::ALBEDO_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::ROUGHNESS_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::NORMAL_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::METALLIC_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::EMISSIVE_LIGHTMAP_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::OCCLUSION_MAP)); - slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::LIGHT_BUFFER)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING_MAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING)); + slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL)); + slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY)); + slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO)); + slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL)); + slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC)); + slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP)); + slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT)); + slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT)); + slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING)); gpu::Shader::makeProgram(*program, slotBindings); @@ -77,6 +78,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->materialBufferUnit = program->getBuffers().findLocation("materialBuffer"); locations->texMapArrayBufferUnit = program->getBuffers().findLocation("texMapArrayBuffer"); locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer"); + locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); ShapeKey key{filter._flags}; auto gpuPipeline = gpu::Pipeline::create(program, state); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 22aa22d4c9..af1b05f5c0 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -193,18 +193,24 @@ class ShapePipeline { public: class Slot { public: - static const int SKINNING_BUFFER = 2; - static const int MATERIAL_BUFFER = 3; - static const int TEXMAPARRAY_BUFFER = 4; - static const int ALBEDO_MAP = 0; - static const int NORMAL_MAP = 1; - static const int METALLIC_MAP = 2; - static const int EMISSIVE_LIGHTMAP_MAP = 3; - static const int ROUGHNESS_MAP = 4; - static const int OCCLUSION_MAP = 5; + enum BUFFER { + SKINNING = 2, + MATERIAL, + TEXMAPARRAY, + LIGHT + }; - static const int LIGHT_BUFFER = 5; - static const int NORMAL_FITTING_MAP = 10; + enum MAP { + ALBEDO = 0, + NORMAL, + METALLIC, + EMISSIVE_LIGHTMAP, + ROUGHNESS, + OCCLUSION, + LIGHT_AMBIENT, + + NORMAL_FITTING = 10, + }; }; class Locations { @@ -220,6 +226,7 @@ public: int materialBufferUnit; int texMapArrayBufferUnit; int lightBufferUnit; + int lightAmbientMapUnit; }; using LocationsPointer = std::shared_ptr; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 15f3ebb985..6d4747df2d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -140,6 +140,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { hadUncaughtExceptions(*this, _fileNameString); }); + + setProcessEventsInterval(MSECS_PER_SECOND); } ScriptEngine::~ScriptEngine() { @@ -170,7 +172,7 @@ void ScriptEngine::runInThread() { } _isThreaded = true; - QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete + QThread* workerThread = new QThread(this); // thread is not owned, so we need to manage the delete QString scriptEngineName = QString("Script Thread:") + getFilename(); workerThread->setObjectName(scriptEngineName); @@ -184,9 +186,6 @@ void ScriptEngine::runInThread() { // tell the thread to stop when the script engine is done connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit); - // when the thread is finished, add thread to the deleteLater queue - connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater); - moveToThread(workerThread); // Starts an event loop, and emits workerThread->started() @@ -199,11 +198,30 @@ void ScriptEngine::waitTillDoneRunning() { // NOTE: waitTillDoneRunning() will be called on the main Application thread, inside of stopAllScripts() // we want the application thread to continue to process events, because the scripts will likely need to - // marshall messages across to the main thread. For example if they access Settings or Meny in any of their + // marshall messages across to the main thread. For example if they access Settings or Menu in any of their // shutdown code. + QString scriptName = getFilename(); + + auto startedWaiting = usecTimestampNow(); while (thread()->isRunning()) { // process events for the main application thread, allowing invokeMethod calls to pass between threads QCoreApplication::processEvents(); + auto stillWaiting = usecTimestampNow(); + auto elapsedUsecs = stillWaiting - startedWaiting; + + // if we've been waiting a second or more, then tell the script engine to stop evaluating + static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND; + static const auto WAITING_TOO_LONG = MAX_SCRIPT_EVALUATION_TIME * 5; + + // if we've been waiting for more than 5 seconds then we should be more aggessive about stopping + if (elapsedUsecs > WAITING_TOO_LONG) { + qCDebug(scriptengine) << "Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] quitting."; + thread()->quit(); + break; + } else if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) { + qCDebug(scriptengine) << "Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] aborting evaluation."; + QMetaObject::invokeMethod(this, "abortEvaluation"); + } } } } @@ -270,6 +288,48 @@ static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantR assert(false); } +// Templated qScriptRegisterMetaType fails to compile with raw pointers +using ScriptableResourceRawPtr = ScriptableResource*; + +static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { + // The first script to encounter this resource will track its memory. + // In this way, it will be more likely to GC. + // This fails in the case that the resource is used across many scripts, but + // in that case it would be too difficult to tell which one should track the memory, and + // this serves the common case (use in a single script). + auto data = resource->getResource(); + if (data && !resource->isInScript()) { + resource->setInScript(true); + QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); + } + + auto object = engine->newQObject( + const_cast(resource), + QScriptEngine::ScriptOwnership); + return object; +} + +static void scriptableResourceFromScriptValue(const QScriptValue& value, ScriptableResourceRawPtr& resource) { + resource = static_cast(value.toQObject()); +} + +static QScriptValue createScriptableResourcePrototype(QScriptEngine* engine) { + auto prototype = engine->newObject(); + + // Expose enum State to JS/QML via properties + QObject* state = new QObject(engine); + state->setObjectName("ResourceState"); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + state->setProperty(metaEnum.key(i), metaEnum.value(i)); + } + + auto prototypeState = engine->newQObject(state, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSlots | QScriptEngine::ExcludeSuperClassMethods); + prototype.setProperty("State", prototypeState); + + return prototype; +} + void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -327,11 +387,16 @@ void ScriptEngine::init() { registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Mat4", &_mat4Library); registerGlobalObject("Uuid", &_uuidLibrary); - registerGlobalObject("AnimationCache", DependencyManager::get().data()); registerGlobalObject("Messages", DependencyManager::get().data()); qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); + // Scriptable cache access + auto resourcePrototype = createScriptableResourcePrototype(this); + globalObject().setProperty("Resource", resourcePrototype); + setDefaultPrototype(qMetaTypeId(), resourcePrototype); + qScriptRegisterMetaType(this, scriptableResourceToScriptValue, scriptableResourceFromScriptValue); + // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); @@ -367,7 +432,6 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { if (partsToGo > 0) { //QObject *object = new QObject; QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); - qDebug() << "partValue[" << pathPart<<"].isArray() :" << partValue.isArray(); partObject.setProperty(pathPart, partValue); } else { partObject.setProperty(pathPart, value); @@ -793,6 +857,12 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM } } +void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { + if (deltaSize > 0) { + reportAdditionalMemoryCost(deltaSize); + } +} + void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); CallbackData timerData = _timerFunctionMap.value(callingTimer); @@ -912,7 +982,18 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac // Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation()) const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment; for (QString file : includeFiles) { - QUrl thisURL { resolvePath(file) }; + QUrl thisURL; + if (file.startsWith("/~/")) { + thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file))); + QUrl defaultScriptsLoc = defaultScriptsLocation(); + if (!defaultScriptsLoc.isParentOf(thisURL)) { + qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries"; + continue; + } + } else { + thisURL = resolvePath(file); + } + if (!_includedURLs.contains(thisURL)) { if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") && ( @@ -1060,11 +1141,6 @@ void ScriptEngine::loadEntityScript(QWeakPointer theEngine, const << QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]"; #endif strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success); - } else { - // FIXME - I'm leaving this in for testing, so that QA can confirm that sometimes the script contents - // returns after the ScriptEngine has been deleted, we can remove this after QA verifies the - // repro case. - qDebug() << "ScriptCache::getScriptContents() returned after our ScriptEngine was deleted... script:" << scriptOrURL; } }, forceRedownload); } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index e8ce00c66c..175a3f1f1c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -131,6 +131,8 @@ public: Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(); @@ -156,6 +158,7 @@ public: public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + void updateMemoryCost(const qint64&); signals: void scriptLoaded(const QString& scriptFilename); diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index eeca49ff84..c6070e0598 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -70,6 +70,12 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) { } } +QString expandScriptPath(const QString& rawPath) { + QStringList splitPath = rawPath.split("/"); + QUrl defaultScriptsLoc = defaultScriptsLocation(); + return defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/"); // 2 to skip the slashes in /~/ +} + QUrl expandScriptUrl(const QUrl& rawScriptURL) { QUrl normalizedScriptURL = normalizeScriptURL(rawScriptURL); if (normalizedScriptURL.scheme() == "http" || @@ -79,9 +85,23 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) { } else if (normalizedScriptURL.scheme() == "file") { if (normalizedScriptURL.path().startsWith("/~/")) { QUrl url = normalizedScriptURL; - QStringList splitPath = url.path().split("/"); + url.setPath(expandScriptPath(url.path())); + + // stop something like Script.include(["/~/../Desktop/naughty.js"]); from working + QFileInfo fileInfo(url.toLocalFile()); + url = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); + QUrl defaultScriptsLoc = defaultScriptsLocation(); - url.setPath(defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/")); // 2 to skip the slashes in /~/ + if (!defaultScriptsLoc.isParentOf(url)) { + qCWarning(scriptengine) << "Script.include() ignoring file path" << rawScriptURL + << "-- outside of standard libraries: " + << url.path() + << defaultScriptsLoc.path(); + return rawScriptURL; + } + if (rawScriptURL.path().endsWith("/") && !url.path().endsWith("/")) { + url.setPath(url.path() + "/"); + } return url; } return normalizedScriptURL; @@ -129,20 +149,7 @@ void ScriptEngines::shutdownScripting() { // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using // them. We don't need to stop scripts that aren't running. if (scriptEngine->isRunning()) { - - // If the script is running, but still evaluating then we need to wait for its evaluation step to - // complete. After that we can handle the stop process appropriately - if (scriptEngine->evaluatePending()) { - while (scriptEngine->evaluatePending()) { - - // This event loop allows any started, but not yet finished evaluate() calls to complete - // we need to let these complete so that we can be guaranteed that the script engine isn't - // in a partially setup state, which can confuse our shutdown unwinding. - QEventLoop loop; - QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit); - loop.exec(); - } - } + qCDebug(scriptengine) << "about to shutdown script:" << scriptName; // We disconnect any script engine signals from the application because we don't want to do any // extra stopScript/loadScript processing that the Application normally does when scripts start diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 5de71663e9..0963b21600 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -101,6 +101,7 @@ protected: }; QUrl normalizeScriptURL(const QUrl& rawScriptURL); +QString expandScriptPath(const QString& rawPath); QUrl expandScriptUrl(const QUrl& rawScriptURL); #endif // hifi_ScriptEngine_h diff --git a/libraries/shared/src/ColorUtils.h b/libraries/shared/src/ColorUtils.h index b47e7c3a98..5ee9254bc9 100644 --- a/libraries/shared/src/ColorUtils.h +++ b/libraries/shared/src/ColorUtils.h @@ -21,9 +21,19 @@ class ColorUtils { public: inline static glm::vec3 toVec3(const xColor& color); - // Convert from gamma 2.2 space to linear - inline static glm::vec3 toLinearVec3(const glm::vec3& srgb); + // Convert to gamma 2.2 space from linear inline static glm::vec3 toGamma22Vec3(const glm::vec3& linear); + + // Convert from sRGB gamma space to linear. + // This is pretty different from converting from 2.2. + inline static glm::vec3 sRGBToLinearVec3(const glm::vec3& srgb); + inline static glm::vec3 tosRGBVec3(const glm::vec3& srgb); + + inline static glm::vec4 sRGBToLinearVec4(const glm::vec4& srgb); + inline static glm::vec4 tosRGBVec4(const glm::vec4& srgb); + + inline static float sRGBToLinearFloat(const float& srgb); + inline static float tosRGBFloat(const float& linear); }; inline glm::vec3 ColorUtils::toVec3(const xColor& color) { @@ -31,16 +41,66 @@ inline glm::vec3 ColorUtils::toVec3(const xColor& color) { return glm::vec3(color.red * ONE_OVER_255, color.green * ONE_OVER_255, color.blue * ONE_OVER_255); } -inline glm::vec3 ColorUtils::toLinearVec3(const glm::vec3& srgb) { - const float GAMMA_22 = 2.2f; - // Couldn't find glm::pow(vec3, vec3) ? so did it myself... - return glm::vec3(glm::pow(srgb.x, GAMMA_22), glm::pow(srgb.y, GAMMA_22), glm::pow(srgb.z, GAMMA_22)); -} - inline glm::vec3 ColorUtils::toGamma22Vec3(const glm::vec3& linear) { const float INV_GAMMA_22 = 1.0f / 2.2f; // Couldn't find glm::pow(vec3, vec3) ? so did it myself... return glm::vec3(glm::pow(linear.x, INV_GAMMA_22), glm::pow(linear.y, INV_GAMMA_22), glm::pow(linear.z, INV_GAMMA_22)); } +// Convert from sRGB color space to linear color space. +inline glm::vec3 ColorUtils::sRGBToLinearVec3(const glm::vec3& srgb) { + return glm::vec3(sRGBToLinearFloat(srgb.x), sRGBToLinearFloat(srgb.y), sRGBToLinearFloat(srgb.z)); +} + +// Convert from linear color space to sRGB color space. +inline glm::vec3 ColorUtils::tosRGBVec3(const glm::vec3& linear) { + return glm::vec3(tosRGBFloat(linear.x), tosRGBFloat(linear.y), tosRGBFloat(linear.z)); +} + +// Convert from sRGB color space with alpha to linear color space with alpha. +inline glm::vec4 ColorUtils::sRGBToLinearVec4(const glm::vec4& srgb) { + return glm::vec4(sRGBToLinearFloat(srgb.x), sRGBToLinearFloat(srgb.y), sRGBToLinearFloat(srgb.z), srgb.w); +} + +// Convert from linear color space with alpha to sRGB color space with alpha. +inline glm::vec4 ColorUtils::tosRGBVec4(const glm::vec4& linear) { + return glm::vec4(tosRGBFloat(linear.x), tosRGBFloat(linear.y), tosRGBFloat(linear.z), linear.w); +} + +// This is based upon the conversions found in section 8.24 of the OpenGL 4.4 4.4 specification. +// glm::pow(color, 2.2f) is approximate, and will cause subtle differences when used with sRGB framebuffers. +inline float ColorUtils::sRGBToLinearFloat(const float &srgb) { + const float SRGB_ELBOW = 0.04045f; + float linearValue = 0.0f; + + // This should mirror the conversion table found in section 8.24: sRGB Texture Color Conversion + if (srgb <= SRGB_ELBOW) { + linearValue = srgb / 12.92f; + } else { + linearValue = powf(((srgb + 0.055f) / 1.055f), 2.4f); + } + + return linearValue; +} + +// This is based upon the conversions found in section 17.3.9 of the OpenGL 4.4 specification. +// glm::pow(color, 1.0f/2.2f) is approximate, and will cause subtle differences when used with sRGB framebuffers. +inline float ColorUtils::tosRGBFloat(const float &linear) { + const float SRGB_ELBOW_INV = 0.0031308f; + float sRGBValue = 0.0f; + + // This should mirror the conversion table found in section 17.3.9: sRGB Conversion + if (linear <= 0.0f) { + sRGBValue = 0.0f; + } else if (0 < linear && linear < SRGB_ELBOW_INV) { + sRGBValue = 12.92f * linear; + } else if (SRGB_ELBOW_INV <= linear && linear < 1) { + sRGBValue = 1.055f * powf(linear, 0.41666f - 0.055f); + } else { + sRGBValue = 1.0f; + } + + return sRGBValue; +} + #endif // hifi_ColorUtils_h \ No newline at end of file diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 025768908c..954ed2d75a 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -56,12 +56,15 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector= frames.length) { + Script.clearInterval(movieInterval); + callback(); + } + }, 1000 / FRAME_RATE); +} + diff --git a/scripts/developer/tests/scriptableResource/movieTest.js b/scripts/developer/tests/scriptableResource/movieTest.js new file mode 100644 index 0000000000..61b2bf7942 --- /dev/null +++ b/scripts/developer/tests/scriptableResource/movieTest.js @@ -0,0 +1,42 @@ +// +// testMovie.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures, plays them on a frame model, and unloads them. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var entity; + +Script.include([ + '../../../developer/utilities/cache/cacheStats.js', + 'lib.js', +], function() { + getFrame(function(frame) { + entity = frame; + prefetch(function(frames) { + play(frame, frames, function() { + // Delete each texture, so the next garbage collection cycle will release them. + + // Setting frames = null breaks the reference, + // but will not delete frames from the calling scope. + // Instead, we must mutate it in-place to free its elements for GC + // (assuming the elements are not held elsewhere). + while (frames.length) { frames.pop(); } + + // Alternatively, forcibly release each texture without relying on GC. + // frames.forEach(function(texture) { texture.release(); }); + + Entities.deleteEntity(entity); + Script.requestGarbageCollection(); + }); + }); + }); +}); + +Script.scriptEnding.connect(function() { entity && Entities.deleteEntity(entity); }); diff --git a/scripts/developer/tests/scriptableResource/prefetchTest.js b/scripts/developer/tests/scriptableResource/prefetchTest.js new file mode 100644 index 0000000000..cda805967e --- /dev/null +++ b/scripts/developer/tests/scriptableResource/prefetchTest.js @@ -0,0 +1,33 @@ +// +// testPrefetch.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures and unloads them. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include([ + '../../../developer/utilities/cache/cacheStats.js', + 'lib.js', +], function() { + prefetch(function(frames) { + // Delete each texture, so the next garbage collection cycle will release them. + + // Setting frames = null breaks the reference, + // but will not delete frames from the calling scope. + // Instead, we must mutate it in-place to free its elements for GC + // (assuming the elements are not held elsewhere). + while (frames.length) { frames.pop(); } + + // Alternatively, forcibly release each texture without relying on GC. + // frames.forEach(function(texture) { texture.release(); }); + + Script.requestGarbageCollection(); + }); +}); + diff --git a/scripts/system/away.js b/scripts/system/away.js index 687345a5e1..932efd6b60 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -46,8 +46,8 @@ var AWAY_INTRO = { endFrame: 83.0 }; -// prefetch the kneel animation so it's resident in memory when we need it. -MyAvatar.prefetchAnimation(AWAY_INTRO.url); +// prefetch the kneel animation and hold a ref so it's always resident in memory when we need it. +var _animation = AnimationCache.prefetch(AWAY_INTRO.url); function playAwayAnimation() { MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame); diff --git a/scripts/tutorials/createDice.js b/scripts/tutorials/createDice.js index 527735da57..00ec1184bc 100644 --- a/scripts/tutorials/createDice.js +++ b/scripts/tutorials/createDice.js @@ -13,6 +13,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + var isDice = false; var NUMBER_OF_DICE = 4; var LIFETIME = 10000; // Dice will live for about 3 hours @@ -29,14 +31,14 @@ var screenSize = Controller.getViewportDimensions(); var BUTTON_SIZE = 32; var PADDING = 3; - +var BOTTOM_PADDING = 50; //a helper library for creating toolbars Script.include("http://hifi-production.s3.amazonaws.com/tutorials/dice/toolBars.js"); -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.dice.toolbar", function(screenSize) { +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.toolbars-dice", function(screenSize) { return { - x: (screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING), - y: (screenSize.y - (BUTTON_SIZE + PADDING)) + x: (screenSize.x - BUTTON_SIZE*3), + y: (screenSize.y - 100) }; }); @@ -54,7 +56,7 @@ var offButton = toolBar.addOverlay("image", { var deleteButton = toolBar.addOverlay("image", { x: screenSize.x / 2 - BUTTON_SIZE, - y: screenSize.y - (BUTTON_SIZE + PADDING), + y: screenSize.y - (BUTTON_SIZE + PADDING)+BOTTOM_PADDING, width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/delete.png", @@ -69,7 +71,7 @@ var deleteButton = toolBar.addOverlay("image", { var diceIconURL = "http://hifi-production.s3.amazonaws.com/tutorials/dice/dice.png" var diceButton = toolBar.addOverlay("image", { x: screenSize.x / 2 + PADDING, - y: screenSize.y - (BUTTON_SIZE + PADDING), + y: screenSize.y - (BUTTON_SIZE + PADDING)+BOTTOM_PADDING, width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: diceIconURL,