diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 21d44a4145..4002cca0c1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -106,8 +106,8 @@ #include <MessagesClient.h> #include <hfm/ModelFormatRegistry.h> #include <model-networking/ModelCacheScriptingInterface.h> +#include <material-networking/MaterialCacheScriptingInterface.h> #include <material-networking/TextureCacheScriptingInterface.h> -#include <material-networking/MaterialCache.h> #include <ModelEntityItem.h> #include <NetworkAccessManager.h> #include <NetworkingConstants.h> @@ -886,6 +886,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set<TextureCache>(); DependencyManager::set<MaterialCache>(); DependencyManager::set<TextureCacheScriptingInterface>(); + DependencyManager::set<MaterialCacheScriptingInterface>(); DependencyManager::set<FramebufferCache>(); DependencyManager::set<AnimationCache>(); DependencyManager::set<AnimationCacheScriptingInterface>(); @@ -2906,6 +2907,7 @@ Application::~Application() { DependencyManager::destroy<AnimationCacheScriptingInterface>(); DependencyManager::destroy<AnimationCache>(); DependencyManager::destroy<FramebufferCache>(); + DependencyManager::destroy<MaterialCacheScriptingInterface>(); DependencyManager::destroy<MaterialCache>(); DependencyManager::destroy<TextureCacheScriptingInterface>(); DependencyManager::destroy<TextureCache>(); @@ -3431,6 +3433,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { // Caches surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data()); surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data()); + surfaceContext->setContextProperty("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data()); surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data()); surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data()); @@ -7461,6 +7464,7 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data()); scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data()); + scriptEngine->registerGlobalObject("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data()); scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data()); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 2697d30de4..d11f6dd6f4 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -108,15 +108,6 @@ protected: virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; } virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; } - - template <typename F, typename T> - T withReadLockResult(const std::function<T()>& f) { - T result; - withReadLock([&] { - result = f(); - }); - return result; - } signals: void requestRenderUpdate(); diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 428c08e9f6..1295fc5722 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -50,6 +50,8 @@ namespace scriptable { * @property {string} emissiveMap * @property {string} albedoMap * @property {string} opacityMap + * @property {string} opacityMapMode + * @property {number|string} opacityCutoff * @property {string} metallicMap * @property {string} specularMap * @property {string} roughnessMap @@ -84,6 +86,8 @@ namespace scriptable { QString emissiveMap; QString albedoMap; QString opacityMap; + QString opacityMapMode; + float opacityCutoff; QString metallicMap; QString specularMap; QString roughnessMap; @@ -94,7 +98,6 @@ namespace scriptable { QString lightMap; QString scatteringMap; std::array<glm::mat4, graphics::Material::NUM_TEXCOORD_TRANSFORMS> texCoordTransforms; - bool defaultFallthrough; std::unordered_map<uint, bool> propertyFallthroughs; // not actually exposed to script diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 45f8461a1a..ca9634e365 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -420,6 +420,18 @@ namespace scriptable { obj.setProperty("opacityMap", material.opacityMap); } + if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT | graphics::MaterialKey::OPACITY_MASK_MAP_BIT)) { + obj.setProperty("opacityMapMode", FALLTHROUGH); + } else if (material.key.getOpacityMapMode() != graphics::Material::DEFAULT_OPACITY_MAP_MODE) { + obj.setProperty("opacityMapMode", material.opacityMapMode); + } + + if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_CUTOFF_VAL_BIT)) { + obj.setProperty("opacityCutoff", FALLTHROUGH); + } else if (material.key.isOpacityCutoff()) { + obj.setProperty("opacityCutoff", material.opacityCutoff); + } + if (hasPropertyFallthroughs && material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT)) { obj.setProperty("occlusionMap", FALLTHROUGH); } else if (!material.occlusionMap.isEmpty()) { diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index e9cc7930ae..bc610108ec 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -26,6 +26,7 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const roughness = material.roughness; metallic = material.metallic; scattering = material.scattering; + opacityCutoff = material.opacityCutoff; unlit = material.unlit; emissive = material.emissive; albedo = material.albedo; @@ -41,6 +42,8 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const occlusionMap = material.occlusionMap; lightMap = material.lightMap; scatteringMap = material.scatteringMap; + opacityMapMode = material.opacityMapMode; + defaultFallthrough = material.defaultFallthrough; propertyFallthroughs = material.propertyFallthroughs; @@ -55,9 +58,12 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint name = material->getName().c_str(); model = material->getModel().c_str(); opacity = material->getOpacity(); + + opacityMapMode = QString(graphics::MaterialKey::getOpacityMapModeName(material->getOpacityMapMode()).c_str()); roughness = material->getRoughness(); metallic = material->getMetallic(); scattering = material->getScattering(); + opacityCutoff = material->getOpacityCutoff(); unlit = material->isUnlit(); emissive = material->getEmissive(); albedo = material->getAlbedo(); diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 6be8ec5f68..dffc52e29f 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -14,6 +14,8 @@ #include <Transform.h> +#include "GraphicsLogging.h" + using namespace graphics; using namespace gpu; @@ -22,7 +24,26 @@ const float Material::DEFAULT_OPACITY { 1.0f }; const float Material::DEFAULT_ALBEDO { 0.5f }; const float Material::DEFAULT_METALLIC { 0.0f }; const float Material::DEFAULT_ROUGHNESS { 1.0f }; -const float Material::DEFAULT_SCATTERING { 0.0f }; +const float Material::DEFAULT_SCATTERING{ 0.0f }; +const MaterialKey::OpacityMapMode Material::DEFAULT_OPACITY_MAP_MODE{ MaterialKey::OPACITY_MAP_OPAQUE }; +const float Material::DEFAULT_OPACITY_CUTOFF { 0.5f }; + + +std::string MaterialKey::getOpacityMapModeName(OpacityMapMode mode) { + const std::string names[3] = { "OPACITY_MAP_OPAQUE", "OPACITY_MAP_MASK", "OPACITY_MAP_BLEND" }; + return names[mode]; +} + + +bool MaterialKey::getOpacityMapModeFromName(const std::string& modeName, MaterialKey::OpacityMapMode& mode) { + for (int i = OPACITY_MAP_OPAQUE; i <= OPACITY_MAP_BLEND; i++) { + mode = (MaterialKey::OpacityMapMode) i; + if (modeName == getOpacityMapModeName(mode)) { + return true; + } + } + return false; +} Material::Material() { for (int i = 0; i < NUM_TOTAL_FLAGS; i++) { @@ -40,6 +61,7 @@ Material::Material(const Material& material) : _roughness(material._roughness), _metallic(material._metallic), _scattering(material._scattering), + _opacityCutoff(material._opacityCutoff), _texcoordTransforms(material._texcoordTransforms), _lightmapParams(material._lightmapParams), _materialParams(material._materialParams), @@ -50,7 +72,7 @@ Material::Material(const Material& material) : } Material& Material::operator=(const Material& material) { - QMutexLocker locker(&_textureMapsMutex); + std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex); _name = material._name; _model = material._model; @@ -61,6 +83,7 @@ Material& Material::operator=(const Material& material) { _roughness = material._roughness; _metallic = material._metallic; _scattering = material._scattering; + _opacityCutoff = material._opacityCutoff; _texcoordTransforms = material._texcoordTransforms; _lightmapParams = material._lightmapParams; _materialParams = material._materialParams; @@ -109,8 +132,22 @@ void Material::setScattering(float scattering) { _scattering = scattering; } +void Material::setOpacityCutoff(float opacityCutoff) { + opacityCutoff = glm::clamp(opacityCutoff, 0.0f, 1.0f); + _key.setOpacityCutoff(opacityCutoff != DEFAULT_OPACITY_CUTOFF); + _opacityCutoff = opacityCutoff; +} + +void Material::setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode) { + _key.setOpacityMapMode(opacityMapMode); +} + +MaterialKey::OpacityMapMode Material::getOpacityMapMode() const { + return _key.getOpacityMapMode(); +} + void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { - QMutexLocker locker(&_textureMapsMutex); + std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex); if (textureMap) { _key.setMapChannel(channel, true); @@ -139,7 +176,14 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur } -void Material::resetOpacityMap() const { +bool Material::resetOpacityMap() const { + // If OpacityMapMode explicit then nothing need to change here. + if (_key.isOpacityMapMode()) { + return false; + } + + // Else, the legacy behavior is to interpret the albedo texture assigned to tune the opacity map mode value + auto previous = _key.getOpacityMapMode(); // Clear the previous flags _key.setOpacityMaskMap(false); _key.setTranslucentMap(false); @@ -163,10 +207,16 @@ void Material::resetOpacityMap() const { } } } + auto newious = _key.getOpacityMapMode(); + if (previous != newious) { + //opacity change detected for this material + return true; + } + return false; } const TextureMapPointer Material::getTextureMap(MapChannel channel) const { - QMutexLocker locker(&_textureMapsMutex); + std::lock_guard<std::recursive_mutex> locker(_textureMapsMutex); auto result = _textureMaps.find(channel); if (result != _textureMaps.end()) { diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 25601c5743..25ff711c0c 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -11,8 +11,7 @@ #ifndef hifi_model_Material_h #define hifi_model_Material_h -#include <QMutex> - +#include <mutex> #include <bitset> #include <map> #include <unordered_map> @@ -44,6 +43,8 @@ public: OPACITY_VAL_BIT, OPACITY_MASK_MAP_BIT, // Opacity Map and Opacity MASK map are mutually exclusive OPACITY_TRANSLUCENT_MAP_BIT, + OPACITY_MAP_MODE_BIT, // Opacity map mode bit is set if the value has set explicitely and not deduced from the textures assigned + OPACITY_CUTOFF_VAL_BIT, SCATTERING_VAL_BIT, // THe map bits must be in the same sequence as the enum names for the map channels @@ -73,6 +74,15 @@ public: NUM_MAP_CHANNELS, }; + enum OpacityMapMode { + OPACITY_MAP_OPAQUE = 0, + OPACITY_MAP_MASK, + OPACITY_MAP_BLEND, + }; + static std::string getOpacityMapModeName(OpacityMapMode mode); + // find the enum value from a string, return true if match found + static bool getOpacityMapModeFromName(const std::string& modeName, OpacityMapMode& mode); + // The signature is the Flags Flags _flags; @@ -94,6 +104,27 @@ public: Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); } Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); } + Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); } + Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); } + Builder& withOpacityMapMode(OpacityMapMode mode) { + switch (mode) { + case OPACITY_MAP_OPAQUE: + _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.reset(OPACITY_MASK_MAP_BIT); + break; + case OPACITY_MAP_MASK: + _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.set(OPACITY_MASK_MAP_BIT); + break; + case OPACITY_MAP_BLEND: + _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.reset(OPACITY_MASK_MAP_BIT); + break; + }; + _flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode! + return (*this); + } + Builder& withOpacityCutoff() { _flags.set(OPACITY_CUTOFF_VAL_BIT); return (*this); } Builder& withScattering() { _flags.set(SCATTERING_VAL_BIT); return (*this); } @@ -102,9 +133,6 @@ public: Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); } Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); } - Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); } - Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); } - Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); } Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); } Builder& withLightMap() { _flags.set(LIGHT_MAP_BIT); return (*this); } @@ -151,6 +179,9 @@ public: void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); } bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; } + void setOpacityCutoff(bool value) { _flags.set(OPACITY_CUTOFF_VAL_BIT, value); } + bool isOpacityCutoff() const { return _flags[OPACITY_CUTOFF_VAL_BIT]; } + void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); } bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; } @@ -171,6 +202,26 @@ public: // Translucency and Opacity Heuristics are combining several flags: + void setOpacityMapMode(OpacityMapMode mode) { + switch (mode) { + case OPACITY_MAP_OPAQUE: + _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.reset(OPACITY_MASK_MAP_BIT); + break; + case OPACITY_MAP_MASK: + _flags.reset(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.set(OPACITY_MASK_MAP_BIT); + break; + case OPACITY_MAP_BLEND: + _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); + _flags.reset(OPACITY_MASK_MAP_BIT); + break; + }; + _flags.set(OPACITY_MAP_MODE_BIT); // Intentionally set the mode! + } + bool isOpacityMapMode() const { return _flags[OPACITY_MAP_MODE_BIT]; } + OpacityMapMode getOpacityMapMode() const { return (isOpacityMaskMap() ? OPACITY_MAP_MASK : (isTranslucentMap() ? OPACITY_MAP_BLEND : OPACITY_MAP_OPAQUE)); } + bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); } bool isOpaque() const { return !isTranslucent(); } bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); } @@ -229,6 +280,12 @@ public: Builder& withoutMaskMap() { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); } Builder& withMaskMap() { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); } + Builder& withoutOpacityMapMode() { _value.reset(MaterialKey::OPACITY_MAP_MODE_BIT); _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); } + Builder& withOpacityMapMode() { _value.set(MaterialKey::OPACITY_MAP_MODE_BIT); _mask.set(MaterialKey::OPACITY_MAP_MODE_BIT); return (*this); } + + Builder& withoutOpacityCutoff() { _value.reset(MaterialKey::OPACITY_CUTOFF_VAL_BIT); _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); } + Builder& withOpacityCutoff() { _value.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); _mask.set(MaterialKey::OPACITY_CUTOFF_VAL_BIT); return (*this); } + Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } @@ -283,6 +340,14 @@ public: void setOpacity(float opacity); float getOpacity() const { return _opacity; } + static const MaterialKey::OpacityMapMode DEFAULT_OPACITY_MAP_MODE; + void setOpacityMapMode(MaterialKey::OpacityMapMode opacityMapMode); + MaterialKey::OpacityMapMode getOpacityMapMode() const; + + static const float DEFAULT_OPACITY_CUTOFF; + void setOpacityCutoff(float opacityCutoff); + float getOpacityCutoff() const { return _opacityCutoff; } + void setUnlit(bool value); bool isUnlit() const { return _key.isUnlit(); } @@ -310,7 +375,8 @@ public: // Albedo maps cannot have opacity detected until they are loaded // This method allows const changing of the key/schemaBuffer without touching the map - void resetOpacityMap() const; + // return true if the opacity changed, flase otherwise + bool resetOpacityMap() const; // conversion from legacy material properties to PBR equivalent static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } @@ -357,6 +423,7 @@ private: float _roughness { DEFAULT_ROUGHNESS }; float _metallic { DEFAULT_METALLIC }; float _scattering { DEFAULT_SCATTERING }; + float _opacityCutoff { DEFAULT_OPACITY_CUTOFF }; std::array<glm::mat4, NUM_TEXCOORD_TRANSFORMS> _texcoordTransforms; glm::vec2 _lightmapParams { 0.0, 1.0 }; glm::vec2 _materialParams { 0.0, 1.0 }; @@ -365,7 +432,7 @@ private: bool _defaultFallthrough { false }; std::unordered_map<uint, bool> _propertyFallthroughs { NUM_TOTAL_FLAGS }; - mutable QMutex _textureMapsMutex { QMutex::Recursive }; + mutable std::recursive_mutex _textureMapsMutex; }; typedef std::shared_ptr<Material> MaterialPointer; @@ -425,18 +492,8 @@ public: float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec2 _spare { 0.0f }; // Padding - + float _opacityCutoff { Material::DEFAULT_OPACITY_CUTOFF }; // Opacity cutoff applyed when using opacityMap as Mask uint32_t _key { 0 }; // a copy of the materialKey -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec3 _spare2 { 0.0f }; - - // for alignment beauty, Material size == Mat4x4 // Texture Coord Transform Array glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS]; diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh index dfd4a8eec4..328ff4a3af 100644 --- a/libraries/graphics/src/graphics/Material.slh +++ b/libraries/graphics/src/graphics/Material.slh @@ -49,8 +49,7 @@ struct TexMapArray { struct Material { vec4 _emissiveOpacity; vec4 _albedoRoughness; - vec4 _metallicScatteringSpare2; - vec4 _keySpare3; + vec4 _metallicScatteringOpacityCutoffKey; }; LAYOUT_STD140(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer { @@ -72,10 +71,11 @@ vec3 getMaterialAlbedo(Material m) { return m._albedoRoughness.rgb; } float getMaterialRoughness(Material m) { return m._albedoRoughness.a; } float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); } -float getMaterialMetallic(Material m) { return m._metallicScatteringSpare2.x; } -float getMaterialScattering(Material m) { return m._metallicScatteringSpare2.y; } +float getMaterialMetallic(Material m) { return m._metallicScatteringOpacityCutoffKey.x; } +float getMaterialScattering(Material m) { return m._metallicScatteringOpacityCutoffKey.y; } +float getMaterialOpacityCutoff(Material m) { return m._metallicScatteringOpacityCutoffKey.z; } -BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._keySpare3.x); } +BITFIELD getMaterialKey(Material m) { return floatBitsToInt(m._metallicScatteringOpacityCutoffKey.w); } const BITFIELD EMISSIVE_VAL_BIT = 0x00000001; const BITFIELD UNLIT_VAL_BIT = 0x00000002; @@ -85,16 +85,18 @@ const BITFIELD GLOSSY_VAL_BIT = 0x00000010; const BITFIELD OPACITY_VAL_BIT = 0x00000020; const BITFIELD OPACITY_MASK_MAP_BIT = 0x00000040; const BITFIELD OPACITY_TRANSLUCENT_MAP_BIT = 0x00000080; -const BITFIELD SCATTERING_VAL_BIT = 0x00000100; +const BITFIELD OPACITY_MAP_MODE_BIT = 0x00000100; +const BITFIELD OPACITY_CUTOFF_VAL_BIT = 0x00000200; +const BITFIELD SCATTERING_VAL_BIT = 0x00000400; -const BITFIELD EMISSIVE_MAP_BIT = 0x00000200; -const BITFIELD ALBEDO_MAP_BIT = 0x00000400; -const BITFIELD METALLIC_MAP_BIT = 0x00000800; -const BITFIELD ROUGHNESS_MAP_BIT = 0x00001000; -const BITFIELD NORMAL_MAP_BIT = 0x00002000; -const BITFIELD OCCLUSION_MAP_BIT = 0x00004000; -const BITFIELD LIGHTMAP_MAP_BIT = 0x00008000; -const BITFIELD SCATTERING_MAP_BIT = 0x00010000; +const BITFIELD EMISSIVE_MAP_BIT = 0x00000800; +const BITFIELD ALBEDO_MAP_BIT = 0x00001000; +const BITFIELD METALLIC_MAP_BIT = 0x00002000; +const BITFIELD ROUGHNESS_MAP_BIT = 0x00004000; +const BITFIELD NORMAL_MAP_BIT = 0x00008000; +const BITFIELD OCCLUSION_MAP_BIT = 0x00010000; +const BITFIELD LIGHTMAP_MAP_BIT = 0x00020000; +const BITFIELD SCATTERING_MAP_BIT = 0x00040000; <@endif@> diff --git a/libraries/graphics/src/graphics/MaterialTextures.slh b/libraries/graphics/src/graphics/MaterialTextures.slh index 278057b01a..2a291e5d57 100644 --- a/libraries/graphics/src/graphics/MaterialTextures.slh +++ b/libraries/graphics/src/graphics/MaterialTextures.slh @@ -214,14 +214,22 @@ vec3 fetchLightMap(vec2 uv) { } <@endfunc@> -<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@> +<@func evalMaterialOpacityMask(fetchedOpacity, materialOpacityCutoff, opacity)@> { - const float OPACITY_MASK_THRESHOLD = 0.5; - <$opacity$> = mix(1.0, - mix(<$fetchedOpacity$>, - step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>), - float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0)), - float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) * <$materialOpacity$>; + // This path only valid for opaque or texel opaque material + <$opacity$> = step(<$materialOpacityCutoff$>, <$fetchedOpacity$>); +} +<@endfunc@> + + +<@func evalMaterialOpacity(fetchedOpacity, materialOpacityCutoff, materialOpacity, matKey, opacity)@> +{ + // This path only valid for transparent material + // Assert that float((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0)) == 1.0 + <$opacity$> = mix(<$fetchedOpacity$>, + step(<$materialOpacityCutoff$>, <$fetchedOpacity$>), + float((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0)) + * <$materialOpacity$>; } <@endfunc@> diff --git a/libraries/material-networking/src/material-networking/MaterialCache.cpp b/libraries/material-networking/src/material-networking/MaterialCache.cpp index 087e1ca049..db4783d249 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.cpp +++ b/libraries/material-networking/src/material-networking/MaterialCache.cpp @@ -139,6 +139,14 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater * @property {string} opacityMap - The URL of the opacity texture image. Set the value the same as the <code>albedoMap</code> * value for transparency. * <code>"hifi_pbr"</code> model only. + * @property {number|string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be: + * <code>"OPACITY_MAP_OPAQUE"</code> for ignoring the opacity map information. + * <code>"OPACITY_MAP_MASK"</code> for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque. + * <code>"OPACITY_MAP_BLEND"</code> for using the opacity map for alpha blending the material surface with the background. + * Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only. + * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map + * when opacityMapMode is "OPACITY_MAP_MASK", range <code>0.0</code> – <code>1.0</code>. + * Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only. * @property {string} roughnessMap - The URL of the roughness texture image. You can use this or <code>glossMap</code>, but not * both. * Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only. @@ -258,6 +266,24 @@ std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource } else if (value.isDouble()) { material->setMetallic(value.toDouble()); } + } else if (key == "opacityMapMode") { + auto value = materialJSON.value(key); + auto valueString = (value.isString() ? value.toString() : ""); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_MAP_MODE_BIT); + } else { + graphics::MaterialKey::OpacityMapMode mode; + if (graphics::MaterialKey::getOpacityMapModeFromName(valueString.toStdString(), mode)) { + material->setOpacityMapMode(mode); + } + } + } else if (key == "opacityCutoff") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_CUTOFF_VAL_BIT); + } else if (value.isDouble()) { + material->setOpacityCutoff(value.toDouble()); + } } else if (key == "scattering") { auto value = materialJSON.value(key); if (value.isString() && value.toString() == FALLTHROUGH) { @@ -748,13 +774,14 @@ bool NetworkMaterial::isMissingTexture() { return false; } -void NetworkMaterial::checkResetOpacityMap() { +bool NetworkMaterial::checkResetOpacityMap() { // If material textures are loaded, check the material translucency // FIXME: This should not be done here. The opacity map should already be reset in Material::setTextureMap. // However, currently that code can be called before the albedo map is defined, so resetOpacityMap will fail. // Geometry::areTexturesLoaded() is called repeatedly until it returns true, so we do the check here for now const auto& albedoTexture = _textures[NetworkMaterial::MapChannel::ALBEDO_MAP]; if (albedoTexture.texture) { - resetOpacityMap(); + return resetOpacityMap(); } + return false; } diff --git a/libraries/material-networking/src/material-networking/MaterialCache.h b/libraries/material-networking/src/material-networking/MaterialCache.h index 18aa5e5994..aa103adb1e 100644 --- a/libraries/material-networking/src/material-networking/MaterialCache.h +++ b/libraries/material-networking/src/material-networking/MaterialCache.h @@ -34,7 +34,7 @@ public: void setLightMap(const QUrl& url); bool isMissingTexture(); - void checkResetOpacityMap(); + bool checkResetOpacityMap(); class Texture { public: diff --git a/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp new file mode 100644 index 0000000000..193d9b96ee --- /dev/null +++ b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.cpp @@ -0,0 +1,17 @@ +// +// MaterialCacheScriptingInterface.cpp +// libraries/mmodel-networking/src/model-networking +// +// Created by Sam Gateau on 17 September 2019. +// 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 "MaterialCacheScriptingInterface.h" + +MaterialCacheScriptingInterface::MaterialCacheScriptingInterface() : + ScriptableResourceCache::ScriptableResourceCache(DependencyManager::get<MaterialCache>()) +{ } + diff --git a/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h new file mode 100644 index 0000000000..c619966a2a --- /dev/null +++ b/libraries/material-networking/src/material-networking/MaterialCacheScriptingInterface.h @@ -0,0 +1,51 @@ +// +// MaterialCacheScriptingInterface.h +// libraries/material-networking/src/material-networking +// +// Created by Sam Gateau on 17 September 2019. +// 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 +// +#pragma once + +#ifndef hifi_MaterialCacheScriptingInterface_h +#define hifi_MaterialCacheScriptingInterface_h + +#include <QObject> + +#include <ResourceCache.h> + +#include "MaterialCache.h" + +class MaterialCacheScriptingInterface : public ScriptableResourceCache, public Dependency { + Q_OBJECT + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * The <code>TextureCache</code> API manages texture cache resources. + * + * @namespace TextureCache + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @property {number} numTotal - Total number of total resources. <em>Read-only.</em> + * @property {number} numCached - Total number of cached resource. <em>Read-only.</em> + * @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em> + * @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em> + * + * @borrows ResourceCache.getResourceList as getResourceList + * @borrows ResourceCache.updateTotalSize as updateTotalSize + * @borrows ResourceCache.prefetch as prefetch + * @borrows ResourceCache.dirty as dirty + */ + +public: + MaterialCacheScriptingInterface(); +}; + +#endif // hifi_MaterialCacheScriptingInterface_h diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 1ed1c65358..1fcfcfcc70 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -472,7 +472,10 @@ bool Geometry::areTexturesLoaded() const { return false; } - material->checkResetOpacityMap(); + bool changed = material->checkResetOpacityMap(); + if (changed) { + qCWarning(modelnetworking) << "Material list: opacity change detected for material " << material->getName().c_str(); + } } for (auto& materialMapping : _materialMapping) { @@ -483,7 +486,10 @@ bool Geometry::areTexturesLoaded() const { return false; } - materialPair.second->checkResetOpacityMap(); + bool changed = materialPair.second->checkResetOpacityMap(); + if (changed) { + qCWarning(modelnetworking) << "Mapping list: opacity change detected for material " << materialPair.first.c_str(); + } } } } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 88e6b14ec7..88a4f5bf32 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -148,6 +148,8 @@ void ResourceCacheSharedItems::clear() { ScriptableResourceCache::ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache) { _resourceCache = resourceCache; + connect(&(*_resourceCache), &ResourceCache::dirty, + this, &ScriptableResourceCache::dirty, Qt::DirectConnection); } QVariantList ScriptableResourceCache::getResourceList() { @@ -323,7 +325,11 @@ QVariantList ResourceCache::getResourceList() { BLOCKING_INVOKE_METHOD(this, "getResourceList", Q_RETURN_ARG(QVariantList, list)); } else { - auto resources = _resources.uniqueKeys(); + QList<QUrl> resources; + { + QReadLocker locker(&_resourcesLock); + resources = _resources.uniqueKeys(); + } list.reserve(resources.size()); for (auto& resource : resources) { list << resource; @@ -510,7 +516,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } - + QList<QSharedPointer<Resource>> ResourceCache::getLoadingRequests() { return DependencyManager::get<ResourceCacheSharedItems>()->getLoadingRequests(); } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index e1f3098658..4213d92fc0 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -317,6 +317,13 @@ class ScriptableResourceCache : public QObject { Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) + /**jsdoc + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). <em>Read-only.</em> + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). <em>Read-only.</em> + */ + Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty) + Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty) + public: ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache); @@ -390,6 +397,9 @@ private: size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); } size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); } size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); } + + size_t getNumGlobalQueriesPending() const { return ResourceCache::getPendingRequestCount(); } + size_t getNumGlobalQueriesLoading() const { return ResourceCache::getLoadingRequestCount(); } }; /// Base class for resources. diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index d0ebe167f9..518e43a8ec 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -461,6 +461,13 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial wasSet = true; } break; + case graphics::MaterialKey::OPACITY_CUTOFF_VAL_BIT: + if (materialKey.isOpacityCutoff()) { + schema._opacityCutoff = material->getOpacityCutoff(); + schemaKey.setOpacityCutoff(true); + wasSet = true; + } + break; case graphics::MaterialKey::SCATTERING_VAL_BIT: if (materialKey.isScattering()) { schema._scattering = material->getScattering(); @@ -752,7 +759,7 @@ bool RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu: // For shadows, we only need opacity mask information auto key = multiMaterial.getMaterialKey(); - if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || key.isOpacityMaskMap()) { + if (renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE || (key.isOpacityMaskMap() || key.isTranslucentMap())) { auto& schemaBuffer = multiMaterial.getSchemaBuffer(); batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer); if (enableTextures) { diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 3e4711dac8..bacc6b0ab1 100644 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -103,14 +103,14 @@ void main(void) { <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> <@if HIFI_USE_TRANSLUCENT@> + float cutoff = getMaterialOpacityCutoff(mat); float opacity = getMaterialOpacity(mat) * _color.a; - <@else@> - float opacity = 1.0; - <@endif@> - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <@if HIFI_USE_TRANSLUCENT@> + <$evalMaterialOpacity(albedoTex.a, cutoff, opacity, matKey, opacity)$>; <$discardInvisible(opacity)$>; <@else@> + float cutoff = getMaterialOpacityCutoff(mat); + float opacity = 1.0; + <$evalMaterialOpacityMask(albedoTex.a, cutoff, opacity)$>; <$discardTransparent(opacity)$>; <@endif@> @@ -155,15 +155,15 @@ void main(void) { <@endif@> <@if HIFI_USE_TRANSLUCENT@> + float cutoff = getMaterialOpacityCutoff(mat); float opacity = getMaterialOpacity(mat) * _color.a; + <$evalMaterialOpacity(albedoTex.a, cutoff, opacity, matKey, opacity)$>; + <$discardInvisible(opacity)$>; <@else@> + float cutoff = getMaterialOpacityCutoff(mat); float opacity = 1.0; - <@endif@> - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <@if HIFI_USE_TRANSLUCENT@> - <$discardInvisible(opacity)$>; - <@else@> - <$discardTransparent(opacity)$>; + <$evalMaterialOpacityMask(albedoTex.a, cutoff, opacity)$>; + <$discardTransparent(opacity)$>; <@endif@> vec3 albedo = getMaterialAlbedo(mat); @@ -217,13 +217,13 @@ void main(void) { _fragColor0 = color; <@else@> _fragColor0 = vec4(evalLightmappedColor( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragNormalWS, - albedo, - lightmap), - opacity); + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragNormalWS, + albedo, + lightmap), + opacity); <@endif@> <@else@> <@if not HIFI_USE_LIGHTMAP@> @@ -241,13 +241,13 @@ void main(void) { opacity); <@else@> _fragColor0 = vec4(evalLightmappedColor( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragNormalWS, - albedo, - lightmap), - opacity); + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragNormalWS, + albedo, + lightmap), + opacity); <@endif@> <@endif@> <@else@> @@ -315,13 +315,13 @@ void main(void) { opacity); <@else@> _fragColor0 = vec4(evalLightmappedColor( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragNormalWS, - albedo, - lightmap), - opacity); + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragNormalWS, + albedo, + lightmap), + opacity); <@endif@> <@endif@> <@endif@> diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp index ae1467ac0f..6fbd3e6f9c 100644 --- a/libraries/render/src/render/EngineStats.cpp +++ b/libraries/render/src/render/EngineStats.cpp @@ -63,4 +63,6 @@ void EngineStats::run(const RenderContextPointer& renderContext) { config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; + + // These new stat values are notified with the "newStats" signal triggered by the timer } diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h index 3ccbd40715..9f8be748f7 100644 --- a/libraries/render/src/render/EngineStats.h +++ b/libraries/render/src/render/EngineStats.h @@ -24,42 +24,42 @@ namespace render { class EngineStatsConfig : public Job::Config{ Q_OBJECT - Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY dirty) - Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY dirty) - Q_PROPERTY(qint64 bufferCPUMemSize MEMBER bufferCPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 bufferGPUMemSize MEMBER bufferGPUMemSize NOTIFY dirty) + Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY newStats) + Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY newStats) + Q_PROPERTY(qint64 bufferCPUMemSize MEMBER bufferCPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 bufferGPUMemSize MEMBER bufferGPUMemSize NOTIFY newStats) - Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY dirty) - Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY dirty) - Q_PROPERTY(quint32 textureResidentGPUCount MEMBER textureResidentGPUCount NOTIFY dirty) - Q_PROPERTY(quint32 textureFramebufferGPUCount MEMBER textureFramebufferGPUCount NOTIFY dirty) - Q_PROPERTY(quint32 textureResourceGPUCount MEMBER textureResourceGPUCount NOTIFY dirty) - Q_PROPERTY(quint32 textureExternalGPUCount MEMBER textureExternalGPUCount NOTIFY dirty) + Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY newStats) + Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY newStats) + Q_PROPERTY(quint32 textureResidentGPUCount MEMBER textureResidentGPUCount NOTIFY newStats) + Q_PROPERTY(quint32 textureFramebufferGPUCount MEMBER textureFramebufferGPUCount NOTIFY newStats) + Q_PROPERTY(quint32 textureResourceGPUCount MEMBER textureResourceGPUCount NOTIFY newStats) + Q_PROPERTY(quint32 textureExternalGPUCount MEMBER textureExternalGPUCount NOTIFY newStats) - Q_PROPERTY(qint64 textureCPUMemSize MEMBER textureCPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 textureGPUMemSize MEMBER textureGPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 textureResidentGPUMemSize MEMBER textureResidentGPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 textureFramebufferGPUMemSize MEMBER textureFramebufferGPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 textureResourceGPUMemSize MEMBER textureResourceGPUMemSize NOTIFY dirty) - Q_PROPERTY(qint64 textureExternalGPUMemSize MEMBER textureExternalGPUMemSize NOTIFY dirty) + Q_PROPERTY(qint64 textureCPUMemSize MEMBER textureCPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 textureGPUMemSize MEMBER textureGPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 textureResidentGPUMemSize MEMBER textureResidentGPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 textureFramebufferGPUMemSize MEMBER textureFramebufferGPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 textureResourceGPUMemSize MEMBER textureResourceGPUMemSize NOTIFY newStats) + Q_PROPERTY(qint64 textureExternalGPUMemSize MEMBER textureExternalGPUMemSize NOTIFY newStats) - Q_PROPERTY(quint32 texturePendingGPUTransferCount MEMBER texturePendingGPUTransferCount NOTIFY dirty) - Q_PROPERTY(qint64 texturePendingGPUTransferSize MEMBER texturePendingGPUTransferSize NOTIFY dirty) - Q_PROPERTY(qint64 textureResourcePopulatedGPUMemSize MEMBER textureResourcePopulatedGPUMemSize NOTIFY dirty) + Q_PROPERTY(quint32 texturePendingGPUTransferCount MEMBER texturePendingGPUTransferCount NOTIFY newStats) + Q_PROPERTY(qint64 texturePendingGPUTransferSize MEMBER texturePendingGPUTransferSize NOTIFY newStats) + Q_PROPERTY(qint64 textureResourcePopulatedGPUMemSize MEMBER textureResourcePopulatedGPUMemSize NOTIFY newStats) - Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY dirty) - Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty) - Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY dirty) + Q_PROPERTY(quint32 frameAPIDrawcallCount MEMBER frameAPIDrawcallCount NOTIFY newStats) + Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY newStats) + Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY newStats) - Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY dirty) - Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY dirty) + Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY newStats) + Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY newStats) - Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY dirty) - Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty) - Q_PROPERTY(quint64 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY dirty) + Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY newStats) + Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY newStats) + Q_PROPERTY(quint64 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY newStats) - Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY dirty) - Q_PROPERTY(quint32 frameSetInputFormatCount MEMBER frameSetInputFormatCount NOTIFY dirty) + Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY newStats) + Q_PROPERTY(quint32 frameSetInputFormatCount MEMBER frameSetInputFormatCount NOTIFY newStats) public: @@ -101,13 +101,6 @@ namespace render { quint32 frameSetPipelineCount{ 0 }; quint32 frameSetInputFormatCount{ 0 }; - - - - void emitDirty() { emit dirty(); } - - signals: - void dirty(); }; class EngineStats { diff --git a/scripts/developer/utilities/cache/cash.js b/scripts/developer/utilities/cache/cash.js new file mode 100644 index 0000000000..14ecf873e3 --- /dev/null +++ b/scripts/developer/utilities/cache/cash.js @@ -0,0 +1,59 @@ +"use strict"; +var Page = Script.require('../lib/skit/Page.js'); + +function openView() { + //window.closed.connect(function() { Script.stop(); }); + + + var pages = new Pages(Script.resolvePath(".")); + function fromQml(message) { + console.log(JSON.stringify(message)) + if (message.method == "inspectResource") { + pages.open("openResourceInspector") + pages.sendTo("openResourceInspector", message) + return; + } + if (pages.open(message.method)) { + return; + } + } + + function openCashWindow(window) { + var onMousePressEvent = function (e) { + }; + Controller.mousePressEvent.connect(onMousePressEvent); + + var onMouseReleaseEvent = function () { + }; + Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); + + var onMouseMoveEvent = function (e) { + }; + Controller.mouseMoveEvent.connect(onMouseMoveEvent); + } + + function closeCashWindow() { + Controller.mousePressEvent.disconnect(onMousePressEvent); + Controller.mouseReleaseEvent.disconnect(onMouseReleaseEvent); + Controller.mouseMoveEvent.disconnect(onMouseMoveEvent); + pages.clear(); + } + + + pages.addPage('Cash', 'Cash', "cash.qml", 300, 500, fromQml, openCashWindow, closeCashWindow); + pages.addPage('openModelCacheInspector', 'Model Cache Inspector', "cash/ModelCacheInspector.qml", 300, 500, fromQml); + pages.addPage('openMaterialCacheInspector', 'Material Cache Inspector', "cash/MaterialCacheInspector.qml", 300, 500, fromQml); + pages.addPage('openTextureCacheInspector', 'Texture Cache Inspector', "cash/TextureCacheInspector.qml", 300, 500, fromQml); + pages.addPage('openAnimationCacheInspector', 'Animation Cache Inspector', "cash/AnimationCacheInspector.qml", 300, 500); + pages.addPage('openSoundCacheInspector', 'Sound Cache Inspector', "cash/SoundCacheInspector.qml", 300, 500); + pages.addPage('openResourceInspector', 'Resource Inspector', "cash/ResourceInspector.qml", 300, 500); + + + pages.open('Cash'); + + + return pages; +} + + +openView(); diff --git a/scripts/developer/utilities/cache/cash.qml b/scripts/developer/utilities/cache/cash.qml new file mode 100644 index 0000000000..159ce95c5f --- /dev/null +++ b/scripts/developer/utilities/cache/cash.qml @@ -0,0 +1,167 @@ +// +// cash.qml +// +// Created by Sam Gateau on 17/9/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import controlsUit 1.0 as HifiControls + +import "../lib/prop" as Prop +import "cash" +import "../lib/plotperf" + +Rectangle { + anchors.fill: parent + id: root; + + Prop.Global { id: global;} + color: global.color + + ScrollView { + id: scrollView + anchors.fill: parent + contentWidth: parent.width + clip: true + + Column { + id: column + width: parent.width + + Prop.PropFolderPanel { + label: "Resource Queries Inspector" + isUnfold: true + panelFrameData: Component { + Column { + PlotPerf { + title: "Global Queries" + height: 80 + valueScale: 1 + valueUnit: "" + plots: [ + { + object: ModelCache, + prop: "numGlobalQueriesPending", + label: "Pending", + color: "#1AC567" + }, + { + object: ModelCache, + prop: "numGlobalQueriesLoading", + label: "Loading", + color: "#FEC567" + }, + { + object: ModelCache, + prop: "numLoading", + label: "Model Loading", + color: "#C5FE67" + } + ] + } + } + } + } + + Prop.PropFolderPanel { + label: "Cache Inspectors" + isUnfold: true + panelFrameData: Component { + Column { + Prop.PropButton { + text: "Model" + onClicked: { + sendToScript({method: "openModelCacheInspector"}); + } + width:column.width + } + Prop.PropButton { + text: "Material" + onClicked: { + sendToScript({method: "openMaterialCacheInspector"}); + } + width:column.width + } + Prop.PropButton { + text: "Texture" + onClicked: { + sendToScript({method: "openTextureCacheInspector"}); + } + width:column.width + } + Prop.PropButton { + text: "Animation" + onClicked: { + sendToScript({method: "openAnimationCacheInspector"}); + } + width:column.width + } + Prop.PropButton { + text: "Sound" + onClicked: { + sendToScript({method: "openSoundCacheInspector"}); + } + width:column.width + } + } + } + } + Prop.PropFolderPanel { + label: "Stats" + isUnfold: true + panelFrameData: Component { Column { + PlotPerf { + title: "Resources" + height: 200 + valueScale: 1 + valueUnit: "" + plots: [ + { + object: TextureCache, + prop: "numTotal", + label: "Textures", + color: "#1AC567" + }, + { + object: TextureCache, + prop: "numCached", + label: "Textures Cached", + color: "#FEC567" + }, + { + object: ModelCache, + prop: "numTotal", + label: "Models", + color: "#FED959" + }, + { + object: ModelCache, + prop: "numCached", + label: "Models Cached", + color: "#FEFE59" + }, + { + object: MaterialCache, + prop: "numTotal", + label: "Materials", + color: "#00B4EF" + }, + { + object: MaterialCache, + prop: "numCached", + label: "Materials Cached", + color: "#FFB4EF" + } + ] + }} + } + } + } + } +} \ No newline at end of file diff --git a/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml b/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml new file mode 100644 index 0000000000..4ded44c2b1 --- /dev/null +++ b/scripts/developer/utilities/cache/cash/AnimationCacheInspector.qml @@ -0,0 +1,21 @@ +// +// AnimationCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +import "../../lib/prop" as Prop + +ResourceCacheInspector { + id: root; + anchors.fill: parent.fill + cache: AnimationCache + cacheResourceName: "Animation" +} diff --git a/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml b/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml new file mode 100644 index 0000000000..160c47c946 --- /dev/null +++ b/scripts/developer/utilities/cache/cash/MaterialCacheInspector.qml @@ -0,0 +1,21 @@ +// +// MaterialCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +import "../../lib/prop" as Prop + +ResourceCacheInspector { + id: root; + anchors.fill: parent.fill + cache: MaterialCache + cacheResourceName: "Material" +} diff --git a/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml b/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml new file mode 100644 index 0000000000..017942dfc9 --- /dev/null +++ b/scripts/developer/utilities/cache/cash/ModelCacheInspector.qml @@ -0,0 +1,21 @@ +// +// ModelCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +import "../../lib/prop" as Prop + +ResourceCacheInspector { + id: root; + anchors.fill: parent.fill + cache: ModelCache + cacheResourceName: "Model" +} diff --git a/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml b/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml new file mode 100644 index 0000000000..2fa0af8cbc --- /dev/null +++ b/scripts/developer/utilities/cache/cash/ResourceCacheInspector.qml @@ -0,0 +1,367 @@ +// +// ResourceCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.12 + +import "../../lib/skit/qml" as Skit +import "../../lib/prop" as Prop + +Item { + id: root; + Prop.Global { id: global } + + anchors.fill: parent.fill + property var cache: {} + property string cacheResourceName: "" + + function fromScript(message) { + switch (message.method) { + case "resetItemList": + resetItemListFromCache() + break; + } + } + + function requestResourceDetails(resourceURL) { + sendToScript({method: "inspectResource", params: {url: resourceURL, semantic: cacheResourceName}}); + } + + Component.onCompleted: { + resetItemListFromCache(); + } + + function fetchItemsList() { + var theList; + if (cache !== undefined) { + theList = cache.getResourceList(); + } else { + theList = ["ResourceCacheInspector.cache is undefined"]; + } + var theListString = new Array(theList.length) + for (var i in theList) { + theListString[i] = (theList[i].toString()) + } + return theListString; + } + + function resetItemListFromCache() { + resetItemList(fetchItemsList()) + } + + property var needFreshList : false + + function updateItemListFromCache() { + needFreshList = true + } + + + Timer { + interval: 2000; running: true; repeat: true + onTriggered: pullFreshValues() + } + + function pullFreshValues() { + if (needFreshList) { + updateItemList(fetchItemsList()) + needFreshList = false + } + } + + property alias resourceItemsModel: visualModel.model + property var currentItemsList: new Array(); + + function packItemEntry(item, identifier) { + var entry = { "identifier": identifier, "name": "", "scheme": "", "host": "", "pathDir": "", "url": item} + if (item.length > 0) { + // Detect scheme: + var schemePos = item.search(":") + entry.scheme = item.substring(0, schemePos) + if (schemePos < 0) schemePos = 0 + else schemePos += 1 + + // path pos is probably after schemePos + var pathPos = schemePos + + // try to detect //userinfo@host:port + var token = item.substr(schemePos, 2); + if (token.search("//") == 0) { + pathPos += 2 + } + item = item.substring(pathPos, item.length) + // item is now everything after scheme:[//] + var splitted = item.split('/') + + // odd ball, the rest of the url has no other'/' ? + // in theory this means it s just the host info ? + // we are assuming that path ALWAYS starts with a slash + entry.host = splitted[0] + + if (splitted.length > 1) { + entry.name = splitted[splitted.length - 1] + + // if splitted is longer than 2 then there should be a path dir + if (splitted.length > 2) { + for (var i = 1; i < splitted.length - 1; i++) { + entry.pathDir += '/' + splitted[i] + } + } + } + } + return entry + } + + + function resetItemList(itemList) { + currentItemsList = [] + resourceItemsModel.clear() + for (var i in itemList) { + var item = itemList[i] + currentItemsList.push(item) + resourceItemsModel.append(packItemEntry(item, currentItemsList.length -1)) + } + // At the end of it, force an update + visualModel.forceUpdate() + } + + function updateItemList(newItemList) { + resetItemList(newItemList) + } + + property var itemFields: ['identifier', 'name', 'scheme', 'host', 'pathDir', 'url'] + + + Column { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Item { + anchors.left: parent.left + anchors.right: parent.right + height: totalCount.height + id: headerTop + + Prop.PropButton { + id: refreshButton + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: "Refresh" + color: needFreshList ? global.colorOrangeAccent : global.fontColor + onPressed: { pullFreshValues() } + } + + GridLayout { + anchors.left: refreshButton.right + anchors.right: parent.right + id: headerCountLane + columns: 3 + + Item { + Layout.fillWidth: true + Prop.PropScalar { + id: itemCount + label: "Count" + object: root.cache + property: "numTotal" + integral: true + readOnly: true + } + } + Item { + Layout.fillWidth: true + Prop.PropScalar { + id: totalCount + label: "Count" + object: root.cache + property: "numTotal" + integral: true + readOnly: true + onSourceValueVarChanged: { updateItemListFromCache() } + } + } + Item { + Layout.fillWidth: true + Prop.PropScalar { + id: cachedCount + label: "Cached" + object: root.cache + property: "numCached" + integral: true + readOnly: true + onSourceValueVarChanged: { updateItemListFromCache() } + } + } + } + } + Item { + anchors.left: parent.left + anchors.right: parent.right + height: orderSelector.height + + Prop.PropText { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: 50 + id: orderSelectorLabel + text: "Sort by" + horizontalAlignment: Text.AlignHCenter + } + Prop.PropComboBox { + anchors.left: orderSelectorLabel.right + width: 80 + id: orderSelector + model: itemFields + currentIndex: 1 + + property var isSchemeVisible: (currentIndex == 2 || filterFieldSelector.currentIndex == 2) + property var isHostVisible: (currentIndex == 3 || filterFieldSelector.currentIndex == 3) + property var isPathDirVisible: (currentIndex == 4 || filterFieldSelector.currentIndex == 4) + property var isURLVisible: (currentIndex == 5 || filterFieldSelector.currentIndex == 5) + } + + Prop.PropTextField { + anchors.left: orderSelector.right + anchors.right: filterFieldSelector.left + id: nameFilter + placeholderText: qsTr("Filter by " + itemFields[filterFieldSelector.currentIndex] + "...") + Layout.fillWidth: true + } + Prop.PropComboBox { + anchors.right: parent.right + id: filterFieldSelector + model: itemFields + currentIndex: 1 + width: 80 + opacity: (nameFilter.text.length > 0) ? 1.0 : 0.5 + } + } + } + + Component { + id: resouceItemDelegate + MouseArea { + id: dragArea + property bool held: false + anchors { left: parent.left; right: parent.right } + height: item.height + onPressed: {held = true} + onReleased: {held = false} + onDoubleClicked: { requestResourceDetails(model.url) } + Rectangle { + id: item + width: parent.width + height: global.slimHeight + color: dragArea.held ? global.colorBackHighlight : (index % 2 ? global.colorBackShadow : global.colorBack) + Row { + id: itemRow + anchors.verticalCenter : parent.verticalCenter + Prop.PropText { + id: itemIdentifier + text: model.identifier + width: 30 + } + Prop.PropSplitter { + visible: orderSelector.isSchemeVisible + size:8 + } + Prop.PropLabel { + visible: orderSelector.isSchemeVisible + text: model.scheme + width: 30 + } + Prop.PropSplitter { + visible: orderSelector.isHostVisible + size:8 + } + Prop.PropLabel { + visible: orderSelector.isHostVisible + text: model.host + width: 150 + } + Prop.PropSplitter { + visible: orderSelector.isPathDirVisible + size:8 + } + Prop.PropLabel { + visible: orderSelector.isPathDirVisible + text: model.pathDir + } + Prop.PropSplitter { + size:8 + } + Prop.PropLabel { + visible: !orderSelector.isURLVisible + text: model.name + } + Prop.PropLabel { + visible: orderSelector.isURLVisible + text: model.url + } + } + } + + } + } + + Skit.SortFilterModel { + id: visualModel + model: ListModel {} + + property int sortOrder: orderSelector.currentIndex + + property var lessThanArray: [ + function(left, right) { return left.index < right.index }, + function(left, right) { return left.name < right.name }, + function(left, right) { return left.scheme < right.scheme }, + function(left, right) { return left.host < right.host }, + function(left, right) { return left.pathDir < right.pathDir }, + function(left, right) { return left.url < right.url } + ]; + lessThan: lessThanArray[sortOrder] + + property int filterField: filterFieldSelector.currentIndex + onFilterFieldChanged: { refreshFilter() } + property var textFilter: nameFilter.text + onTextFilterChanged: { refreshFilter() } + + function filterToken(itemWord, token) { return (itemWord.search(token) > -1) } + property var acceptItemArray: [ + function(item) { return true }, + function(item) { return filterToken(item.identifier.toString(), textFilter) }, + function(item) { return filterToken(item.name, textFilter) }, + function(item) { return filterToken(item.scheme, textFilter) }, + function(item) { return filterToken(item.host, textFilter) }, + function(item) { return filterToken(item.pathDir, textFilter) }, + function(item) { return filterToken(item.url, textFilter) } + ] + + function refreshFilter() { + //console.log("refreshFilter! token = " + textFilter + " field = " + filterField) + acceptItem = acceptItemArray[(textFilter.length != 0) * + (1 + filterField)] + } + + delegate: resouceItemDelegate + } + + ListView { + anchors.top: header.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + clip: true + + id: listView + model: visualModel + } +} diff --git a/scripts/developer/utilities/cache/cash/ResourceInspector.qml b/scripts/developer/utilities/cache/cash/ResourceInspector.qml new file mode 100644 index 0000000000..099eb735d0 --- /dev/null +++ b/scripts/developer/utilities/cache/cash/ResourceInspector.qml @@ -0,0 +1,58 @@ +// +// ResourceInspector.qml +// +// Created by Sam Gateau on 2019-09-24 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.12 + +import "../../lib/prop" as Prop + +Item { + id: root; + Prop.Global { id: global } + + anchors.fill: parent.fill + property var cache: {} + property string cacheResourceName: "" + + function fromScript(message) { + switch (message.method) { + case "inspectResource": + inspectResource(message.params.url, message.params.semantic) + break; + } + } + + function inspectResource(url, semantic) { + console.log("inspectResource :" + url + " as " + semantic) + info.text = "url: " + url + "\nsemantic: " + semantic + "\n"; + + if (semantic == "Texture") { + var res = TextureCache.prefetch(url, 0) + info.text += JSON.stringify(res); + } else if (semantic == "Model") { + var res = ModelCache.prefetch(url) + info.text += JSON.stringify(res); + } + } + + TextEdit { + id: info + anchors.fill: parent + text: "Click an object to get material JSON" + width: root.width + font.pointSize: 10 + color: "#FFFFFF" + readOnly: true + selectByMouse: true + wrapMode: Text.WordWrap + } +} + diff --git a/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml b/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml new file mode 100644 index 0000000000..26b043469e --- /dev/null +++ b/scripts/developer/utilities/cache/cash/SoundCacheInspector.qml @@ -0,0 +1,21 @@ +// +// SoundCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +import "../../lib/prop" as Prop + +ResourceCacheInspector { + id: root; + anchors.fill: parent.fill + cache: SoundCache + cacheResourceName: "Sound" +} diff --git a/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml b/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml new file mode 100644 index 0000000000..9bfd663a4e --- /dev/null +++ b/scripts/developer/utilities/cache/cash/TextureCacheInspector.qml @@ -0,0 +1,21 @@ +// +// TextureCacheInspector.qml +// +// Created by Sam Gateau on 2019-09-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 + +import "../../lib/prop" as Prop + +ResourceCacheInspector { + id: root; + anchors.fill: parent.fill + cache: TextureCache + cacheResourceName: "Texture" +} diff --git a/scripts/developer/utilities/cache/cash/qmldir b/scripts/developer/utilities/cache/cash/qmldir new file mode 100644 index 0000000000..8793a6b8f5 --- /dev/null +++ b/scripts/developer/utilities/cache/cash/qmldir @@ -0,0 +1,6 @@ +ResourceCacheInspector 1.0 ResourceCacheInspector.qml +TextureCacheInspector 1.0 TextureCacheInspector.qml +MaterialCacheInspector 1.0 MaterialCacheInspector.qml +ModelCacheInspector 1.0 ModelCacheInspector.qml +AnimationCacheInspector 1.0 AnimationCacheInspector.qml +SoundCacheInspector 1.0 SoundCacheInspector.qml \ No newline at end of file diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index e2576fe783..a935163bd9 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -91,12 +91,12 @@ Rectangle { } } - Original.ScrollView { + ListView { anchors.fill: parent - ListView { - id: theView - model: jobsModel - delegate: objRecursiveDelegate - } + + id: theView + model: jobsModel + delegate: objRecursiveDelegate } + } \ No newline at end of file diff --git a/scripts/developer/utilities/lib/prop/PropEnum.qml b/scripts/developer/utilities/lib/prop/PropEnum.qml index 2268b21e34..97c385281d 100644 --- a/scripts/developer/utilities/lib/prop/PropEnum.qml +++ b/scripts/developer/utilities/lib/prop/PropEnum.qml @@ -16,6 +16,7 @@ PropItem { id: root property alias enums : valueCombo.model + property alias currentIndex : valueCombo.currentIndex PropComboBox { id: valueCombo diff --git a/scripts/developer/utilities/lib/prop/PropItem.qml b/scripts/developer/utilities/lib/prop/PropItem.qml index 6d2f4c11ad..3779e7f4a9 100644 --- a/scripts/developer/utilities/lib/prop/PropItem.qml +++ b/scripts/developer/utilities/lib/prop/PropItem.qml @@ -25,19 +25,16 @@ Item { // function defaultGet() { var v = root.object[root.property]; return v; } function defaultSet(value) { root.object[root.property] = value; } - // function defaultSetReadOnly(value) { log ( "read only " + property + ", NOT setting to " + value); } - // function defaultSetReadOnly(value) {} - // property var valueVarSetter: (root.readOnly ? defaultSetReadOnly : defaultSet) - property var valueVarSetter: defaultSet + function defaultSetReadOnly(value) {} + + property var valueVarSetter: (readOnly ? defaultSetReadOnly : defaultSet) property var valueVarGetter: defaultGet // PropItem is stretching horizontally accross its parent // Fixed height + height: global.lineHeight anchors.left: parent.left anchors.right: parent.right - height: global.lineHeight - anchors.leftMargin: global.horizontalMargin - anchors.rightMargin: global.horizontalMargin // LabelControl And SplitterControl are on the left side of the PropItem property bool showLabel: true diff --git a/scripts/developer/utilities/lib/prop/PropScalar.qml b/scripts/developer/utilities/lib/prop/PropScalar.qml index ce89342997..3776f5c4bc 100644 --- a/scripts/developer/utilities/lib/prop/PropScalar.qml +++ b/scripts/developer/utilities/lib/prop/PropScalar.qml @@ -32,9 +32,7 @@ PropItem { property var sourceValueVar: root.valueVarGetter() function applyValueVarFromWidgets(value) { - if (!root.readOnly) { - root.valueVarSetter(value) - } + root.valueVarSetter(value) } PropLabel { @@ -58,6 +56,7 @@ PropItem { MouseArea{ id: mousearea + enabled: !root.readOnly anchors.fill: parent onDoubleClicked: { sliderControl.visible = !sliderControl.visible } } diff --git a/scripts/developer/utilities/lib/prop/qmldir b/scripts/developer/utilities/lib/prop/qmldir index e09785846d..99e721fb33 100644 --- a/scripts/developer/utilities/lib/prop/qmldir +++ b/scripts/developer/utilities/lib/prop/qmldir @@ -1,8 +1,10 @@ Module Prop Global 1.0 style/Global.qml PropText 1.0 style/PiText.qml +PropTextField 1.0 style/PiTextField.qml PropLabel 1.0 style/PiLabel.qml PropSplitter 1.0 style/PiSplitter.qml +PropButton 1.0 style/PiButton.qml PropComboBox 1.0 style/PiComboBox.qml PropCanvasIcon 1.0 style/PiCanvasIcon.qml PropCheckBox 1.0 style/PiCheckBox.qml diff --git a/scripts/developer/utilities/lib/prop/style/PiButton.qml b/scripts/developer/utilities/lib/prop/style/PiButton.qml new file mode 100644 index 0000000000..5469431d81 --- /dev/null +++ b/scripts/developer/utilities/lib/prop/style/PiButton.qml @@ -0,0 +1,35 @@ +// +// Prop/style/PiButton.qml +// +// Created by Sam Gateau on 17/09/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.6 +import QtQuick.Controls 2.1 + +Button { + Global { id: global } + id: control + text: "" + spacing: 0 + property alias color: theContentItem.color + + contentItem: PiText { + id: theContentItem + text: control.text + horizontalAlignment: Text.AlignHCenter + color: global.fontColor + } + + background: Rectangle { + color: control.down ? global.colorBackHighlight : global.colorBackShadow + opacity: enabled ? 1 : 0.3 + border.color: control.down ? global.colorBorderHighight : (control.hovered ? global.colorBorderHighight : global.colorBorderLight) + border.width: global.valueBorderWidth + radius: global.valueBorderRadius + } +} \ No newline at end of file diff --git a/scripts/developer/utilities/lib/prop/style/PiComboBox.qml b/scripts/developer/utilities/lib/prop/style/PiComboBox.qml index 92164fefc5..9430228eba 100644 --- a/scripts/developer/utilities/lib/prop/style/PiComboBox.qml +++ b/scripts/developer/utilities/lib/prop/style/PiComboBox.qml @@ -16,7 +16,9 @@ ComboBox { id: valueCombo height: global.slimHeight - + width: 120 + implicitHeight: global.slimHeight + // look flat: true @@ -51,8 +53,6 @@ ComboBox { } background: Rectangle { - implicitWidth: 120 - implicitHeight: 40 color: global.colorBack border.color: valueCombo.popup.visible ? global.colorBorderLighter : global.colorBorderLight border.width: global.valueBorderWidth diff --git a/scripts/developer/utilities/lib/prop/style/PiTextField.qml b/scripts/developer/utilities/lib/prop/style/PiTextField.qml new file mode 100644 index 0000000000..8dfb9d88ee --- /dev/null +++ b/scripts/developer/utilities/lib/prop/style/PiTextField.qml @@ -0,0 +1,31 @@ +// +// Prop/style/PiTextField.qml +// +// Created by Sam Gateau on 9/24/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +TextField { + id: control + Global { id: global } + implicitHeight: global.slimHeight + implicitWidth: 200 + + placeholderText: qsTr("Enter description") + + color: global.fontColor + font.pixelSize: global.fontSize + font.family: global.fontFamily + font.weight: global.fontWeight + + background: Rectangle { + color: (control.text.length > 0) ? global.colorBackHighlight : global.colorBackShadow + border.color: (control.text.length > 0) ? global.colorBorderHighight : "transparent" + } +} diff --git a/scripts/developer/utilities/render/luci/Page.js b/scripts/developer/utilities/lib/skit/Page.js similarity index 69% rename from scripts/developer/utilities/render/luci/Page.js rename to scripts/developer/utilities/lib/skit/Page.js index 06c9704abf..189c26044a 100644 --- a/scripts/developer/utilities/render/luci/Page.js +++ b/scripts/developer/utilities/lib/skit/Page.js @@ -10,11 +10,12 @@ "use strict"; (function() { -function Page(title, qmlurl, width, height, onViewCreated, onViewClosed) { +function Page(title, qmlurl, width, height, onFromQml, onViewCreated, onViewClosed) { this.title = title; - this.qml = qmlurl; + this.qmlurl = qmlurl; this.width = width; this.height = height; + this.onFromQml = onFromQml; this.onViewCreated = onViewCreated; this.onViewClosed = onViewClosed; @@ -30,6 +31,9 @@ Page.prototype.killView = function () { //this.window.closed.disconnect(function () { // this.killView(); //}); + if (this.onFromQml) { + this.window.fromQml.disconnect(this.onFromQml) + } this.window.close(); this.window = false; } @@ -39,12 +43,15 @@ Page.prototype.createView = function () { var that = this; if (!this.window) { print("Page: New window for page:" + this.title); - this.window = Desktop.createWindow(Script.resolvePath(this.qml), { + this.window = Desktop.createWindow(this.qmlurl, { title: this.title, presentationMode: Desktop.PresentationMode.NATIVE, size: {x: this.width, y: this.height} }); this.onViewCreated(this.window); + if (this.onFromQml) { + this.window.fromQml.connect(this.onFromQml) + } this.window.closed.connect(function () { that.killView(); that.onViewClosed(); @@ -53,11 +60,13 @@ Page.prototype.createView = function () { }; -Pages = function () { +Pages = function (relativePath) { + print(relativePath) + this._relativePath = relativePath this._pages = {}; }; -Pages.prototype.addPage = function (command, title, qmlurl, width, height, onViewCreated, onViewClosed) { +Pages.prototype.addPage = function (command, title, qmlurl, width, height, onFromQml, onViewCreated, onViewClosed) { if (onViewCreated === undefined) { // Workaround for bad linter onViewCreated = function(window) {}; @@ -66,7 +75,7 @@ Pages.prototype.addPage = function (command, title, qmlurl, width, height, onVie // Workaround for bad linter onViewClosed = function() {}; } - this._pages[command] = new Page(title, qmlurl, width, height, onViewCreated, onViewClosed); + this._pages[command] = new Page(title, Script.resolvePath(this._relativePath + qmlurl), width, height, onFromQml, onViewCreated, onViewClosed); }; Pages.prototype.open = function (command) { @@ -87,4 +96,12 @@ Pages.prototype.clear = function () { this._pages = {}; }; +Pages.prototype.sendTo = function (command, message) { + if (!this._pages[command]) { + print("Pages: unknown command = " + command); + return; + } + this._pages[command].window.sendToQml(message); +}; + }()); diff --git a/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml b/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml new file mode 100644 index 0000000000..08ad8d1dbd --- /dev/null +++ b/scripts/developer/utilities/lib/skit/qml/SortFilterModel.qml @@ -0,0 +1,93 @@ +import QtQuick 2.9 +import QtQml.Models 2.3 + +DelegateModel { + id: delegateModel + + property var lessThan: function(left, right) { return true; } + property var acceptItem: function(item) { return true; } + + function insertPosition(lessThanFunctor, item) { + var lower = 0 + var upper = visibleItems.count + while (lower < upper) { + var middle = Math.floor(lower + (upper - lower) / 2) + var result = lessThanFunctor(item.model, visibleItems.get(middle).model); + if (result) { + upper = middle + } else { + lower = middle + 1 + } + } + return lower + } + + function sortAndFilter(lessThanFunctor, acceptItemFunctor) { + while (unsortedItems.count > 0) { + var item = unsortedItems.get(0) + + if (acceptItemFunctor(item.model)) { + var index = insertPosition(lessThanFunctor, item) + + item.groups = ["items","visible"] + visibleItems.move(item.visibleIndex, index) + } else { + item.groups = ["items"] + } + } + } + + // Private bool to track when items changed and view is dirty + property bool itemsDirty: true + function update() { + console.log("SortFilterMode: update and sort and filter items !!" + items.count); + if (items.count > 0) { + items.setGroups(0, items.count, ["items","unsorted"]); + } + + sortAndFilter(lessThan, acceptItem) + itemsDirty = false; + itemsUpdated() + } + + signal itemsUpdated() + + function updateOnItemsChanged() { + itemsDirty = true; + if (isAutoUpdateOnChanged) { + update() + } + } + + property bool isAutoUpdateOnChanged: false + function setAutoUpdateOnChanged(enabled) { + isAutoUpdateOnChanged = enabled + if (enabled) { + update() + } + } + + function forceUpdate() { + update(); + } + items.onChanged: updateOnItemsChanged() + onLessThanChanged: update() + onAcceptItemChanged: update() + + groups: [ + DelegateModelGroup { + id: visibleItems + + name: "visible" + includeByDefault: false + }, + DelegateModelGroup { + id: unsortedItems + + name: "unsorted" + includeByDefault: false + } + ] + + filterOnGroup: "visible" +} \ No newline at end of file diff --git a/scripts/developer/utilities/lib/skit/qml/qmldir b/scripts/developer/utilities/lib/skit/qml/qmldir new file mode 100644 index 0000000000..14d11d998a --- /dev/null +++ b/scripts/developer/utilities/lib/skit/qml/qmldir @@ -0,0 +1 @@ +SortFilterModel 1.0 SortFilterModel.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/luci.js b/scripts/developer/utilities/render/luci.js index e2e5523ccd..3b832bdfb4 100644 --- a/scripts/developer/utilities/render/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -1,14 +1,13 @@ var MaterialInspector = Script.require('./materialInspector.js'); -var Page = Script.require('./luci/Page.js'); + +var Page = Script.require('../lib/skit/Page.js'); function openView() { - //window.closed.connect(function() { Script.stop(); }); + var pages = new Pages(Script.resolvePath(".")); - - var pages = new Pages(); function fromQml(message) { if (pages.open(message.method)) { return; @@ -17,12 +16,6 @@ function openView() { var luciWindow function openLuciWindow(window) { - if (luciWindow !== undefined) { - activeWindow.fromQml.disconnect(fromQml); - } - if (window !== undefined) { - window.fromQml.connect(fromQml); - } luciWindow = window; @@ -57,9 +50,6 @@ function openView() { } function closeLuciWindow() { - if (luciWindow !== undefined) { - activeWindow.fromQml.disconnect(fromQml); - } luciWindow = {}; Controller.mousePressEvent.disconnect(onMousePressEvent); @@ -68,10 +58,10 @@ function openView() { pages.clear(); } - pages.addPage('Luci', 'Luci', '../luci.qml', 300, 420, openLuciWindow, closeLuciWindow); - pages.addPage('openEngineInspectorView', 'Render Engine Inspector', '../engineInspector.qml', 300, 400); - pages.addPage('openEngineLODView', 'Render LOD', '../lod.qml', 300, 400); - pages.addPage('openMaterialInspectorView', 'Material Inspector', '../materialInspector.qml', 300, 400, MaterialInspector.setWindow, MaterialInspector.setWindow); + pages.addPage('Luci', 'Luci', 'luci.qml', 300, 420, fromQml, openLuciWindow, closeLuciWindow); + pages.addPage('openEngineInspectorView', 'Render Engine Inspector', 'engineInspector.qml', 300, 400); + pages.addPage('openEngineLODView', 'Render LOD', 'lod.qml', 300, 400); + pages.addPage('openMaterialInspectorView', 'Material Inspector', 'materialInspector.qml', 300, 400, null, MaterialInspector.setWindow, MaterialInspector.setWindow); pages.open('Luci'); diff --git a/scripts/developer/utilities/render/materialInspector.js b/scripts/developer/utilities/render/materialInspector.js index 98d9f769fb..8a7d5ad7dd 100644 --- a/scripts/developer/utilities/render/materialInspector.js +++ b/scripts/developer/utilities/render/materialInspector.js @@ -128,7 +128,7 @@ function fromQml(message) { var SELECT_LIST = "luci_materialInspector_SelectionList"; Selection.enableListHighlight(SELECT_LIST, { - outlineUnoccludedColor: { red: 255, green: 255, blue: 255 } + outlineUnoccludedColor: { red: 125, green: 255, blue: 225 } }); function setSelectedObject(id, type) { Selection.clearSelectedItemsList(SELECT_LIST); diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index ff0d7630e5..31e154d38c 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -51,6 +51,12 @@ void RenderThread::initialize(QWindow* window) { _backend = _gpuContext->getBackend(); _context.doneCurrent(); _context.moveToThread(_thread); + + if (!_presentPipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + _presentPipeline = gpu::Pipeline::create(program, state); + } #else auto size = window->size(); _extent = vk::Extent2D{ (uint32_t)size.width(), (uint32_t)size.height() }; @@ -169,15 +175,28 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { } #ifdef USE_GL + static gpu::BatchPointer batch = nullptr; + if (!batch) { + batch = std::make_shared<gpu::Batch>(); + batch->setPipeline(_presentPipeline); + batch->setFramebuffer(nullptr); + batch->setResourceTexture(0, frame->framebuffer->getRenderBuffer(0)); + batch->setViewportTransform(ivec4(uvec2(0), ivec2(windowSize.width(), windowSize.height()))); + batch->draw(gpu::TRIANGLE_STRIP, 4); + } + glDisable(GL_FRAMEBUFFER_SRGB); + _gpuContext->executeBatch(*batch); + + // Keep this raw gl code here for reference //glDisable(GL_FRAMEBUFFER_SRGB); //glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + /* glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBlitFramebuffer( 0, 0, fboSize.x, fboSize.y, 0, 0, windowSize.width(), windowSize.height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); - +*/ (void)CHECK_GL_ERROR(); _context.swapBuffers(); _context.doneCurrent(); diff --git a/tools/gpu-frame-player/src/RenderThread.h b/tools/gpu-frame-player/src/RenderThread.h index 09eef56623..b090e1737f 100644 --- a/tools/gpu-frame-player/src/RenderThread.h +++ b/tools/gpu-frame-player/src/RenderThread.h @@ -57,7 +57,7 @@ public: uint32_t _externalTexture{ 0 }; void move(const glm::vec3& v); glm::mat4 _correction; - + gpu::PipelinePointer _presentPipeline; void resize(const QSize& newSize); void setup() override;