Merge branch 'master' of https://github.com/highfidelity/hifi into hand-controller-pointer

This commit is contained in:
howard-stearns 2016-05-03 14:03:10 -07:00
commit adb11c9266
42 changed files with 888 additions and 313 deletions

View file

@ -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");

View file

@ -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.

View file

@ -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();

View file

@ -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 };

View file

@ -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()) {

View file

@ -54,6 +54,9 @@ public:
virtual bool lifetimeIsOver() { return false; }
virtual quint64 getExpires() { return 0; }
virtual bool isMine() { return _isMine; }
virtual void setIsMine(bool value) { _isMine = value; }
bool locallyAddedButNotYetReceived = false;
virtual bool shouldSuppressLocationEdits() { return false; }
@ -89,6 +92,7 @@ protected:
QUuid _id;
EntityActionType _type;
bool _active { false };
bool _isMine { false }; // did this interface create / edit this action?
};

View file

@ -658,6 +658,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (_simulationOwner.set(newSimOwner)) {
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
somethingChanged = true;
// recompute weOwnSimulation so that if this is the packet that tells use we are the owner,
// we ignore the physics changes from this packet.
weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
}
}
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
@ -1702,6 +1705,7 @@ bool EntityItem::updateAction(EntitySimulation* simulation, const QUuid& actionI
success = action->updateArguments(arguments);
if (success) {
action->setIsMine(true);
serializeActions(success, _allActionsDataCache);
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
} else {
@ -1808,7 +1812,9 @@ void EntityItem::deserializeActionsInternal() {
EntityActionPointer action = _objectActions[actionID];
// TODO: make sure types match? there isn't currently a way to
// change the type of an existing action.
action->deserialize(serializedAction);
if (!action->isMine()) {
action->deserialize(serializedAction);
}
action->locallyAddedButNotYetReceived = false;
updated << actionID;
} else {

View file

@ -830,6 +830,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
if (!action) {
return false;
}
action->setIsMine(true);
success = entity->addAction(simulation, action);
entity->grabSimulationOwnership();
return false; // Physics will cause a packet to be sent, so don't send from here.

View file

@ -49,7 +49,7 @@ public:
const rgbColor& getColor() const { return _color; }
xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; }
glm::vec3 getColorRGB() const { return ColorUtils::toLinearVec3(toGlm(getXColor())); }
glm::vec3 getColorRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(getXColor())); }
static const xColor DEFAULT_COLOR;
void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); }
@ -62,17 +62,17 @@ public:
bool _isColorStartInitialized = false;
void setColorStart(const xColor& colorStart) { _colorStart = colorStart; _isColorStartInitialized = true; }
xColor getColorStart() const { return _isColorStartInitialized ? _colorStart : getXColor(); }
glm::vec3 getColorStartRGB() const { return _isColorStartInitialized ? ColorUtils::toLinearVec3(toGlm(_colorStart)) : getColorRGB(); }
glm::vec3 getColorStartRGB() const { return _isColorStartInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_colorStart)) : getColorRGB(); }
bool _isColorFinishInitialized = false;
void setColorFinish(const xColor& colorFinish) { _colorFinish = colorFinish; _isColorFinishInitialized = true; }
xColor getColorFinish() const { return _isColorFinishInitialized ? _colorFinish : getXColor(); }
glm::vec3 getColorFinishRGB() const { return _isColorStartInitialized ? ColorUtils::toLinearVec3(toGlm(_colorFinish)) : getColorRGB(); }
glm::vec3 getColorFinishRGB() const { return _isColorStartInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_colorFinish)) : getColorRGB(); }
static const xColor DEFAULT_COLOR_SPREAD;
void setColorSpread(const xColor& colorSpread) { _colorSpread = colorSpread; }
xColor getColorSpread() const { return _colorSpread; }
glm::vec3 getColorSpreadRGB() const { return ColorUtils::toLinearVec3(toGlm(_colorSpread)); }
glm::vec3 getColorSpreadRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(_colorSpread)); }
static const float MAXIMUM_ALPHA;
static const float MINIMUM_ALPHA;

View file

@ -11,9 +11,14 @@
<@if not GPU_COLOR_SLH@>
<@def GPU_COLOR_SLH@>
float sRGBFloatToLinear(float value) {
const float SRGB_ELBOW = 0.04045;
return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4);
}
vec3 colorToLinearRGB(vec3 srgb) {
const float GAMMA_22 = 2.2;
return pow(srgb, vec3(GAMMA_22));
return vec3(sRGBFloatToLinear(srgb.r), sRGBFloatToLinear(srgb.g), sRGBFloatToLinear(srgb.b));
}
vec4 colorToLinearRGBA(vec4 srgba) {

View file

@ -11,6 +11,7 @@
using namespace gpu;
const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA };
const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA };
const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA };
const Element Element::VEC2F_UV{ VEC2, FLOAT, UV };
const Element Element::VEC2F_XY{ VEC2, FLOAT, XY };

View file

@ -245,6 +245,7 @@ public:
}
static const Element COLOR_RGBA_32;
static const Element COLOR_SRGBA_32;
static const Element VEC4F_COLOR_RGBA;
static const Element VEC2F_UV;
static const Element VEC2F_XY;

