mirror of
https://github.com/overte-org/overte.git
synced 2025-04-14 07:47:30 +02:00
Merge pull request #7719 from zzmp/feat/resource-prefetch
Add prefetching to exposed caches
This commit is contained in:
commit
49d02b802a
18 changed files with 570 additions and 161 deletions
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<AnimClip>("prefetch", url, 0, 0, 1.0, false, false);
|
||||
_prefetchedAnimations.push_back(clipNode);
|
||||
}
|
||||
|
||||
void Rig::destroyAnimGraph() {
|
||||
_animSkeleton.reset();
|
||||
_animLoader.reset();
|
||||
|
|
|
@ -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<QString, AnimNode::Pointer> _origRoleAnimations;
|
||||
std::vector<AnimNode::Pointer> _prefetchedAnimations;
|
||||
|
||||
bool _lastEnableInverseKinematics { true };
|
||||
bool _enableInverseKinematics { true };
|
||||
|
|
|
@ -380,7 +380,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_pendingAmbientTexture = false;
|
||||
_ambientTexture.clear();
|
||||
} else {
|
||||
_ambientTexture = textureCache->getTexture(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_ptr<ZoneEntityIt
|
|||
_skyboxTexture.clear();
|
||||
} else {
|
||||
// Update the Texture of the Skybox with the one pointed by this zone
|
||||
_skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE);
|
||||
_skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), NetworkTexture::CUBE_TEXTURE);
|
||||
_pendingSkyboxTexture = true;
|
||||
|
||||
if (_skyboxTexture && _skyboxTexture->isLoaded()) {
|
||||
|
|
|
@ -237,13 +237,14 @@ ModelCache::ModelCache() {
|
|||
|
||||
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||
bool delayLoad, const void* extra) {
|
||||
const GeometryExtra* geometryExtra = static_cast<const GeometryExtra*>(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<const GeometryExtra*>(extra);
|
||||
auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash();
|
||||
auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl();
|
||||
resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl);
|
||||
}
|
||||
|
||||
return QSharedPointer<Resource>(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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Type>();
|
||||
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<NetworkTexture>();
|
||||
}
|
||||
|
||||
|
||||
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<Resource> TextureCache::createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
|
||||
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->type, textureExtra->content),
|
||||
auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE;
|
||||
auto content = textureExtra ? textureExtra->content : QByteArray();
|
||||
return QSharedPointer<Resource>(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)
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <QImage>
|
||||
#include <QMap>
|
||||
#include <QColor>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <ResourceCache.h>
|
||||
|
@ -25,79 +26,6 @@
|
|||
namespace gpu {
|
||||
class Batch;
|
||||
}
|
||||
class NetworkTexture;
|
||||
|
||||
typedef QSharedPointer<NetworkTexture> 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<TextureLoader> TextureLoaderFunc;
|
||||
protected:
|
||||
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& 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<TextureLoader>;
|
||||
|
||||
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<NetworkTexture>;
|
||||
|
||||
/// 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<Resource> createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& 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
|
||||
|
|
|
@ -119,6 +119,92 @@ QSharedPointer<Resource> 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<NodeList>();
|
||||
if (nodeList) {
|
||||
|
@ -219,7 +305,7 @@ QVariantList ResourceCache::getResourceList() {
|
|||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
void ResourceCache::setRequestLimit(int limit) {
|
||||
_requestLimit = limit;
|
||||
|
||||
|
@ -272,6 +358,7 @@ QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl&
|
|||
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), 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;
|
||||
|
|
|
@ -24,9 +24,12 @@
|
|||
#include <QtCore/QWeakPointer>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
#include <QtCore/QQueue>
|
||||
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
#include <QScriptEngine>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#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<QWeakPointer<Resource>> _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<Resource>& 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> _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<QObject>& value, uint seed = 0);
|
||||
|
|
|
@ -270,6 +270,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<ScriptableResourceRawPtr>(resource),
|
||||
QScriptEngine::ScriptOwnership);
|
||||
return object;
|
||||
}
|
||||
|
||||
static void scriptableResourceFromScriptValue(const QScriptValue& value, ScriptableResourceRawPtr& resource) {
|
||||
resource = static_cast<ScriptableResourceRawPtr>(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<ScriptableResource::State>();
|
||||
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 +369,16 @@ void ScriptEngine::init() {
|
|||
registerGlobalObject("Vec3", &_vec3Library);
|
||||
registerGlobalObject("Mat4", &_mat4Library);
|
||||
registerGlobalObject("Uuid", &_uuidLibrary);
|
||||
registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
|
||||
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
|
||||
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);
|
||||
|
||||
// Scriptable cache access
|
||||
auto resourcePrototype = createScriptableResourcePrototype(this);
|
||||
globalObject().setProperty("Resource", resourcePrototype);
|
||||
setDefaultPrototype(qMetaTypeId<ScriptableResource*>(), resourcePrototype);
|
||||
qScriptRegisterMetaType(this, scriptableResourceToScriptValue, scriptableResourceFromScriptValue);
|
||||
|
||||
// constants
|
||||
globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));
|
||||
|
||||
|
@ -793,6 +840,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<QTimer*>(sender());
|
||||
CallbackData timerData = _timerFunctionMap.value(callingTimer);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -20,8 +20,6 @@ for (i = 0; i < l; i++) {
|
|||
print(roles[i]);
|
||||
}
|
||||
|
||||
MyAvatar.prefetchAnimation(THE_BIRD_RIGHT_URL);
|
||||
|
||||
// replace point animations with the bird!
|
||||
MyAvatar.overrideRoleAnimation("rightHandPointIntro", THE_BIRD_RIGHT_URL, 30, false, 0, 12);
|
||||
MyAvatar.overrideRoleAnimation("rightHandPointHold", THE_BIRD_RIGHT_URL, 30, false, 12, 12);
|
||||
|
|
99
scripts/developer/tests/scriptableResource/lib.js
Normal file
99
scripts/developer/tests/scriptableResource/lib.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// lib.js
|
||||
// scripts/developer/tests/scriptableResource
|
||||
//
|
||||
// Created by Zach Pomerantz on 4/20/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Preloads textures to play a simple movie, plays it, and frees those textures.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var NUM_FRAMES = 158; // 158 available
|
||||
var FRAME_RATE = 30; // 30 default
|
||||
|
||||
function getFrame(callback) {
|
||||
// A model exported from blender with a texture named 'Picture' on one face.
|
||||
var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx";
|
||||
|
||||
var model = ModelCache.prefetch(FRAME_URL);
|
||||
if (model.state === Resource.State.FINISHED) {
|
||||
makeFrame(Resource.State.FINISHED);
|
||||
} else {
|
||||
model.stateChanged.connect(makeFrame);
|
||||
}
|
||||
|
||||
function makeFrame(state) {
|
||||
if (state == Resource.State.FAILED) { throw "Failed to load frame"; }
|
||||
if (state != Resource.State.FINISHED) { return; }
|
||||
|
||||
var pictureFrameProperties = {
|
||||
name: 'scriptableResourceTest Picture Frame',
|
||||
type: 'Model',
|
||||
position: getPosition(),
|
||||
modelURL: FRAME_URL,
|
||||
dynamic: true,
|
||||
};
|
||||
|
||||
callback(Entities.addEntity(pictureFrameProperties));
|
||||
}
|
||||
|
||||
function getPosition() {
|
||||
// Always put it 5 meters in front of you
|
||||
var position = MyAvatar.position;
|
||||
var yaw = MyAvatar.bodyYaw + MyAvatar.getHeadFinalYaw();
|
||||
var rads = (yaw / 180) * Math.PI;
|
||||
|
||||
position.y += 0.5;
|
||||
position.x += - 5 * Math.sin(rads);
|
||||
position.z += - 5 * Math.cos(rads);
|
||||
|
||||
print(JSON.stringify(position));
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
function prefetch(callback) {
|
||||
// A folder full of individual frames.
|
||||
var MOVIE_URL = "http://hifi-content.s3.amazonaws.com/james/vidtest/";
|
||||
|
||||
var frames = [];
|
||||
|
||||
var numLoading = 0;
|
||||
for (var i = 1; i <= NUM_FRAMES; ++i) {
|
||||
var padded = pad(i, 3);
|
||||
var filepath = MOVIE_URL + padded + '.jpg';
|
||||
var texture = TextureCache.prefetch(filepath);
|
||||
frames.push(texture);
|
||||
if (!texture.state == Resource.State.FINISHED) {
|
||||
numLoading++;
|
||||
texture.stateChanged.connect(function(state) {
|
||||
if (state == Resource.State.FAILED || state == Resource.State.FINISHED) {
|
||||
--numLoading;
|
||||
if (!numLoading) { callback(frames); }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!numLoading) { callback(frames); }
|
||||
|
||||
function pad(num, size) { // left-pad num with zeros until it is size digits
|
||||
var s = num.toString();
|
||||
while (s.length < size) { s = "0" + s; }
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
function play(model, frames, callback) {
|
||||
var frame = 0;
|
||||
var movieInterval = Script.setInterval(function() {
|
||||
Entities.editEntity(model, { textures: JSON.stringify({ Picture: frames[frame].url }) });
|
||||
if (++frame >= frames.length) {
|
||||
Script.clearInterval(movieInterval);
|
||||
callback();
|
||||
}
|
||||
}, 1000 / FRAME_RATE);
|
||||
}
|
||||
|
42
scripts/developer/tests/scriptableResource/movieTest.js
Normal file
42
scripts/developer/tests/scriptableResource/movieTest.js
Normal file
|
@ -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); });
|
33
scripts/developer/tests/scriptableResource/prefetchTest.js
Normal file
33
scripts/developer/tests/scriptableResource/prefetchTest.js
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue