Merge branch 'master' into 20855

This commit is contained in:
David Rowe 2016-04-01 17:32:47 +13:00
commit b9054897f7
38 changed files with 573 additions and 213 deletions

View file

@ -0,0 +1,26 @@
//
// changeColorOnEnterLeave.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 3/31/16.
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
this.enterEntity = function(myID) {
print("enterEntity() myID:" + myID);
Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} });
};
this.leaveEntity = function(myID) {
print("leaveEntity() myID:" + myID);
Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} });
};
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View file

@ -0,0 +1,41 @@
float aspect(vec2 v) {
return v.x / v.y;
}
vec3 aspectCorrectedTexture() {
vec2 uv = _position.xy;
uv += 0.5;
uv.y = 1.0 - uv.y;
float targetAspect = iWorldScale.x / iWorldScale.y;
float sourceAspect = aspect(iChannelResolution[0].xy);
float aspectCorrection = sourceAspect / targetAspect;
if (aspectCorrection > 1.0) {
float offset = aspectCorrection - 1.0;
float halfOffset = offset / 2.0;
uv.y -= halfOffset;
uv.y *= aspectCorrection;
} else {
float offset = 1.0 - aspectCorrection;
float halfOffset = offset / 2.0;
uv.x -= halfOffset;
uv.x /= aspectCorrection;
}
if (any(lessThan(uv, vec2(0.0)))) {
return vec3(0.0);
}
if (any(greaterThan(uv, vec2(1.0)))) {
return vec3(0.0);
}
vec4 color = texture(iChannel0, uv);
return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x));
}
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
specular = aspectCorrectedTexture();
return 1.0;
}

View file

@ -0,0 +1,67 @@
Script.include("https://s3.amazonaws.com/DreamingContent/scripts/Austin.js");
var ENTITY_SPAWN_LIMIT = 500;
var ENTITY_LIFETIME = 600;
var RADIUS = 1.0; // Spawn within this radius (square)
var TEST_ENTITY_NAME = "EntitySpawnTest";
var entities = [];
var textureIndex = 0;
var texture = Script.resolvePath('cube_texture.png');
var shader = Script.resolvePath('textureStress.fs');
var qml = Script.resolvePath('textureStress.qml');
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: qml,
height: 240,
width: 320,
toolWindow: false,
visible: true
});
function deleteItems(count) {
if (!count) {
var ids = Entities.findEntities(MyAvatar.position, 50);
ids.forEach(function(id) {
var properties = Entities.getEntityProperties(id, ["name"]);
if (properties.name === TEST_ENTITY_NAME) {
Entities.deleteEntity(id);
}
}, this);
entities = [];
return;
} else {
// FIXME... implement
}
}
function createItems(count) {
for (var i = 0; i < count; ++i) {
var newEntity = Entities.addEntity({
type: "Box",
name: TEST_ENTITY_NAME,
position: AUSTIN.avatarRelativePosition(AUSTIN.randomPositionXZ({ x: 0, y: 0, z: -2 }, RADIUS)),
color: { r: 255, g: 255, b: 255 },
dimensions: AUSTIN.randomDimensions(),
lifetime: ENTITY_LIFETIME,
userData: JSON.stringify({
ProceduralEntity: {
version: 2,
shaderUrl: shader,
channels: [ texture + "?" + textureIndex++ ]
}
})
});
entities.push(newEntity);
}
}
qmlWindow.fromQml.connect(function(message){
print(message);
if (message[0] === "create") {
var count = message[1] || 1;
createItems(message[1] || 1);
} else if (message[0] === "delete") {
deleteItems(message[1]);
}
});

View file

@ -0,0 +1,69 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Rectangle {
id: root
width: parent ? parent.width : 100
height: parent ? parent.height : 100
signal sendToScript(var message);
Text {
id: label
text: "GPU Texture Usage: "
}
Text {
id: usage
anchors.left: label.right
anchors.leftMargin: 8
text: "N/A"
Timer {
repeat: true
running: true
interval: 500
onTriggered: {
usage.text = Render.getConfig("Stats")["textureGPUMemoryUsage"];
}
}
}
Column {
anchors { left: parent.left; right: parent.right; top: label.bottom; topMargin: 8; bottom: parent.bottom }
spacing: 8
Button {
text: "Add 1"
onClicked: root.sendToScript(["create", 1]);
}
Button {
text: "Add 10"
onClicked: root.sendToScript(["create", 10]);
}
Button {
text: "Add 100"
onClicked: root.sendToScript(["create", 100]);
}
/*
Button {
text: "Delete 1"
onClicked: root.sendToScript(["delete", 1]);
}
Button {
text: "Delete 10"
onClicked: root.sendToScript(["delete", 10]);
}
Button {
text: "Delete 100"
onClicked: root.sendToScript(["delete", 100]);
}
*/
Button {
text: "Delete All"
onClicked: root.sendToScript(["delete", 0]);
}
}
}

View file

@ -3357,9 +3357,10 @@ void Application::update(float deltaTime) {
}
{
PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("havestChanges");
PerformanceTimer perfTimer("harvestChanges");
if (_physicsEngine->hasOutgoingChanges()) {
getEntities()->getTree()->withWriteLock([&] {
PerformanceTimer perfTimer("handleOutgoingChanges");
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
_entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID());
avatarManager->handleOutgoingChanges(outgoingChanges);
@ -3375,6 +3376,7 @@ void Application::update(float deltaTime) {
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
// deadlock.)
_entitySimulation.handleCollisionEvents(collisionEvents);
// NOTE: the getEntities()->update() call below will wait for lock
// and will simulate entity motion (the EntityTree has been given an EntitySimulation).
getEntities()->update(); // update the models...

View file

@ -859,7 +859,11 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
AvatarData::setSkeletonModelURL(skeletonModelURL);
_skeletonModel->setURL(_skeletonModelURL);
if (QThread::currentThread() == thread()) {
_skeletonModel->setURL(_skeletonModelURL);
} else {
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", Qt::QueuedConnection, Q_ARG(QUrl, _skeletonModelURL));
}
}
// create new model, can return an instance of a SoftAttachmentModel rather then Model

View file

@ -97,3 +97,11 @@ bool HMDScriptingInterface::isMounted() const{
auto displayPlugin = qApp->getActiveDisplayPlugin();
return (displayPlugin->isHmd() && displayPlugin->isDisplayVisible());
}
QString HMDScriptingInterface::preferredAudioInput() const {
return qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
}
QString HMDScriptingInterface::preferredAudioOutput() const {
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}

View file

@ -34,6 +34,8 @@ public:
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
Q_INVOKABLE QString preferredAudioInput() const;
Q_INVOKABLE QString preferredAudioOutput() const;
public:
HMDScriptingInterface();

View file

@ -36,7 +36,7 @@ AnimationPointer AnimationCache::getAnimation(const QUrl& url) {
QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared);
return QSharedPointer<Resource>(new Animation(url), &Resource::deleter);
}
Animation::Animation(const QUrl& url) : Resource(url) {}

View file

@ -175,6 +175,50 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio
return (numSourceSamples * ratio) + 0.5f;
}
#ifdef Q_OS_WIN
QString friendlyNameForAudioDevice(IMMDevice* pEndpoint) {
QString deviceName;
IPropertyStore* pPropertyStore;
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
pEndpoint->Release();
pEndpoint = NULL;
PROPVARIANT pv;
PropVariantInit(&pv);
HRESULT hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
pPropertyStore->Release();
pPropertyStore = NULL;
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
if (!IsWindows8OrGreater()) {
// Windows 7 provides only the 31 first characters of the device name.
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
}
PropVariantClear(&pv);
return deviceName;
}
QString AudioClient::friendlyNameForAudioDevice(wchar_t* guid) {
QString deviceName;
HRESULT hr = S_OK;
CoInitialize(NULL);
IMMDeviceEnumerator* pMMDeviceEnumerator = NULL;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator);
IMMDevice* pEndpoint;
hr = pMMDeviceEnumerator->GetDevice(guid, &pEndpoint);
if (hr == E_NOTFOUND) {
printf("Audio Error: device not found\n");
deviceName = QString("NONE");
} else {
deviceName = ::friendlyNameForAudioDevice(pEndpoint);
}
pMMDeviceEnumerator->Release();
pMMDeviceEnumerator = NULL;
CoUninitialize();
return deviceName;
}
#endif
QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
#ifdef __APPLE__
if (QAudioDeviceInfo::availableDevices(mode).size() > 1) {
@ -248,23 +292,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
printf("Audio Error: device not found\n");
deviceName = QString("NONE");
} else {
IPropertyStore* pPropertyStore;
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
pEndpoint->Release();
pEndpoint = NULL;
PROPVARIANT pv;
PropVariantInit(&pv);
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
pPropertyStore->Release();
pPropertyStore = NULL;
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
if (!IsWindows8OrGreater()) {
// Windows 7 provides only the 31 first characters of the device name.
const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
}
qCDebug(audioclient) << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
PropVariantClear(&pv);
deviceName = friendlyNameForAudioDevice(pEndpoint);
}
pMMDeviceEnumerator->Release();
pMMDeviceEnumerator = NULL;

View file

@ -16,6 +16,7 @@
#include <memory>
#include <vector>
#include <QtCore/qsystemdetection.h>
#include <QtCore/QByteArray>
#include <QtCore/QElapsedTimer>
#include <QtCore/QObject>
@ -126,6 +127,10 @@ public:
static const float CALLBACK_ACCELERATOR_RATIO;
#ifdef Q_OS_WIN
static QString friendlyNameForAudioDevice(wchar_t* guid);
#endif
public slots:
void start();
void stop();

View file