View file

@ -18,6 +18,8 @@
#include "GPULogging.h"
#include "Context.h"
#include "ColorUtils.h"
using namespace gpu;
static int TexturePointerMetaTypeId = qRegisterMetaType<TexturePointer>();
@ -637,18 +639,6 @@ void SphericalHarmonics::assignPreset(int p) {
}
}
glm::vec3 sRGBToLinear(glm::vec3& color) {
const float GAMMA_CORRECTION = 2.2f;
return glm::pow(color, glm::vec3(GAMMA_CORRECTION));
}
glm::vec3 linearTosRGB(glm::vec3& color) {
const float GAMMA_CORRECTION_INV = 1.0f / 2.2f;
return glm::pow(color, glm::vec3(GAMMA_CORRECTION_INV));
}
// Originial code for the Spherical Harmonics taken from "Sun and Black Cat- Igor Dykhta (igor dykhta email) <20> 2007-2014 "
void sphericalHarmonicsAdd(float * result, int order, const float * inputA, const float * inputB) {
const int numCoeff = order * order;
@ -803,7 +793,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT);
// Gamma correct
clr = sRGBToLinear(clr);
clr = ColorUtils::sRGBToLinearVec3(clr);
// scale color and add to previously accumulated coefficients
sphericalHarmonicsScale(shBuffB.data(), order,

View file

@ -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);

View file

@ -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,

View file

@ -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)
{

View file

@ -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

View file

@ -109,10 +109,15 @@ SphericalHarmonics getLightAmbientSphere(Light l) {
return l._ambientSphere;
}
bool getLightHasAmbientMap(Light l) {
return l._control.x > 0;
}
float getLightAmbientMapNumMips(Light l) {
return l._control.x;
}
<@if GPU_FEATURE_PROFILE == GPU_CORE @>
uniform lightBuffer {
Light light;

View file

@ -65,7 +65,7 @@ Material::~Material() {
void Material::setEmissive(const Color& emissive, bool isSRGB) {
_key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f))));
_schemaBuffer.edit<Schema>()._key = (uint32) _key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._emissive = (isSRGB ? ColorUtils::toLinearVec3(emissive) : emissive);
_schemaBuffer.edit<Schema>()._emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive);
}
void Material::setOpacity(float opacity) {
@ -77,7 +77,7 @@ void Material::setOpacity(float opacity) {
void Material::setAlbedo(const Color& albedo, bool isSRGB) {
_key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f))));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._albedo = (isSRGB ? ColorUtils::toLinearVec3(albedo) : albedo);
_schemaBuffer.edit<Schema>()._albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo);
}
void Material::setRoughness(float roughness) {
@ -89,7 +89,7 @@ void Material::setRoughness(float roughness) {
void Material::setFresnel(const Color& fresnel, bool isSRGB) {
//_key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f))));
_schemaBuffer.edit<Schema>()._fresnel = (isSRGB ? ColorUtils::toLinearVec3(fresnel) : fresnel);
_schemaBuffer.edit<Schema>()._fresnel = (isSRGB ? ColorUtils::sRGBToLinearVec3(fresnel) : fresnel);
}
void Material::setMetallic(float metallic) {

View file

@ -245,16 +245,16 @@ public:
const MaterialKey& getKey() const { return _key; }
void setEmissive(const Color& emissive, bool isSRGB = true);
Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get<Schema>()._emissive) : _schemaBuffer.get<Schema>()._emissive); }
Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get<Schema>()._emissive) : _schemaBuffer.get<Schema>()._emissive); }
void setOpacity(float opacity);
float getOpacity() const { return _schemaBuffer.get<Schema>()._opacity; }
void setAlbedo(const Color& albedo, bool isSRGB = true);
Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get<Schema>()._albedo) : _schemaBuffer.get<Schema>()._albedo); }
Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get<Schema>()._albedo) : _schemaBuffer.get<Schema>()._albedo); }
void setFresnel(const Color& fresnel, bool isSRGB = true);
Color getFresnel(bool SRGB = true) const { return (SRGB ? ColorUtils::toGamma22Vec3(_schemaBuffer.get<Schema>()._fresnel) : _schemaBuffer.get<Schema>()._fresnel); }
Color getFresnel(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get<Schema>()._fresnel) : _schemaBuffer.get<Schema>()._fresnel); }
void setMetallic(float metallic);
float getMetallic() const { return _schemaBuffer.get<Schema>()._metallic; }

View file

@ -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;

View file

@ -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);

View file

@ -25,6 +25,43 @@ vec4 evalSkyboxLight(vec3 direction, float lod) {
}
<@endfunc@>
<@func declareEvalGlobalSpecularIrradiance(supportAmbientSphere, supportAmbientMap, supportIfAmbientMapElseAmbientSphere)@>
vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) {
return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5);
}
<@if supportAmbientMap@>
<$declareSkyboxMap()$>
<@endif@>
vec3 evalGlobalSpecularIrradiance(Light light, vec3 fragEyeDir, vec3 fragNormal, float roughness, vec3 fresnel, float obscurance) {
vec3 direction = -reflect(fragEyeDir, fragNormal);
vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness);
vec3 specularLight;
<@if supportIfAmbientMapElseAmbientSphere@>
if (getLightHasAmbientMap(light))
<@endif@>
<@if supportAmbientMap@>
{
float levels = getLightAmbientMapNumMips(light);
float lod = min(floor((roughness) * levels), levels);
specularLight = evalSkyboxLight(direction, lod).xyz;
}
<@endif@>
<@if supportIfAmbientMapElseAmbientSphere@>
else
<@endif@>
<@if supportAmbientSphere@>
{
specularLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz;
}
<@endif@>
return specularLight * ambientFresnel * getLightAmbientIntensity(light);
}
<@endfunc@>
<@func prepareGlobalLight()@>
// prepareGlobalLight
@ -45,11 +82,6 @@ vec4 evalSkyboxLight(vec3 direction, float lod) {
color += emissive;
<@endfunc@>
<@func declareAmbientFresnel()@>
vec3 fresnelSchlickAmbient(vec3 fresnelColor, vec3 lightDir, vec3 halfDir, float gloss) {
return fresnelColor + (max(vec3(gloss), fresnelColor) - fresnelColor) * pow(1.0 - clamp(dot(lightDir, halfDir), 0.0, 1.0), 5);
}
<@endfunc@>
<@func declareEvalAmbientGlobalColor()@>
vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) {
@ -60,7 +92,8 @@ vec3 evalAmbientGlobalColor(mat4 invViewMat, float shadowAttenuation, float obsc
<@endfunc@>
<@func declareEvalAmbientSphereGlobalColor()@>
<$declareAmbientFresnel()$>
<$declareEvalGlobalSpecularIrradiance(1, 0, 0)$>
vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) {
<$prepareGlobalLight()$>
@ -69,18 +102,15 @@ vec3 evalAmbientSphereGlobalColor(mat4 invViewMat, float shadowAttenuation, floa
color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light);
// Specular highlight from ambient
vec3 direction = -reflect(fragEyeDir, fragNormal);
vec3 skyboxLight = evalSphericalLight(getLightAmbientSphere(light), direction).xyz;
vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness);
color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light);
vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance);
color += specularLighting;
return color;
}
<@endfunc@>
<@func declareEvalSkyboxGlobalColor()@>
<$declareSkyboxMap()$>
<$declareAmbientFresnel()$>
<$declareEvalGlobalSpecularIrradiance(0, 1, 0)$>
vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness) {
<$prepareGlobalLight()$>
@ -89,13 +119,8 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu
color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light);
// Specular highlight from ambient
vec3 direction = -reflect(fragEyeDir, fragNormal);
float levels = getLightAmbientMapNumMips(light);
float lod = min(floor((roughness) * levels), levels);
vec4 skyboxLight = evalSkyboxLight(direction, lod);
vec3 ambientFresnel = fresnelSchlickAmbient(fresnel, fragEyeDir, fragNormal, 1 - roughness);
color += ambientFresnel * skyboxLight.rgb * obscurance * getLightAmbientIntensity(light);
vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance);
color += specularLighting;
return color;
}
@ -125,4 +150,26 @@ vec3 evalLightmappedColor(mat4 invViewMat, float shadowAttenuation, float obscur
}
<@endfunc@>
<@func declareEvalGlobalLightingAlphaBlended()@>
<$declareEvalGlobalSpecularIrradiance(1, 1, 1)$>
vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, float obscurance, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 emissive, float roughness, float opacity) {
<$prepareGlobalLight()$>
// Diffuse from ambient
color += (1 - metallic) * albedo * evalSphericalLight(getLightAmbientSphere(light), fragNormal).xyz * obscurance * getLightAmbientIntensity(light);
// Specular highlight from ambient
vec3 specularLighting = evalGlobalSpecularIrradiance(light, fragEyeDir, fragNormal, roughness, fresnel, obscurance);
color += specularLighting / opacity;
return color;
}
<@endfunc@>
<@endif@>

View file

@ -506,9 +506,9 @@ GeometryCache::GeometryCache() :
std::make_shared<render::ShapePipeline>(getSimplePipeline(), nullptr,
[](const render::ShapePipeline&, gpu::Batch& batch) {
// Set the defaults needed for a simple program
batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
}
);
@ -1736,11 +1736,11 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool cul
// If not textured, set a default albedo map
if (!textured) {
batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
}
// Set a default normal map
batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
}
@ -1758,7 +1758,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool culled
_emissiveShader = gpu::Shader::createProgram(VS, PSEmissive);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::NORMAL_FITTING_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
gpu::Shader::makeProgram(*_simpleShader, slotBindings);
gpu::Shader::makeProgram(*_emissiveShader, slotBindings);
});

View file