@ -36,5 +36,5 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) {
QSharedPointer<Resource> SoundCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
bool delayLoad, const void* extra) {
qCDebug(audio) << "Requesting sound at" << url.toString();
return QSharedPointer<Resource>(new Sound(url), &Resource::allReferencesCleared);
return QSharedPointer<Resource>(new Sound(url), &Resource::deleter);
}

View file

@ -130,6 +130,7 @@ void EntityTreeRenderer::setTree(OctreePointer newTree) {
}
void EntityTreeRenderer::update() {
PerformanceTimer perfTimer("ETRupdate");
if (_tree && !_shuttingDown) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
tree->update();
@ -159,12 +160,14 @@ void EntityTreeRenderer::update() {
bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector<EntityItemID>* entitiesContainingAvatar) {
bool didUpdate = false;
float radius = 1.0f; // for now, assume 1 meter radius
float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later
QVector<EntityItemPointer> foundEntities;
// find the entities near us
// don't let someone else change our tree while we search
_tree->withReadLock([&] {
// FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
@ -173,36 +176,37 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3&
_bestZoneVolume = std::numeric_limits<float>::max();
// create a list of entities that actually contain the avatar's position
foreach(EntityItemPointer entity, foundEntities) {
if (entity->contains(avatarPosition)) {
if (entitiesContainingAvatar) {
*entitiesContainingAvatar << entity->getEntityItemID();
}
for (auto& entity : foundEntities) {
auto isZone = entity->getType() == EntityTypes::Zone;
auto hasScript = !entity->getScript().isEmpty();
// if this entity is a zone, use this time to determine the bestZone
if (entity->getType() == EntityTypes::Zone) {
if (!entity->getVisible()) {
#ifdef WANT_DEBUG
qCDebug(entitiesrenderer) << "not visible";
#endif
} else {
// only consider entities that are zones or have scripts, all other entities can
// be ignored because they can have events fired on them.
// FIXME - this could be optimized further by determining if the script is loaded
// and if it has either an enterEntity or leaveEntity method
if (isZone || hasScript) {
// now check to see if the point contains our entity, this can be expensive if
// the entity has a collision hull
if (entity->contains(avatarPosition)) {
if (entitiesContainingAvatar) {
*entitiesContainingAvatar << entity->getEntityItemID();
}
// if this entity is a zone and visible, determine if it is the bestZone
if (isZone && entity->getVisible()) {
float entityVolumeEstimate = entity->getVolumeEstimate();
if (entityVolumeEstimate < _bestZoneVolume) {
_bestZoneVolume = entityVolumeEstimate;
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
}
else if (entityVolumeEstimate == _bestZoneVolume) {
} else if (entityVolumeEstimate == _bestZoneVolume) {
// in the case of the volume being equal, we will use the
// EntityItemID to deterministically pick one entity over the other
if (!_bestZone) {
_bestZoneVolume = entityVolumeEstimate;
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
}
else {
// in the case of the volume being equal, we will use the
// EntityItemID to deterministically pick one entity over the other
if (entity->getEntityItemID() < _bestZone->getEntityItemID()) {
_bestZoneVolume = entityVolumeEstimate;
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
}
} else if (entity->getEntityItemID() < _bestZone->getEntityItemID()) {
_bestZoneVolume = entityVolumeEstimate;
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(entity);
}
}
}
@ -217,13 +221,24 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3&
});
return didUpdate;
}
bool EntityTreeRenderer::checkEnterLeaveEntities() {
PerformanceTimer perfTimer("checkEnterLeaveEntities");
auto now = usecTimestampNow();
bool didUpdate = false;
if (_tree && !_shuttingDown) {
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
if (avatarPosition != _lastAvatarPosition) {
// we want to check our enter/leave state if we've moved a significant amount, or
// if some amount of time has elapsed since we last checked. We check the time
// elapsed because zones or entities might have been created "around us" while we've
// been stationary
auto movedEnough = glm::distance(avatarPosition, _lastAvatarPosition) > ZONE_CHECK_DISTANCE;
auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL;
if (movedEnough || enoughTimeElapsed) {
_lastZoneCheck = now;
QVector<EntityItemID> entitiesContainingAvatar;
didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, &entitiesContainingAvatar);
@ -248,8 +263,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
}
_currentEntitiesInside = entitiesContainingAvatar;
_lastAvatarPosition = avatarPosition;
} else {
didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, nullptr);
}
}
return didUpdate;

View file

@ -178,6 +178,10 @@ private:
std::shared_ptr<ZoneEntityItem> _bestZone;
float _bestZoneVolume;
quint64 _lastZoneCheck { 0 };
const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
const float ZONE_CHECK_DISTANCE = 0.001f;
glm::vec3 _previousKeyLightColor;
float _previousKeyLightIntensity;
float _previousKeyLightAmbientIntensity;

View file

@ -116,11 +116,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
QJsonParseError error;
QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
// If textures are invalid, revert to original textures
if (error.error != QJsonParseError::NoError) {
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures;
return _originalTextures;
}
QVariantMap texturesMap = texturesJson.toVariant().toMap();
// If textures are unset, revert to original textures
if (texturesMap.isEmpty()) {
return _originalTextures;
}
return texturesJson.toVariant().toMap();
}
@ -133,10 +140,8 @@ void RenderableModelEntityItem::remapTextures() {
return; // nothing to do if the model has not yet loaded
}
auto& geometry = _model->getGeometry()->getGeometry();
if (!_originalTexturesRead) {
_originalTextures = geometry->getTextures();
_originalTextures = _model->getTextures();
_originalTexturesRead = true;
// Default to _originalTextures to avoid remapping immediately and lagging on load
@ -152,7 +157,7 @@ void RenderableModelEntityItem::remapTextures() {
auto newTextures = parseTexturesToMap(textures);
if (newTextures != _currentTextures) {
geometry->setTextures(newTextures);
_model->setTextures(newTextures);
_currentTextures = newTextures;
}
}
@ -366,41 +371,16 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
assert(getType() == EntityTypes::Model);
if (hasModel()) {
if (_model) {
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0;
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
_showCollisionHull = shouldShowCollisionHull;
render::PendingChanges pendingChanges;
_model->removeFromScene(scene, pendingChanges);
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
scene->enqueuePendingChanges(pendingChanges);
}
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
// the renderable item. As it stands now the model checks it's visible/invisible state
// so most of the time we don't do anything in this function.
_model->setVisibleInScene(getVisible(), scene);
}
remapTextures();
// Prepare the current frame
{
// float alpha = getLocalRenderAlpha();
if (!_model || _needsModelReload) {
// TODO: this getModel() appears to be about 3% of model render time. We should optimize
PerformanceTimer perfTimer("getModel");
EntityTreeRenderer* renderer = static_cast<EntityTreeRenderer*>(args->_renderer);
getModel(renderer);
// Remap textures immediately after loading to avoid flicker
remapTextures();
}
if (_model) {
@ -431,15 +411,40 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
}
});
updateModelBounds();
}
}
// Check if the URL has changed
// Do this last as the getModel is queued for the next frame,
// and we need to keep state directing the model to reinitialize
auto& currentURL = getParsedModelURL();
if (currentURL != _model->getURL()) {
// Defer setting the url to the render thread
getModel(_myRenderer);
}
// Enqueue updates for the next frame
if (_model) {
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
// the renderable item. As it stands now the model checks it's visible/invisible state
// so most of the time we don't do anything in this function.
_model->setVisibleInScene(getVisible(), scene);
// Remap textures for the next frame to avoid flicker
remapTextures();
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0;
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
_showCollisionHull = shouldShowCollisionHull;
render::PendingChanges pendingChanges;
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
scene->enqueuePendingChanges(pendingChanges);
}
auto& currentURL = getParsedModelURL();
if (currentURL != _model->getURL()) {
// Defer setting the url to the render thread
getModel(_myRenderer);
}
}
} else {

View file

@ -457,6 +457,14 @@ uint32 Texture::getStoredMipSize(uint16 level) const {
return 0;
}
gpu::Resource::Size Texture::getStoredSize() const {
auto size = 0;
for (int level = 0; level < evalNumMips(); ++level) {
size += getStoredMipSize(level);
}
return size;
}
uint16 Texture::evalNumSamplesUsed(uint16 numSamplesTried) {
uint16 sample = numSamplesTried;
if (numSamplesTried <= 1)

View file

@ -288,9 +288,12 @@ public:
Stamp getStamp() const { return _stamp; }
Stamp getDataStamp() const { return _storage->getStamp(); }
// The size in bytes of data stored in the texture
// The theoretical size in bytes of data stored in the texture
Size getSize() const { return _size; }
// The actual size in bytes of data stored in the texture
Size getStoredSize() const;
// Resize, unless auto mips mode would destroy all the sub mips
Size resize1D(uint16 width, uint16 numSamples);
Size resize2D(uint16 width, uint16 height, uint16 numSamples);

View file

@ -66,16 +66,15 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
GeometryExtra extra{ mapping, textureBaseUrl };
// Get the raw GeometryResource, not the wrapped NetworkGeometry
_geometryResource = modelCache->getResource(url, QUrl(), true, &extra).staticCast<GeometryResource>();
_geometryResource = modelCache->getResource(url, QUrl(), false, &extra).staticCast<GeometryResource>();
// Avoid caching nested resources - their references will be held by the parent
_geometryResource->_isCacheable = false;
if (_geometryResource->isLoaded()) {
onGeometryMappingLoaded(!_geometryResource->getURL().isEmpty());
} else {
connect(_geometryResource.data(), &Resource::finished, this, &GeometryMappingResource::onGeometryMappingLoaded);
}
// Avoid caching nested resources - their references will be held by the parent
_geometryResource->_isCacheable = false;
}
}
@ -86,6 +85,10 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
_meshes = _geometryResource->_meshes;
_materials = _geometryResource->_materials;
}
// Avoid holding onto extra references
_geometryResource.reset();
finishedLoading(success);
}
@ -157,7 +160,7 @@ class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
GeometryResource(url), _mapping(mapping), _textureBaseUrl(textureBaseUrl.isValid() ? textureBaseUrl : url) {}
GeometryResource(url, textureBaseUrl.isValid() ? textureBaseUrl : url), _mapping(mapping) {}
virtual void downloadFinished(const QByteArray& data) override;
@ -166,7 +169,6 @@ protected:
private:
QVariantHash _mapping;
QUrl _textureBaseUrl;
};
void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
@ -220,13 +222,20 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QShar
resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl);
}
return QSharedPointer<Resource>(resource, &Resource::allReferencesCleared);
return QSharedPointer<Resource>(resource, &Resource::deleter);
}
std::shared_ptr<NetworkGeometry> ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) {
GeometryExtra geometryExtra = { mapping, textureBaseUrl };
GeometryResource::Pointer resource = getResource(url, QUrl(), true, &geometryExtra).staticCast<GeometryResource>();
return std::make_shared<NetworkGeometry>(resource);
if (resource) {
if (resource->isLoaded() && !resource->hasTextures()) {
resource->setTextures();
}
return std::make_shared<NetworkGeometry>(resource);
} else {
return NetworkGeometry::Pointer();
}
}
const QVariantMap Geometry::getTextures() const {
@ -270,6 +279,9 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
material->setTextures(textureMap);
_areTexturesLoaded = false;
// If we only use cached textures, they should all be loaded
areTexturesLoaded();
}
}
} else {
@ -279,8 +291,6 @@ void Geometry::setTextures(const QVariantMap& textureMap) {
bool Geometry::areTexturesLoaded() const {
if (!_areTexturesLoaded) {
_hasTransparentTextures = false;
for (auto& material : _materials) {
// Check if material textures are loaded
if (std::any_of(material->_textures.cbegin(), material->_textures.cend(),
@ -293,8 +303,6 @@ bool Geometry::areTexturesLoaded() const {
const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP];
if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) {
material->resetOpacityMap();
_hasTransparentTextures |= material->getKey().isTranslucent();
}
}
@ -313,6 +321,21 @@ const std::shared_ptr<const NetworkMaterial> Geometry::getShapeMaterial(int shap
return nullptr;
}
void GeometryResource::deleter() {
resetTextures();
Resource::deleter();
}
void GeometryResource::setTextures() {
for (const FBXMaterial& material : _geometry->materials) {
_materials.push_back(std::make_shared<NetworkMaterial>(material, _textureBaseUrl));
}
}
void GeometryResource::resetTextures() {
_materials.clear();
}
NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) {
connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished);
connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed);

View file

@ -74,9 +74,6 @@ public:
void setTextures(const QVariantMap& textureMap);
virtual bool areTexturesLoaded() const;
// Returns true if any albedo texture has a non-masking alpha channel.
// This can only be known after areTexturesLoaded().
bool hasTransparentTextures() const { return _hasTransparentTextures; }
protected:
friend class GeometryMappingResource;
@ -91,7 +88,6 @@ protected:
private:
mutable bool _areTexturesLoaded { false };
mutable bool _hasTransparentTextures { false };
};
/// A geometry loaded from the network.
@ -99,15 +95,24 @@ class GeometryResource : public Resource, public Geometry {
public:
using Pointer = QSharedPointer<GeometryResource>;
GeometryResource(const QUrl& url) : Resource(url) {}
GeometryResource(const QUrl& url, const QUrl& textureBaseUrl = QUrl()) : Resource(url) {}
virtual bool areTexturesLoaded() const { return isLoaded() && Geometry::areTexturesLoaded(); }
virtual void deleter() override;
protected:
friend class ModelCache;
friend class GeometryMappingResource;
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
// Geometries may not hold onto textures while cached - that is for the texture cache
bool hasTextures() const { return !_materials.empty(); }
void setTextures();
void resetTextures();
QUrl _textureBaseUrl;
virtual bool isCacheable() const override { return _loaded && _isCacheable; }
bool _isCacheable { true };
};

View file

@ -28,6 +28,6 @@ NetworkShaderPointer ShaderCache::getShader(const QUrl& url) {
}
QSharedPointer<Resource> ShaderCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkShader(url, delayLoad), &Resource::allReferencesCleared);
return QSharedPointer<Resource>(new NetworkShader(url, delayLoad), &Resource::deleter);
}