@ -138,8 +138,8 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
auto textureCache = DependencyManager::get<TextureCache>();
batch.setUniformBuffer(ShapePipeline::Slot::MATERIAL_BUFFER, _drawMaterial->getSchemaBuffer());
batch.setUniformBuffer(ShapePipeline::Slot::TEXMAPARRAY_BUFFER, _drawMaterial->getTexMapArrayBuffer());
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, _drawMaterial->getSchemaBuffer());
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::TEXMAPARRAY, _drawMaterial->getTexMapArrayBuffer());
auto materialKey = _drawMaterial->getKey();
auto textureMaps = _drawMaterial->getTextureMaps();
@ -148,68 +148,68 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
if (materialKey.isAlbedoMap()) {
auto albedoMap = textureMaps[model::MaterialKey::ALBEDO_MAP];
if (albedoMap && albedoMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, albedoMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, albedoMap->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getGrayTexture());
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getGrayTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO_MAP, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::ALBEDO, textureCache->getWhiteTexture());
}
// Roughness map
if (materialKey.isRoughnessMap()) {
auto roughnessMap = textureMaps[model::MaterialKey::ROUGHNESS_MAP];
if (roughnessMap && roughnessMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, roughnessMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, roughnessMap->getTextureView());
// texcoord are assumed to be the same has albedo
} else {
batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::ROUGHNESS_MAP, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::ROUGHNESS, textureCache->getWhiteTexture());
}
// Normal map
if (materialKey.isNormalMap()) {
auto normalMap = textureMaps[model::MaterialKey::NORMAL_MAP];
if (normalMap && normalMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, normalMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, normalMap->getTextureView());
// texcoord are assumed to be the same has albedo
} else {
batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, textureCache->getBlueTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, textureCache->getBlueTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::NORMAL_MAP, nullptr);
batch.setResourceTexture(ShapePipeline::Slot::MAP::NORMAL, nullptr);
}
// Metallic map
if (materialKey.isMetallicMap()) {
auto specularMap = textureMaps[model::MaterialKey::METALLIC_MAP];
if (specularMap && specularMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, specularMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, specularMap->getTextureView());
// texcoord are assumed to be the same has albedo
} else {
batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, textureCache->getBlackTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, textureCache->getBlackTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::METALLIC_MAP, nullptr);
batch.setResourceTexture(ShapePipeline::Slot::MAP::METALLIC, nullptr);
}
// Occlusion map
if (materialKey.isOcclusionMap()) {
auto specularMap = textureMaps[model::MaterialKey::OCCLUSION_MAP];
if (specularMap && specularMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, specularMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, specularMap->getTextureView());
// texcoord are assumed to be the same has albedo
} else {
batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, textureCache->getWhiteTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, textureCache->getWhiteTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::OCCLUSION_MAP, nullptr);
batch.setResourceTexture(ShapePipeline::Slot::MAP::OCCLUSION, nullptr);
}
// Emissive / Lightmap
@ -217,20 +217,20 @@ void MeshPartPayload::bindMaterial(gpu::Batch& batch, const ShapePipeline::Locat
auto lightmapMap = textureMaps[model::MaterialKey::LIGHTMAP_MAP];
if (lightmapMap && lightmapMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, lightmapMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, lightmapMap->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getGrayTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getGrayTexture());
}
} else if (materialKey.isEmissiveMap()) {
auto emissiveMap = textureMaps[model::MaterialKey::EMISSIVE_MAP];
if (emissiveMap && emissiveMap->isDefined()) {
batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, emissiveMap->getTextureView());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, emissiveMap->getTextureView());
} else {
batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, textureCache->getBlackTexture());
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, textureCache->getBlackTexture());
}
} else {
batch.setResourceTexture(ShapePipeline::Slot::EMISSIVE_LIGHTMAP_MAP, nullptr);
batch.setResourceTexture(ShapePipeline::Slot::MAP::EMISSIVE_LIGHTMAP, nullptr);
}
}
@ -474,9 +474,9 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline:
Transform transform;
if (state.clusterBuffer) {
if (canCauterize && _model->getCauterizeBones()) {
batch.setUniformBuffer(ShapePipeline::Slot::SKINNING_BUFFER, state.cauterizedClusterBuffer);
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.cauterizedClusterBuffer);
} else {
batch.setUniformBuffer(ShapePipeline::Slot::SKINNING_BUFFER, state.clusterBuffer);
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
}
} else {
if (canCauterize && _model->getCauterizeBones()) {

View file

@ -75,16 +75,16 @@ gpu::BufferView getDefaultMaterialBuffer() {
void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) {
// Set a default albedo map
batch.setResourceTexture(render::ShapePipeline::Slot::ALBEDO_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::ALBEDO,
DependencyManager::get<TextureCache>()->getWhiteTexture());
// Set a default normal map
batch.setResourceTexture(render::ShapePipeline::Slot::NORMAL_FITTING_MAP,
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
// Set a default material
if (pipeline.locations->materialBufferUnit >= 0) {
static const gpu::BufferView OPAQUE_SCHEMA_BUFFER = getDefaultMaterialBuffer();
batch.setUniformBuffer(ShapePipeline::Slot::MATERIAL_BUFFER, OPAQUE_SCHEMA_BUFFER);
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::MATERIAL, OPAQUE_SCHEMA_BUFFER);
}
}
@ -94,7 +94,7 @@ void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch) {
if (pipeline.locations->lightBufferUnit >= 0) {
DependencyManager::get<DeferredLightingEffect>()->setupKeyLightBatch(batch,
pipeline.locations->lightBufferUnit,
-1);
pipeline.locations->lightAmbientMapUnit);
}
}

View file

@ -15,7 +15,8 @@
<@include model/Material.slh@>
<@include DeferredGlobalLight.slh@>
<$declareEvalAmbientSphereGlobalColor()$>
<$declareEvalGlobalLightingAlphaBlended()$>
<@include gpu/Transform.slh@>
<$declareStandardCameraTransform()$>
@ -58,7 +59,7 @@ void main(void) {
TransformCamera cam = getTransformCamera();
_fragColor = vec4(evalAmbientSphereGlobalColor(
_fragColor = vec4(evalGlobalLightingAlphaBlended(
cam._viewInverse,
1.0,
1.0,
@ -67,6 +68,6 @@ void main(void) {
albedo,
metallic,
emissive,
roughness),
roughness, opacity),
opacity);
}

View file

@ -363,7 +363,7 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c
batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT));
// need the gamma corrected color here
glm::vec4 lrgba = glm::vec4(ColorUtils::toLinearVec3(glm::vec3(*color)), color->a);
glm::vec4 lrgba = ColorUtils::sRGBToLinearVec4(*color);
batch._glUniform4fv(_colorLoc, 1, (const float*)&lrgba);
batch.setInputFormat(_format);

View file

@ -51,17 +51,18 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program
void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state,
BatchSetter batchSetter) {
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::SKINNING_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::MATERIAL_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::TEXMAPARRAY_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::ALBEDO_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::ROUGHNESS_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::NORMAL_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::METALLIC_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::EMISSIVE_LIGHTMAP_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::OCCLUSION_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::LIGHT_BUFFER));
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING_MAP));
slotBindings.insert(gpu::Shader::Binding(std::string("skinClusterBuffer"), Slot::BUFFER::SKINNING));
slotBindings.insert(gpu::Shader::Binding(std::string("materialBuffer"), Slot::BUFFER::MATERIAL));
slotBindings.insert(gpu::Shader::Binding(std::string("texMapArrayBuffer"), Slot::BUFFER::TEXMAPARRAY));
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), Slot::MAP::ALBEDO));
slotBindings.insert(gpu::Shader::Binding(std::string("roughnessMap"), Slot::MAP::ROUGHNESS));
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), Slot::MAP::NORMAL));
slotBindings.insert(gpu::Shader::Binding(std::string("metallicMap"), Slot::MAP::METALLIC));
slotBindings.insert(gpu::Shader::Binding(std::string("emissiveMap"), Slot::MAP::EMISSIVE_LIGHTMAP));
slotBindings.insert(gpu::Shader::Binding(std::string("occlusionMap"), Slot::MAP::OCCLUSION));
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), Slot::BUFFER::LIGHT));
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT));
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), Slot::NORMAL_FITTING));
gpu::Shader::makeProgram(*program, slotBindings);
@ -77,6 +78,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p
locations->materialBufferUnit = program->getBuffers().findLocation("materialBuffer");
locations->texMapArrayBufferUnit = program->getBuffers().findLocation("texMapArrayBuffer");
locations->lightBufferUnit = program->getBuffers().findLocation("lightBuffer");
locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap");
ShapeKey key{filter._flags};
auto gpuPipeline = gpu::Pipeline::create(program, state);

View file