View file

@ -166,12 +166,11 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path) {
return texture;
}
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),
&Resource::allReferencesCleared);
&Resource::deleter);
}
NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) :
@ -339,10 +338,13 @@ void NetworkTexture::setImage(void* voidTexture, int originalWidth,
if (gpuTexture) {
_width = gpuTexture->getWidth();
_height = gpuTexture->getHeight();
setBytes(gpuTexture->getStoredSize());
} else {
// FIXME: If !gpuTexture, we failed to load!
_width = _height = 0;
qWarning() << "Texture did not load";
}
finishedLoading(true);
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));

View file

@ -117,22 +117,22 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) {
}
void ResourceCache::addUnusedResource(const QSharedPointer<Resource>& resource) {
if (resource->getBytesTotal() > _unusedResourcesMaxSize) {
// If it doesn't fit anyway, let's leave whatever is already in the cache.
// If it doesn't fit or its size is unknown, leave the cache alone.
if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) {
resource->setCache(nullptr);
return;
}
reserveUnusedResource(resource->getBytesTotal());
reserveUnusedResource(resource->getBytes());
resource->setLRUKey(++_lastLRUKey);
_unusedResources.insert(resource->getLRUKey(), resource);
_unusedResourcesSize += resource->getBytesTotal();
_unusedResourcesSize += resource->getBytes();
}
void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resource) {
if (_unusedResources.contains(resource->getLRUKey())) {
_unusedResources.remove(resource->getLRUKey());
_unusedResourcesSize -= resource->getBytesTotal();
_unusedResourcesSize -= resource->getBytes();
}
}
@ -142,7 +142,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
// unload the oldest resource
QMap<int, QSharedPointer<Resource> >::iterator it = _unusedResources.begin();
_unusedResourcesSize -= it.value()->getBytesTotal();
_unusedResourcesSize -= it.value()->getBytes();
it.value()->setCache(nullptr);
_unusedResources.erase(it);
}
@ -399,7 +399,7 @@ void Resource::makeRequest() {
connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress);
connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished);
_bytesReceived = _bytesTotal = 0;
_bytesReceived = _bytesTotal = _bytes = 0;
_request->send();
}
@ -412,6 +412,8 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota
void Resource::handleReplyFinished() {
Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
_bytes = _bytesTotal;
if (!_request || _request != sender()) {
// This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted.
qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current"

View file

@ -181,6 +181,9 @@ public:
/// For loading resources, returns the number of total bytes (<= zero if unknown).
qint64 getBytesTotal() const { return _bytesTotal; }
/// For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly set).
qint64 getBytes() const { return _bytes; }
/// For loading resources, returns the load progress.
float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; }
@ -191,7 +194,7 @@ public:
void setCache(ResourceCache* cache) { _cache = cache; }
Q_INVOKABLE void allReferencesCleared();
virtual void deleter() { allReferencesCleared(); }
const QUrl& getURL() const { return _url; }
@ -222,10 +225,15 @@ protected:
/// This should be overridden by subclasses that need to process the data once it is downloaded.
virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); }
/// Called when the download is finished and processed, sets the number of actual bytes.
void setBytes(qint64 bytes) { _bytes = bytes; }
/// Called when the download is finished and processed.
/// This should be called by subclasses that override downloadFinished to mark the end of processing.
Q_INVOKABLE void finishedLoading(bool success);
Q_INVOKABLE void allReferencesCleared();
QUrl _url;
QUrl _activeUrl;
bool _startedLoading = false;
@ -253,6 +261,7 @@ private:
QTimer* _replyTimer = nullptr;
qint64 _bytesReceived = 0;
qint64 _bytesTotal = 0;
qint64 _bytes = 0;
int _attempts = 0;
};