@ -193,18 +193,24 @@ class ShapePipeline {
public:
class Slot {
public:
static const int SKINNING_BUFFER = 2;
static const int MATERIAL_BUFFER = 3;
static const int TEXMAPARRAY_BUFFER = 4;
static const int ALBEDO_MAP = 0;
static const int NORMAL_MAP = 1;
static const int METALLIC_MAP = 2;
static const int EMISSIVE_LIGHTMAP_MAP = 3;
static const int ROUGHNESS_MAP = 4;
static const int OCCLUSION_MAP = 5;
enum BUFFER {
SKINNING = 2,
MATERIAL,
TEXMAPARRAY,
LIGHT
};
static const int LIGHT_BUFFER = 5;
static const int NORMAL_FITTING_MAP = 10;
enum MAP {
ALBEDO = 0,
NORMAL,
METALLIC,
EMISSIVE_LIGHTMAP,
ROUGHNESS,
OCCLUSION,
LIGHT_AMBIENT,
NORMAL_FITTING = 10,
};
};
class Locations {
@ -220,6 +226,7 @@ public:
int materialBufferUnit;
int texMapArrayBufferUnit;
int lightBufferUnit;
int lightAmbientMapUnit;
};
using LocationsPointer = std::shared_ptr<Locations>;

View file

@ -140,6 +140,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {
hadUncaughtExceptions(*this, _fileNameString);
});
setProcessEventsInterval(MSECS_PER_SECOND);
}
ScriptEngine::~ScriptEngine() {
@ -170,7 +172,7 @@ void ScriptEngine::runInThread() {
}
_isThreaded = true;
QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete
QThread* workerThread = new QThread(this); // thread is not owned, so we need to manage the delete
QString scriptEngineName = QString("Script Thread:") + getFilename();
workerThread->setObjectName(scriptEngineName);
@ -184,9 +186,6 @@ void ScriptEngine::runInThread() {
// tell the thread to stop when the script engine is done
connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
// when the thread is finished, add thread to the deleteLater queue
connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);
moveToThread(workerThread);
// Starts an event loop, and emits workerThread->started()
@ -199,11 +198,30 @@ void ScriptEngine::waitTillDoneRunning() {
// NOTE: waitTillDoneRunning() will be called on the main Application thread, inside of stopAllScripts()
// we want the application thread to continue to process events, because the scripts will likely need to
// marshall messages across to the main thread. For example if they access Settings or Meny in any of their
// marshall messages across to the main thread. For example if they access Settings or Menu in any of their
// shutdown code.
QString scriptName = getFilename();
auto startedWaiting = usecTimestampNow();
while (thread()->isRunning()) {
// process events for the main application thread, allowing invokeMethod calls to pass between threads
QCoreApplication::processEvents();
auto stillWaiting = usecTimestampNow();
auto elapsedUsecs = stillWaiting - startedWaiting;
// if we've been waiting a second or more, then tell the script engine to stop evaluating
static const auto MAX_SCRIPT_EVALUATION_TIME = USECS_PER_SECOND;
static const auto WAITING_TOO_LONG = MAX_SCRIPT_EVALUATION_TIME * 5;
// if we've been waiting for more than 5 seconds then we should be more aggessive about stopping
if (elapsedUsecs > WAITING_TOO_LONG) {
qCDebug(scriptengine) << "Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] quitting.";
thread()->quit();
break;
} else if (elapsedUsecs > MAX_SCRIPT_EVALUATION_TIME) {
qCDebug(scriptengine) << "Script " << scriptName << " has been running too long [" << elapsedUsecs << " usecs] aborting evaluation.";
QMetaObject::invokeMethod(this, "abortEvaluation");
}
}
}
}
@ -270,6 +288,48 @@ static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantR
assert(false);
}
// Templated qScriptRegisterMetaType fails to compile with raw pointers
using ScriptableResourceRawPtr = ScriptableResource*;
static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) {
// The first script to encounter this resource will track its memory.
// In this way, it will be more likely to GC.
// This fails in the case that the resource is used across many scripts, but
// in that case it would be too difficult to tell which one should track the memory, and
// this serves the common case (use in a single script).
auto data = resource->getResource();
if (data && !resource->isInScript()) {
resource->setInScript(true);
QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64)));
}
auto object = engine->newQObject(
const_cast<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 +387,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)));
@ -367,7 +432,6 @@ void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
if (partsToGo > 0) {
//QObject *object = new QObject;
QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership);
qDebug() << "partValue[" << pathPart<<"].isArray() :" << partValue.isArray();
partObject.setProperty(pathPart, partValue);
} else {
partObject.setProperty(pathPart, value);
@ -793,6 +857,12 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM
}
}
void ScriptEngine::updateMemoryCost(const qint64& deltaSize) {
if (deltaSize > 0) {
reportAdditionalMemoryCost(deltaSize);
}
}
void ScriptEngine::timerFired() {
QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());
CallbackData timerData = _timerFunctionMap.value(callingTimer);
@ -912,7 +982,18 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())
const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment;
for (QString file : includeFiles) {
QUrl thisURL { resolvePath(file) };
QUrl thisURL;
if (file.startsWith("/~/")) {
thisURL = expandScriptUrl(QUrl::fromLocalFile(expandScriptPath(file)));
QUrl defaultScriptsLoc = defaultScriptsLocation();
if (!defaultScriptsLoc.isParentOf(thisURL)) {
qDebug() << "ScriptEngine::include -- skipping" << file << "-- outside of standard libraries";
continue;
}
} else {
thisURL = resolvePath(file);
}
if (!_includedURLs.contains(thisURL)) {
if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
(
@ -1060,11 +1141,6 @@ void ScriptEngine::loadEntityScript(QWeakPointer<ScriptEngine> theEngine, const
<< QThread::currentThread() << "] expected thread [" << strongEngine->thread() << "]";
#endif
strongEngine->entityScriptContentAvailable(entityID, scriptOrURL, contents, isURL, success);
} else {
// FIXME - I'm leaving this in for testing, so that QA can confirm that sometimes the script contents
// returns after the ScriptEngine has been deleted, we can remove this after QA verifies the
// repro case.
qDebug() << "ScriptCache::getScriptContents() returned after our ScriptEngine was deleted... script:" << scriptOrURL;
}
}, forceRedownload);
}

View file

@ -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);

View file

@ -70,6 +70,12 @@ QUrl normalizeScriptURL(const QUrl& rawScriptURL) {
}
}
QString expandScriptPath(const QString& rawPath) {
QStringList splitPath = rawPath.split("/");
QUrl defaultScriptsLoc = defaultScriptsLocation();
return defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/"); // 2 to skip the slashes in /~/
}
QUrl expandScriptUrl(const QUrl& rawScriptURL) {
QUrl normalizedScriptURL = normalizeScriptURL(rawScriptURL);
if (normalizedScriptURL.scheme() == "http" ||
@ -79,9 +85,23 @@ QUrl expandScriptUrl(const QUrl& rawScriptURL) {
} else if (normalizedScriptURL.scheme() == "file") {
if (normalizedScriptURL.path().startsWith("/~/")) {
QUrl url = normalizedScriptURL;
QStringList splitPath = url.path().split("/");
url.setPath(expandScriptPath(url.path()));
// stop something like Script.include(["/~/../Desktop/naughty.js"]); from working
QFileInfo fileInfo(url.toLocalFile());
url = QUrl::fromLocalFile(fileInfo.canonicalFilePath());
QUrl defaultScriptsLoc = defaultScriptsLocation();
url.setPath(defaultScriptsLoc.path() + "/" + splitPath.mid(2).join("/")); // 2 to skip the slashes in /~/
if (!defaultScriptsLoc.isParentOf(url)) {
qCWarning(scriptengine) << "Script.include() ignoring file path" << rawScriptURL
<< "-- outside of standard libraries: "
<< url.path()
<< defaultScriptsLoc.path();
return rawScriptURL;
}
if (rawScriptURL.path().endsWith("/") && !url.path().endsWith("/")) {
url.setPath(url.path() + "/");
}
return url;
}
return normalizedScriptURL;
@ -129,20 +149,7 @@ void ScriptEngines::shutdownScripting() {
// "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
// them. We don't need to stop scripts that aren't running.
if (scriptEngine->isRunning()) {
// If the script is running, but still evaluating then we need to wait for its evaluation step to
// complete. After that we can handle the stop process appropriately
if (scriptEngine->evaluatePending()) {
while (scriptEngine->evaluatePending()) {
// This event loop allows any started, but not yet finished evaluate() calls to complete
// we need to let these complete so that we can be guaranteed that the script engine isn't
// in a partially setup state, which can confuse our shutdown unwinding.
QEventLoop loop;
QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit);
loop.exec();
}
}
qCDebug(scriptengine) << "about to shutdown script:" << scriptName;
// We disconnect any script engine signals from the application because we don't want to do any
// extra stopScript/loadScript processing that the Application normally does when scripts start

View file

@ -101,6 +101,7 @@ protected:
};
QUrl normalizeScriptURL(const QUrl& rawScriptURL);
QString expandScriptPath(const QString& rawPath);
QUrl expandScriptUrl(const QUrl& rawScriptURL);
#endif // hifi_ScriptEngine_h

View file

@ -21,9 +21,19 @@ class ColorUtils {
public:
inline static glm::vec3 toVec3(const xColor& color);
// Convert from gamma 2.2 space to linear
inline static glm::vec3 toLinearVec3(const glm::vec3& srgb);
// Convert to gamma 2.2 space from linear
inline static glm::vec3 toGamma22Vec3(const glm::vec3& linear);
// Convert from sRGB gamma space to linear.
// This is pretty different from converting from 2.2.
inline static glm::vec3 sRGBToLinearVec3(const glm::vec3& srgb);
inline static glm::vec3 tosRGBVec3(const glm::vec3& srgb);
inline static glm::vec4 sRGBToLinearVec4(const glm::vec4& srgb);
inline static glm::vec4 tosRGBVec4(const glm::vec4& srgb);
inline static float sRGBToLinearFloat(const float& srgb);
inline static float tosRGBFloat(const float& linear);
};
inline glm::vec3 ColorUtils::toVec3(const xColor& color) {
@ -31,16 +41,66 @@ inline glm::vec3 ColorUtils::toVec3(const xColor& color) {
return glm::vec3(color.red * ONE_OVER_255, color.green * ONE_OVER_255, color.blue * ONE_OVER_255);
}
inline glm::vec3 ColorUtils::toLinearVec3(const glm::vec3& srgb) {
const float GAMMA_22 = 2.2f;
// Couldn't find glm::pow(vec3, vec3) ? so did it myself...
return glm::vec3(glm::pow(srgb.x, GAMMA_22), glm::pow(srgb.y, GAMMA_22), glm::pow(srgb.z, GAMMA_22));
}
inline glm::vec3 ColorUtils::toGamma22Vec3(const glm::vec3& linear) {
const float INV_GAMMA_22 = 1.0f / 2.2f;
// Couldn't find glm::pow(vec3, vec3) ? so did it myself...
return glm::vec3(glm::pow(linear.x, INV_GAMMA_22), glm::pow(linear.y, INV_GAMMA_22), glm::pow(linear.z, INV_GAMMA_22));
}
// Convert from sRGB color space to linear color space.
inline glm::vec3 ColorUtils::sRGBToLinearVec3(const glm::vec3& srgb) {
return glm::vec3(sRGBToLinearFloat(srgb.x), sRGBToLinearFloat(srgb.y), sRGBToLinearFloat(srgb.z));
}
// Convert from linear color space to sRGB color space.
inline glm::vec3 ColorUtils::tosRGBVec3(const glm::vec3& linear) {
return glm::vec3(tosRGBFloat(linear.x), tosRGBFloat(linear.y), tosRGBFloat(linear.z));
}
// Convert from sRGB color space with alpha to linear color space with alpha.
inline glm::vec4 ColorUtils::sRGBToLinearVec4(const glm::vec4& srgb) {
return glm::vec4(sRGBToLinearFloat(srgb.x), sRGBToLinearFloat(srgb.y), sRGBToLinearFloat(srgb.z), srgb.w);
}
// Convert from linear color space with alpha to sRGB color space with alpha.
inline glm::vec4 ColorUtils::tosRGBVec4(const glm::vec4& linear) {
return glm::vec4(tosRGBFloat(linear.x), tosRGBFloat(linear.y), tosRGBFloat(linear.z), linear.w);
}
// This is based upon the conversions found in section 8.24 of the OpenGL 4.4 4.4 specification.
// glm::pow(color, 2.2f) is approximate, and will cause subtle differences when used with sRGB framebuffers.
inline float ColorUtils::sRGBToLinearFloat(const float &srgb) {
const float SRGB_ELBOW = 0.04045f;
float linearValue = 0.0f;
// This should mirror the conversion table found in section 8.24: sRGB Texture Color Conversion
if (srgb <= SRGB_ELBOW) {
linearValue = srgb / 12.92f;
} else {
linearValue = powf(((srgb + 0.055f) / 1.055f), 2.4f);
}
return linearValue;
}
// This is based upon the conversions found in section 17.3.9 of the OpenGL 4.4 specification.
// glm::pow(color, 1.0f/2.2f) is approximate, and will cause subtle differences when used with sRGB framebuffers.
inline float ColorUtils::tosRGBFloat(const float &linear) {
const float SRGB_ELBOW_INV = 0.0031308f;
float sRGBValue = 0.0f;
// This should mirror the conversion table found in section 17.3.9: sRGB Conversion
if (linear <= 0.0f) {
sRGBValue = 0.0f;
} else if (0 < linear && linear < SRGB_ELBOW_INV) {
sRGBValue = 12.92f * linear;
} else if (SRGB_ELBOW_INV <= linear && linear < 1) {
sRGBValue = 1.055f * powf(linear, 0.41666f - 0.055f);
} else {
sRGBValue = 1.0f;
}
return sRGBValue;
}
#endif // hifi_ColorUtils_h

View file

@ -56,12 +56,15 @@ QString findMostRecentFileExtension(const QString& originalFileName, QVector<QSt
}
QUrl defaultScriptsLocation() {
#ifdef Q_OS_WIN
return QUrl(("file:///" + QCoreApplication::applicationDirPath()).toLower() + "/scripts");
#elif defined(Q_OS_OSX)
return QUrl(("file://" + QCoreApplication::applicationDirPath() + "/../Resources/scripts").toLower());
#else
// return "http://s3.amazonaws.com/hifi-public";
return QUrl("file://" + QCoreApplication::applicationDirPath() + "/scripts");
#ifdef Q_OS_WIN
QString path = QCoreApplication::applicationDirPath() + "/scripts";
#elif defined(Q_OS_OSX)
QString path = QCoreApplication::applicationDirPath() + "/../Resources/scripts";
#else
QString path = QCoreApplication::applicationDirPath() + "/scripts";
#endif
QFileInfo fileInfo(path);
return QUrl::fromLocalFile(fileInfo.canonicalFilePath());
}

View file

@ -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);

View 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);
}

View 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); });