View file

@ -74,6 +74,9 @@ public:
/// whether the HMD is being worn
virtual bool isDisplayVisible() const { return false; }
virtual QString getPreferredAudioInDevice() const { return QString(); }
virtual QString getPreferredAudioOutDevice() const { return QString(); }
// Rendering support
/**

View file

@ -36,6 +36,6 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) {
}
QSharedPointer<Resource> ClipCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::allReferencesCleared);
return QSharedPointer<Resource>(new NetworkClipLoader(url, delayLoad), &Resource::deleter);
}

View file

@ -76,14 +76,9 @@ AbstractViewStateInterface* Model::_viewState = NULL;
bool Model::needsFixupInScene() const {
if (readyToAddToScene()) {
// Once textures are loaded, fixup if they are now transparent
if (_needsUpdateTransparentTextures && _geometry->getGeometry()->areTexturesLoaded()) {
_needsUpdateTransparentTextures = false;
bool hasTransparentTextures = _geometry->getGeometry()->hasTransparentTextures();
if (_hasTransparentTextures != hasTransparentTextures) {
_hasTransparentTextures = hasTransparentTextures;
return true;
}
if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) {
_needsUpdateTextures = false;
return true;
}
if (!_readyWhenAdded) {
return true;
@ -167,13 +162,15 @@ void Model::enqueueLocationChange() {
render::PendingChanges pendingChanges;
foreach (auto itemID, self->_modelMeshRenderItems.keys()) {
pendingChanges.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, modelMeshOffset](ModelMeshPartPayload& data) {
// Ensure the model geometry was not reset between frames
if (data._model->isLoaded()) {
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
data._model->updateClusterMatrices(modelTransform.getTranslation(), modelTransform.getRotation());
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
// update the model transform and bounding box for this render item.
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
data.updateTransformForSkinnedMesh(modelTransform, modelMeshOffset, state.clusterMatrices);
}
});
}
@ -544,43 +541,6 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
}
}
bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges, bool showCollisionHull) {
if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) {
_showCollisionHull = showCollisionHull;
segregateMeshGroups();
}
bool somethingAdded = false;
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
_readyWhenAdded = readyToAddToScene();
return somethingAdded;
}
bool Model::addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters,
@ -592,28 +552,48 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
bool somethingAdded = false;
foreach (auto renderItem, _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
renderPayload->addStatusGetters(statusGetters);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
if (_modelMeshRenderItems.size()) {
for (auto item : _modelMeshRenderItems.keys()) {
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
}
} else {
for (auto renderItem : _modelMeshRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<ModelMeshPartPayload>(item, [](ModelMeshPartPayload& data) {
data.notifyLocationChanged();
});
_modelMeshRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
}
foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
renderPayload->addStatusGetters(statusGetters);
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
if (_collisionRenderItems.size()) {
for (auto item : _collisionRenderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
}
} else {
for (auto renderItem : _collisionRenderItemsSet) {
auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
if (statusGetters.size()) {
renderPayload->addStatusGetters(statusGetters);
}
pendingChanges.resetItem(item, renderPayload);
pendingChanges.updateItem<MeshPartPayload>(item, [](MeshPartPayload& data) {
data.notifyLocationChanged();
});
_collisionRenderItems.insert(item, renderPayload);
somethingAdded = true;
}
}
_readyWhenAdded = readyToAddToScene();
@ -789,6 +769,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1;
}
void Model::setTextures(const QVariantMap& textures) {
if (isLoaded()) {
_needsUpdateTextures = true;
_geometry->getGeometry()->setTextures(textures);
}
}
void Model::setURL(const QUrl& url) {
// don't recreate the geometry if it's the same URL
if (_url == url && _geometry && _geometry->getURL() == url) {
@ -805,8 +792,7 @@ void Model::setURL(const QUrl& url) {
}
_needsReload = true;
_needsUpdateTransparentTextures = true;
_hasTransparentTextures = false;
_needsUpdateTextures = true;
_meshGroupsKnown = false;
invalidCalculatedMeshBoxes();
deleteGeometry();
@ -1061,7 +1047,7 @@ void Model::simulateInternal(float deltaTime) {
void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) {
PerformanceTimer perfTimer("Model::updateClusterMatrices");
if (!_needsUpdateClusterMatrices) {
if (!_needsUpdateClusterMatrices || !isLoaded()) {
return;
}
_needsUpdateClusterMatrices = false;

View file

@ -87,7 +87,10 @@ public:
bool initWhenReady(render::ScenePointer scene);
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
bool showCollisionHull = false);
bool showCollisionHull = false) {
auto getters = render::Item::Status::Getters(0);
return addToScene(scene, pendingChanges, getters, showCollisionHull);
}
bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters,
@ -129,6 +132,9 @@ public:
/// Returns a reference to the shared collision geometry.
const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; }
const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); }
void setTextures(const QVariantMap& textures);
/// Provided as a convenience, will crash if !isLoaded()
// And so that getGeometry() isn't chained everywhere
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); }
@ -385,9 +391,8 @@ protected:
bool _readyWhenAdded { false };
bool _needsReload { true };
bool _needsUpdateClusterMatrices { true };
mutable bool _needsUpdateTransparentTextures { true };
mutable bool _hasTransparentTextures { false };
bool _showCollisionHull { false };
mutable bool _needsUpdateTextures { true };
friend class ModelMeshPartPayload;
RigPointer _rig;

View file

@ -18,6 +18,7 @@
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include "ResourceManager.h"
#include "ScriptEngines.h"
BatchLoader::BatchLoader(const QList<QUrl>& urls)
: QObject(),
@ -34,8 +35,9 @@ void BatchLoader::start() {
}
_started = true;
for (const auto& url : _urls) {
for (const auto& rawURL : _urls) {
QUrl url = expandScriptUrl(normalizeScriptURL(rawURL));
auto request = ResourceManager::createResourceRequest(this, url);
if (!request) {
_data.insert(url, QString());

View file

@ -220,11 +220,10 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
return;
}
_fileNameString = scriptURL.toString();
QUrl url = expandScriptUrl(normalizeScriptURL(scriptURL));
_fileNameString = url.toString();
_isReloading = reload;
QUrl url(scriptURL);
bool isPending;
auto scriptCache = DependencyManager::get<ScriptCache>();
scriptCache->getScript(url, this, isPending, reload);
@ -848,7 +847,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
QUrl url(include);
// first lets check to see if it's already a full URL
if (!url.scheme().isEmpty()) {
return url;
return expandScriptUrl(normalizeScriptURL(url));
}
// we apparently weren't a fully qualified url, so, let's assume we're relative
@ -865,7 +864,7 @@ QUrl ScriptEngine::resolvePath(const QString& include) const {
}
// at this point we should have a legitimate fully qualified URL for our parent
url = parentURL.resolved(url);
url = expandScriptUrl(normalizeScriptURL(parentURL.resolved(url)));
return url;
}

View file

@ -429,7 +429,7 @@ ScriptEngine* ScriptEngines::loadScript(const QUrl& scriptFilename, bool isUserL
connect(scriptEngine, &ScriptEngine::errorLoadingScript, this, &ScriptEngines::onScriptEngineError);
// get the script engine object to load the script at the designated script URL
scriptEngine->loadURL(QUrl(expandScriptUrl(scriptUrl.toString())), reload);
scriptEngine->loadURL(scriptUrl, reload);
}
return scriptEngine;

View file

@ -309,6 +309,9 @@ void ScriptsModel::rebuildTree() {
QString hash;
QStringList pathList = script->getLocalPath().split(tr("/"));
pathList.removeLast();
if (pathList.isEmpty()) {
continue;
}
QStringList::const_iterator pathIterator;
for (pathIterator = pathList.constBegin(); pathIterator != pathList.constEnd(); ++pathIterator) {
hash.append(*pathIterator + "/");

View file

@ -122,13 +122,18 @@ void QmlWindowClass::initQml(QVariantMap properties) {
object->setProperty(SOURCE_PROPERTY, _source);
// Forward messages received from QML on to the script
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
});
}
Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
}
void QmlWindowClass::qmlToScript(const QVariant& message) {
QJSValue js = qvariant_cast<QJSValue>(message);
emit fromQml(js.toVariant());
}
void QmlWindowClass::sendToQml(const QVariant& message) {
// Forward messages received from the script on to QML
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));

View file

@ -63,6 +63,7 @@ signals:
protected slots:
void hasClosed();
void qmlToScript(const QVariant& message);
protected:
static QVariantMap parseArguments(QScriptContext* context);

View file

@ -12,8 +12,8 @@ if (WIN32)
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME oculus)
setup_hifi_plugin()
link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins)
setup_hifi_plugin(Multimedia)
link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins audio-client networking)
include_hifi_library_headers(octree)
@ -21,5 +21,6 @@ if (WIN32)
find_package(LibOVR REQUIRED)
target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES})
target_link_libraries(${TARGET_NAME} Winmm.lib)
endif()

View file

@ -21,6 +21,7 @@ public:
// Stereo specific methods
virtual void resetSensors() override final;
virtual void beginFrameRender(uint32_t frameIndex) override;
float getTargetFrameRate() override { return _hmdDesc.DisplayRefreshRate; }
protected:

View file

@ -6,7 +6,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OculusDisplayPlugin.h"
// Odd ordering of header is required to avoid 'macro redinition warnings'
#include <AudioClient.h>
#include <OVR_CAPI_Audio.h>
#include <shared/NsightHelpers.h>
#include "OculusHelpers.h"
const QString OculusDisplayPlugin::NAME("Oculus Rift");
@ -86,3 +93,26 @@ void OculusDisplayPlugin::hmdPresent() {
}
}
}
bool OculusDisplayPlugin::isHmdMounted() const {
ovrSessionStatus status;
return (OVR_SUCCESS(ovr_GetSessionStatus(_session, &status)) &&
(ovrFalse != status.HmdMounted));
}
QString OculusDisplayPlugin::getPreferredAudioInDevice() const {
WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE];
if (!OVR_SUCCESS(ovr_GetAudioDeviceInGuidStr(buffer))) {
return QString();
}
return AudioClient::friendlyNameForAudioDevice(buffer);
}
QString OculusDisplayPlugin::getPreferredAudioOutDevice() const {
WCHAR buffer[OVR_AUDIO_MAX_DEVICE_STR_SIZE];
if (!OVR_SUCCESS(ovr_GetAudioDeviceOutGuidStr(buffer))) {
return QString();
}
return AudioClient::friendlyNameForAudioDevice(buffer);
}

View file

@ -12,20 +12,18 @@
struct SwapFramebufferWrapper;
using SwapFboPtr = QSharedPointer<SwapFramebufferWrapper>;
const float TARGET_RATE_Oculus = 75.0f;
class OculusDisplayPlugin : public OculusBaseDisplayPlugin {
using Parent = OculusBaseDisplayPlugin;
public:
const QString& getName() const override { return NAME; }
float getTargetFrameRate() override { return TARGET_RATE_Oculus; }
QString getPreferredAudioInDevice() const override;
QString getPreferredAudioOutDevice() const override;
protected:
bool internalActivate() override;
void hmdPresent() override;
// FIXME update with Oculus API call once it's available in the SDK
bool isHmdMounted() const override { return true; }
bool isHmdMounted() const override;
void customizeContext() override;
void uncustomizeContext() override;
void cycleDebugOutput() override;