View 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();
});
});

View file

@ -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);

View file

@ -13,6 +13,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var isDice = false;
var NUMBER_OF_DICE = 4;
var LIFETIME = 10000; // Dice will live for about 3 hours
@ -29,14 +31,14 @@ var screenSize = Controller.getViewportDimensions();
var BUTTON_SIZE = 32;
var PADDING = 3;
var BOTTOM_PADDING = 50;
//a helper library for creating toolbars
Script.include("http://hifi-production.s3.amazonaws.com/tutorials/dice/toolBars.js");
var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.dice.toolbar", function(screenSize) {
var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.toolbars-dice", function(screenSize) {
return {
x: (screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING),
y: (screenSize.y - (BUTTON_SIZE + PADDING))
x: (screenSize.x - BUTTON_SIZE*3),
y: (screenSize.y - 100)
};
});
@ -54,7 +56,7 @@ var offButton = toolBar.addOverlay("image", {
var deleteButton = toolBar.addOverlay("image", {
x: screenSize.x / 2 - BUTTON_SIZE,
y: screenSize.y - (BUTTON_SIZE + PADDING),
y: screenSize.y - (BUTTON_SIZE + PADDING)+BOTTOM_PADDING,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/delete.png",
@ -69,7 +71,7 @@ var deleteButton = toolBar.addOverlay("image", {
var diceIconURL = "http://hifi-production.s3.amazonaws.com/tutorials/dice/dice.png"
var diceButton = toolBar.addOverlay("image", {
x: screenSize.x / 2 + PADDING,
y: screenSize.y - (BUTTON_SIZE + PADDING),
y: screenSize.y - (BUTTON_SIZE + PADDING)+BOTTOM_PADDING,
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: diceIconURL,