mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 13:33:38 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into hdr
This commit is contained in:
commit
e6572a42e3
34 changed files with 758 additions and 182 deletions
|
@ -15,6 +15,7 @@ import "styles"
|
|||
import "windows"
|
||||
import "hifi"
|
||||
import "hifi/toolbars"
|
||||
import "styles-uit" as HifiStyles
|
||||
import "controls-uit" as HifiControls
|
||||
|
||||
Window {
|
||||
|
@ -168,7 +169,24 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME replace with TextField
|
||||
HifiStyles.RalewayLight {
|
||||
id: notice;
|
||||
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.50;
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: parent.inputAreaStep + 12
|
||||
left: addressLine.left
|
||||
right: addressLine.right
|
||||
}
|
||||
}
|
||||
HifiStyles.FiraSansRegular {
|
||||
id: location;
|
||||
font.pixelSize: addressLine.font.pixelSize;
|
||||
color: "gray";
|
||||
clip: true;
|
||||
anchors.fill: addressLine;
|
||||
visible: !addressLine.activeFocus;
|
||||
}
|
||||
TextInput {
|
||||
id: addressLine
|
||||
focus: true
|
||||
|
@ -179,14 +197,12 @@ Window {
|
|||
right: parent.right
|
||||
leftMargin: forwardArrow.width
|
||||
rightMargin: forwardArrow.width / 2
|
||||
topMargin: parent.inputAreaStep + hifi.layout.spacing
|
||||
bottomMargin: parent.inputAreaStep + hifi.layout.spacing
|
||||
topMargin: parent.inputAreaStep + (2 * hifi.layout.spacing)
|
||||
bottomMargin: parent.inputAreaStep
|
||||
}
|
||||
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
|
||||
helperText: "Go to: place, @user, /path, network address"
|
||||
helperPixelSize: font.pixelSize * 0.75
|
||||
helperItalic: true
|
||||
onTextChanged: filterChoicesByText()
|
||||
onActiveFocusChanged: updateLocationText(focus)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,12 +360,24 @@ Window {
|
|||
});
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
addressLine.forceActiveFocus()
|
||||
fillDestinations();
|
||||
function updateLocationText(focus) {
|
||||
addressLine.text = "";
|
||||
if (focus) {
|
||||
notice.text = "Go to a place, @user, path or network address";
|
||||
notice.color = "gray";
|
||||
} else {
|
||||
addressLine.text = ""
|
||||
notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected";
|
||||
notice.color = AddressManager.isConnected ? "gray" : "crimson";
|
||||
// Display hostname, which includes ip address, localhost, and other non-placenames.
|
||||
location.text = (AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '');
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
focus = false;
|
||||
updateLocationText(false);
|
||||
if (visible) {
|
||||
fillDestinations();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,14 +25,25 @@ AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntit
|
|||
{
|
||||
_type = ACTION_TYPE_HOLD;
|
||||
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
if (myAvatar) {
|
||||
myAvatar->addHoldAction(this);
|
||||
}
|
||||
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold";
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold" << (void*)this;
|
||||
#endif
|
||||
}
|
||||
|
||||
AvatarActionHold::~AvatarActionHold() {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
if (myAvatar) {
|
||||
myAvatar->removeHoldAction(this);
|
||||
}
|
||||
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::~AvatarActionHold";
|
||||
qDebug() << "AvatarActionHold::~AvatarActionHold" << (void*)this;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -460,3 +471,40 @@ void AvatarActionHold::deserialize(QByteArray serializedArguments) {
|
|||
|
||||
forceBodyNonStatic();
|
||||
}
|
||||
|
||||
void AvatarActionHold::lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, const AnimPose& postAvatarUpdateRoomPose) {
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return;
|
||||
}
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
return;
|
||||
}
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
|
||||
if (!rigidBody) {
|
||||
return;
|
||||
}
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
|
||||
if (!holdingAvatar || !holdingAvatar->isMyAvatar()) {
|
||||
return;
|
||||
}
|
||||
|
||||
btTransform worldTrans = rigidBody->getWorldTransform();
|
||||
AnimPose worldBodyPose(glm::vec3(1), bulletToGLM(worldTrans.getRotation()), bulletToGLM(worldTrans.getOrigin()));
|
||||
|
||||
// transform the body transform into sensor space with the prePhysics sensor-to-world matrix.
|
||||
// then transform it back into world uisng the postAvatarUpdate sensor-to-world matrix.
|
||||
AnimPose newWorldBodyPose = postAvatarUpdateRoomPose * prePhysicsRoomPose.inverse() * worldBodyPose;
|
||||
|
||||
worldTrans.setOrigin(glmToBullet(newWorldBodyPose.trans));
|
||||
worldTrans.setRotation(glmToBullet(newWorldBodyPose.rot));
|
||||
rigidBody->setWorldTransform(worldTrans);
|
||||
|
||||
bool positionSuccess;
|
||||
ownerEntity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false);
|
||||
bool orientationSuccess;
|
||||
ownerEntity->setOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QUuid>
|
||||
|
||||
#include <EntityItem.h>
|
||||
#include <AnimPose.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
|
||||
#include "avatar/MyAvatar.h"
|
||||
|
@ -41,6 +42,8 @@ public:
|
|||
|
||||
virtual void prepareForPhysicsSimulation() override;
|
||||
|
||||
void lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, const AnimPose& postAvatarUpdateRoomPose);
|
||||
|
||||
private:
|
||||
void doKinematicUpdate(float deltaTimeStep);
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "Application.h"
|
||||
#include "devices/Faceshift.h"
|
||||
#include "AvatarManager.h"
|
||||
#include "AvatarActionHold.h"
|
||||
#include "Menu.h"
|
||||
#include "MyAvatar.h"
|
||||
#include "Physics.h"
|
||||
|
@ -1309,6 +1310,8 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
} else {
|
||||
_follow.deactivate();
|
||||
}
|
||||
|
||||
_prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix);
|
||||
}
|
||||
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
|
@ -1549,8 +1552,11 @@ void MyAvatar::postUpdate(float deltaTime) {
|
|||
|
||||
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
|
||||
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
|
||||
}
|
||||
|
||||
AnimPose postUpdateRoomPose(_sensorToWorldMatrix);
|
||||
|
||||
updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose);
|
||||
}
|
||||
|
||||
void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
||||
|
||||
|
@ -2257,3 +2263,35 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||
_holdActions.push_back(holdAction);
|
||||
}
|
||||
|
||||
// thread-safe
|
||||
void MyAvatar::removeHoldAction(AvatarActionHold* holdAction) {
|
||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||
auto iter = std::find(std::begin(_holdActions), std::end(_holdActions), holdAction);
|
||||
if (iter != std::end(_holdActions)) {
|
||||
_holdActions.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose) {
|
||||
EntityTreeRenderer* entityTreeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
// to prevent actions from adding or removing themselves from the _holdActions vector
|
||||
// while we are iterating, we need to enter a critical section.
|
||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||
|
||||
// lateAvatarUpdate will modify entity position & orientation, so we need an entity write lock
|
||||
entityTree->withWriteLock([&] {
|
||||
for (auto& holdAction : _holdActions) {
|
||||
holdAction->lateAvatarUpdate(prePhysicsPose, postUpdatePose);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "MyCharacterController.h"
|
||||
#include <ThreadSafeValueCache.h>
|
||||
|
||||
class AvatarActionHold;
|
||||
class ModelItemID;
|
||||
|
||||
enum DriveKeys {
|
||||
|
@ -277,6 +278,10 @@ public:
|
|||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
||||
void addHoldAction(AvatarActionHold* holdAction); // thread-safe
|
||||
void removeHoldAction(AvatarActionHold* holdAction); // thread-safe
|
||||
void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose);
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
@ -488,6 +493,10 @@ private:
|
|||
|
||||
bool _hmdLeanRecenterEnabled = true;
|
||||
|
||||
AnimPose _prePhysicsRoomPose;
|
||||
std::mutex _holdActionsMutex;
|
||||
std::vector<AvatarActionHold*> _holdActions;
|
||||
|
||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
||||
|
|
|
@ -125,3 +125,8 @@ void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool
|
|||
Q_ARG(const QString&, menuOption),
|
||||
Q_ARG(bool, isChecked));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ public slots:
|
|||
|
||||
bool isOptionChecked(const QString& menuOption);
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
void triggerOption(const QString& menuOption);
|
||||
|
||||
signals:
|
||||
void menuItemEvent(const QString& menuItem);
|
||||
|
|
|
@ -206,3 +206,7 @@ void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) {
|
|||
void WindowScriptingInterface::shareSnapshot(const QString& path) {
|
||||
qApp->shareSnapshot(path);
|
||||
}
|
||||
|
||||
bool WindowScriptingInterface::isPhysicsEnabled() {
|
||||
return qApp->isPhysicsEnabled();
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ public slots:
|
|||
void copyToClipboard(const QString& text);
|
||||
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
|
||||
void shareSnapshot(const QString& path);
|
||||
bool isPhysicsEnabled();
|
||||
|
||||
signals:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
|
|
|
@ -40,15 +40,6 @@ ApplicationOverlay::ApplicationOverlay()
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
_domainStatusBorder = geometryCache->allocateID();
|
||||
_magnifierBorder = geometryCache->allocateID();
|
||||
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
// threads, we need to use a sync object to deteremine when
|
||||
// the current UI texture is no longer being read from, and only
|
||||
// then release it back to the UI for re-use
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [&](GLuint textureId) {
|
||||
_uiTexture = textureId;
|
||||
});
|
||||
}
|
||||
|
||||
ApplicationOverlay::~ApplicationOverlay() {
|
||||
|
@ -96,18 +87,32 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
|||
|
||||
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
if (_uiTexture) {
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setModelTransform(Transform());
|
||||
batch.resetViewTransform();
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _uiTexture);
|
||||
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
||||
if (!_uiTexture) {
|
||||
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D([](uint32_t recycleTexture, void* recycleFence){
|
||||
DependencyManager::get<OffscreenUi>()->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_uiTexture->setSource(__FUNCTION__);
|
||||
}
|
||||
// Once we move UI rendering and screen rendering to different
|
||||
// threads, we need to use a sync object to deteremine when
|
||||
// the current UI texture is no longer being read from, and only
|
||||
// then release it back to the UI for re-use
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = offscreenUi->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_uiTexture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setModelTransform(Transform());
|
||||
batch.resetViewTransform();
|
||||
batch.setResourceTexture(0, _uiTexture);
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
|
||||
|
|
|
@ -40,13 +40,13 @@ private:
|
|||
|
||||
float _alpha{ 1.0f };
|
||||
float _trailingAudioLoudness{ 0.0f };
|
||||
uint32_t _uiTexture{ 0 };
|
||||
|
||||
int _domainStatusBorder;
|
||||
int _magnifierBorder;
|
||||
|
||||
ivec2 _previousBorderSize{ -1 };
|
||||
|
||||
gpu::TexturePointer _uiTexture;
|
||||
gpu::TexturePointer _overlayDepthTexture;
|
||||
gpu::TexturePointer _overlayColorTexture;
|
||||
gpu::FramebufferPointer _overlayFramebuffer;
|
||||
|
|
|
@ -73,16 +73,18 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
if (!_webSurface) {
|
||||
_webSurface = new OffscreenQmlSurface();
|
||||
auto deleter = [](OffscreenQmlSurface* webSurface) {
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
};
|
||||
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
|
||||
_webSurface->create(currentContext);
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||
_webSurface->load("WebView.qml");
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
}
|
||||
|
||||
|
@ -97,14 +99,22 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
} else {
|
||||
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D([webSurface](uint32_t recycleTexture, void* recycleFence) {
|
||||
webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
}));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setResourceTexture(0, _texture);
|
||||
batch.setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
||||
|
|
|
@ -41,9 +41,9 @@ public:
|
|||
virtual Web3DOverlay* createClone() const override;
|
||||
|
||||
private:
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
gpu::TexturePointer _texture;
|
||||
QString _url;
|
||||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
|
|
|
@ -129,7 +129,19 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
// Save the original GL context, because creating a QML surface will create a new context
|
||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
_webSurface = new OffscreenQmlSurface();
|
||||
|
||||
auto deleter = [](OffscreenQmlSurface* webSurface) {
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
};
|
||||
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), deleter);
|
||||
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
|
||||
_webSurface->create(currentContext);
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||
|
@ -140,9 +152,6 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
_webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
|
||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
||||
_texture = textureId;
|
||||
});
|
||||
// Restore the original GL context
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
|
||||
|
@ -217,20 +226,33 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
// without worrying about excessive overhead.
|
||||
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
||||
|
||||
if (!_texture) {
|
||||
auto webSurface = _webSurface;
|
||||
auto recycler = [webSurface] (uint32_t recycleTexture, void* recycleFence) {
|
||||
webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||
};
|
||||
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D(recycler));
|
||||
_texture->setSource(__FUNCTION__);
|
||||
}
|
||||
OffscreenQmlSurface::TextureAndFence newTextureAndFence;
|
||||
bool newTextureAvailable = _webSurface->fetchTexture(newTextureAndFence);
|
||||
if (newTextureAvailable) {
|
||||
_texture->setExternalTexture(newTextureAndFence.first, newTextureAndFence.second);
|
||||
}
|
||||
|
||||
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Web);
|
||||
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
bool success;
|
||||
batch.setModelTransform(getTransformToCenter(success));
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
if (_texture) {
|
||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
}
|
||||
batch.setResourceTexture(0, _texture);
|
||||
|
||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
|
||||
|
@ -344,16 +366,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
|||
_mouseMoveConnection = QMetaObject::Connection();
|
||||
QObject::disconnect(_hoverLeaveConnection);
|
||||
_hoverLeaveConnection = QMetaObject::Connection();
|
||||
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
auto webSurface = _webSurface;
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
|
||||
webSurface->deleteLater();
|
||||
});
|
||||
_webSurface = nullptr;
|
||||
_webSurface.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,10 +78,10 @@ private:
|
|||
void destroyWebSurface();
|
||||
glm::vec2 getWindowSize() const;
|
||||
|
||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||
QMetaObject::Connection _connection;
|
||||
uint32_t _texture{ 0 };
|
||||
ivec2 _lastPress{ INT_MIN };
|
||||
gpu::TexturePointer _texture;
|
||||
ivec2 _lastPress { INT_MIN };
|
||||
bool _pressed{ false };
|
||||
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLEscrow.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "GLLogging.h"
|
||||
|
||||
|
@ -265,6 +264,14 @@ private:
|
|||
// Helper methods
|
||||
void setupFbo();
|
||||
bool allowNewFrame(uint8_t fps);
|
||||
bool fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
void releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence);
|
||||
|
||||
// Texture management
|
||||
std::mutex _textureMutex;
|
||||
GLuint _latestTexture { 0 };
|
||||
GLsync _latestTextureFence { 0 };
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> _returnedTextures;
|
||||
|
||||
// Rendering members
|
||||
OffscreenGLCanvas _canvas;
|
||||
|
@ -274,7 +281,6 @@ private:
|
|||
GLuint _fbo { 0 };
|
||||
GLuint _depthStencil { 0 };
|
||||
RawTextureRecycler _textures { true };
|
||||
GLTextureEscrow _escrow;
|
||||
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
uvec2 _size{ 1920, 1080 };
|
||||
|
@ -406,9 +412,6 @@ void OffscreenQmlRenderThread::init() {
|
|||
|
||||
_renderControl->initialize(_canvas.getContext());
|
||||
setupFbo();
|
||||
_escrow.setRecycler([this](GLuint texture){
|
||||
_textures.recycleTexture(texture);
|
||||
});
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::cleanup() {
|
||||
|
@ -485,27 +488,93 @@ void OffscreenQmlRenderThread::render() {
|
|||
|
||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
||||
|
||||
// Clear out any pending textures to be returned
|
||||
{
|
||||
std::list<OffscreenQmlSurface::TextureAndFence> returnedTextures;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
returnedTextures.swap(_returnedTextures);
|
||||
}
|
||||
if (!returnedTextures.empty()) {
|
||||
for (const auto& textureAndFence : returnedTextures) {
|
||||
GLsync fence = static_cast<GLsync>(textureAndFence.second);
|
||||
if (fence) {
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
}
|
||||
_textures.recycleTexture(textureAndFence.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
GLuint texture = _textures.getNextTexture();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
|
||||
PROFILE_RANGE("qml_render->rendercontrol")
|
||||
_renderControl->render();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureFence) {
|
||||
}
|
||||
if (_latestTexture) {
|
||||
_textures.recycleTexture(_latestTexture);
|
||||
glDeleteSync(_latestTextureFence);
|
||||
_latestTexture = 0;
|
||||
_latestTextureFence = 0;
|
||||
}
|
||||
|
||||
_latestTextureFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
_latestTexture = texture;
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
glFlush();
|
||||
}
|
||||
|
||||
_quickWindow->resetOpenGLState();
|
||||
_escrow.submit(texture);
|
||||
_lastRenderTime = usecTimestampNow();
|
||||
} catch (std::runtime_error& error) {
|
||||
qWarning() << "Failed to render QML: " << error.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::fetchTexture(OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
textureAndFence = { 0, 0 };
|
||||
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 == _latestTexture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure writes to the latest texture are complete before before returning it for reading
|
||||
Q_ASSERT(0 != _latestTextureFence);
|
||||
textureAndFence = { _latestTexture, _latestTextureFence };
|
||||
_latestTextureFence = 0;
|
||||
_latestTexture = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OffscreenQmlRenderThread::releaseTexture(const OffscreenQmlSurface::TextureAndFence& textureAndFence) {
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
_returnedTextures.push_back(textureAndFence);
|
||||
}
|
||||
|
||||
bool OffscreenQmlRenderThread::allowNewFrame(uint8_t fps) {
|
||||
// If we already have a pending texture, don't render another one
|
||||
// i.e. don't render faster than the consumer context, since it wastes
|
||||
// GPU cycles on producing output that will never be seen
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_textureMutex);
|
||||
if (0 != _latestTexture) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto minRenderInterval = USECS_PER_SECOND / fps;
|
||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||
return (lastInterval > minRenderInterval);
|
||||
|
@ -726,13 +795,18 @@ void OffscreenQmlSurface::updateQuick() {
|
|||
// Lock the GUI size while syncing
|
||||
QMutexLocker locker(&(_renderer->_mutex));
|
||||
_renderer->_queue.add(RENDER);
|
||||
// FIXME need to find a better way to handle the render lockout than this locking of the main thread
|
||||
_renderer->_waitCondition.wait(&(_renderer->_mutex));
|
||||
_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_renderer->_escrow.fetchSignaledAndRelease(_currentTexture)) {
|
||||
emit textureUpdated(_currentTexture);
|
||||
}
|
||||
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) {
|
||||
return _renderer->fetchTexture(texture);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) {
|
||||
_renderer->releaseTexture(texture);
|
||||
}
|
||||
|
||||
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||
|
@ -752,7 +826,6 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
|
|||
return _mouseTranslator(originalPoint);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
//
|
||||
// Event handling customization
|
||||
|
|
|
@ -71,8 +71,17 @@ public:
|
|||
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
|
||||
bool eventFilter(QObject* originalDestination, QEvent* event) override;
|
||||
|
||||
using TextureAndFence = std::pair<uint32_t, void*>;
|
||||
// Checks to see if a new texture is available. If one is, the function returns true and
|
||||
// textureAndFence will be populated with the texture ID and a fence which will be signalled
|
||||
// when the texture is safe to read.
|
||||
// Returns false if no new texture is available
|
||||
bool fetchTexture(TextureAndFence& textureAndFence);
|
||||
// Release a previously acquired texture, along with a fence which indicates when reads from the
|
||||
// texture have completed.
|
||||
void releaseTexture(const TextureAndFence& textureAndFence);
|
||||
|
||||
signals:
|
||||
void textureUpdated(unsigned int texture);
|
||||
void focusObjectChanged(QObject* newFocus);
|
||||
void focusTextChanged(bool focusText);
|
||||
|
||||
|
@ -100,7 +109,6 @@ private:
|
|||
QQmlComponent* _qmlComponent{ nullptr };
|
||||
QQuickItem* _rootItem{ nullptr };
|
||||
QTimer _updateTimer;
|
||||
uint32_t _currentTexture{ 0 };
|
||||
bool _render{ false };
|
||||
bool _polish{ true };
|
||||
bool _paused{ true };
|
||||
|
|
|
@ -119,8 +119,6 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
|||
(&::gpu::gl::GLBackend::do_startNamedCall),
|
||||
(&::gpu::gl::GLBackend::do_stopNamedCall),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glActiveBindTexture),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glUniform1i),
|
||||
(&::gpu::gl::GLBackend::do_glUniform1f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform2f),
|
||||
|
@ -388,14 +386,6 @@ void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) {
|
|||
// As long as we don;t use several versions of shaders we can avoid this more complex code path
|
||||
// #define GET_UNIFORM_LOCATION(shaderUniformLoc) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo());
|
||||
#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc
|
||||
void GLBackend::do_glActiveBindTexture(const Batch& batch, size_t paramOffset) {
|
||||
glActiveTexture(batch._params[paramOffset + 2]._uint);
|
||||
glBindTexture(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._uint),
|
||||
batch._params[paramOffset + 0]._uint);
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
|
@ -568,6 +558,11 @@ void GLBackend::releaseBuffer(GLuint id, Size size) const {
|
|||
_buffersTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const {
|
||||
Lock lock(_trashMutex);
|
||||
_externalTexturesTrash.push_back({ id, recycler });
|
||||
}
|
||||
|
||||
void GLBackend::releaseTexture(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_texturesTrash.push_back({ id, size });
|
||||
|
@ -662,6 +657,19 @@ void GLBackend::recycle() const {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_externalTexturesTrash, externalTexturesTrash);
|
||||
}
|
||||
for (auto pair : externalTexturesTrash) {
|
||||
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
pair.second(pair.first, fence);
|
||||
decrementTextureGPUCount();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> programsTrash;
|
||||
{
|
||||
|
|
|
@ -130,8 +130,6 @@ public:
|
|||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
virtual void do_glActiveBindTexture(const Batch& batch, size_t paramOffset) final;
|
||||
|
||||
virtual void do_glUniform1i(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_glUniform1f(const Batch& batch, size_t paramOffset) final;
|
||||
virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final;
|
||||
|
@ -170,6 +168,7 @@ public:
|
|||
virtual bool isTextureReady(const TexturePointer& texture);
|
||||
|
||||
virtual void releaseBuffer(GLuint id, Size size) const;
|
||||
virtual void releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const;
|
||||
virtual void releaseTexture(GLuint id, Size size) const;
|
||||
virtual void releaseFramebuffer(GLuint id) const;
|
||||
virtual void releaseShader(GLuint id) const;
|
||||
|
@ -194,6 +193,7 @@ protected:
|
|||
mutable Mutex _trashMutex;
|
||||
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
|
||||
mutable std::list<std::pair<GLuint, Size>> _texturesTrash;
|
||||
mutable std::list<std::pair<GLuint, Texture::ExternalRecycler>> _externalTexturesTrash;
|
||||
mutable std::list<GLuint> _framebuffersTrash;
|
||||
mutable std::list<GLuint> _shadersTrash;
|
||||
mutable std::list<GLuint> _programsTrash;
|
||||
|
|
|
@ -136,6 +136,7 @@ float GLTexture::getMemoryPressure() {
|
|||
// Create the texture and allocate storage
|
||||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable) :
|
||||
GLObject(backend, texture, id),
|
||||
_external(false),
|
||||
_source(texture.source()),
|
||||
_storageStamp(texture.getStamp()),
|
||||
_target(getGLTextureType(texture)),
|
||||
|
@ -152,10 +153,41 @@ GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& tex
|
|||
Backend::setGPUObject(texture, this);
|
||||
}
|
||||
|
||||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id) :
|
||||
GLObject(backend, texture, id),
|
||||
_external(true),
|
||||
_source(texture.source()),
|
||||
_storageStamp(0),
|
||||
_target(getGLTextureType(texture)),
|
||||
_internalFormat(GL_RGBA8),
|
||||
// FIXME force mips to 0?
|
||||
_maxMip(texture.maxMip()),
|
||||
_minMip(texture.minMip()),
|
||||
_virtualSize(0),
|
||||
_transferrable(false)
|
||||
{
|
||||
Backend::setGPUObject(texture, this);
|
||||
|
||||
// FIXME Is this necessary?
|
||||
//withPreservedTexture([this] {
|
||||
// syncSampler();
|
||||
// if (_gpuObject.isAutogenerateMips()) {
|
||||
// generateMips();
|
||||
// }
|
||||
//});
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture() {
|
||||
if (_id) {
|
||||
auto backend = _backend.lock();
|
||||
if (backend) {
|
||||
auto backend = _backend.lock();
|
||||
if (backend) {
|
||||
if (_external) {
|
||||
auto recycler = _gpuObject.getExternalRecycler();
|
||||
if (recycler) {
|
||||
backend->releaseExternalTexture(_id, recycler);
|
||||
} else {
|
||||
qWarning() << "No recycler available for texture " << _id << " possible leak";
|
||||
}
|
||||
} else if (_id) {
|
||||
backend->releaseTexture(_id, _size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,42 @@ public:
|
|||
template <typename GLTextureType>
|
||||
static GLTextureType* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) {
|
||||
const Texture& texture = *texturePointer;
|
||||
|
||||
// Special case external textures
|
||||
if (texture.getUsage().isExternal()) {
|
||||
Texture::ExternalUpdates updates = texture.getUpdates();
|
||||
if (!updates.empty()) {
|
||||
Texture::ExternalRecycler recycler = texture.getExternalRecycler();
|
||||
Q_ASSERT(recycler);
|
||||
// Discard any superfluous updates
|
||||
while (updates.size() > 1) {
|
||||
const auto& update = updates.front();
|
||||
// Superfluous updates will never have been read, but we want to ensure the previous
|
||||
// writes to them are complete before they're written again, so return them with the
|
||||
// same fences they arrived with. This can happen on any thread because no GL context
|
||||
// work is involved
|
||||
recycler(update.first, update.second);
|
||||
updates.pop_front();
|
||||
}
|
||||
|
||||
// The last texture remaining is the one we'll use to create the GLTexture
|
||||
const auto& update = updates.front();
|
||||
// Check for a fence, and if it exists, inject a wait into the command stream, then destroy the fence
|
||||
if (update.second) {
|
||||
GLsync fence = static_cast<GLsync>(update.second);
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
}
|
||||
|
||||
// Create the new texture object (replaces any previous texture object)
|
||||
new GLTextureType(backend.shared_from_this(), texture, update.first);
|
||||
}
|
||||
|
||||
// Return the texture object (if any) associated with the texture, without extensive logic
|
||||
// (external textures are
|
||||
return Backend::getGPUObject<GLTextureType>(texture);
|
||||
}
|
||||
|
||||
if (!texture.isDefined()) {
|
||||
// NO texture definition yet so let's avoid thinking
|
||||
return nullptr;
|
||||
|
@ -110,6 +146,8 @@ public:
|
|||
|
||||
~GLTexture();
|
||||
|
||||
// Is this texture generated outside the GPU library?
|
||||
const bool _external;
|
||||
const GLuint& _texture { _id };
|
||||
const std::string _source;
|
||||
const Stamp _storageStamp;
|
||||
|
@ -159,6 +197,7 @@ protected:
|
|||
std::atomic<GLSyncState> _syncState { GLSyncState::Idle };
|
||||
|
||||
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable);
|
||||
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
||||
|
||||
void setSyncState(GLSyncState syncState) { _syncState = syncState; }
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
using Parent = GLTexture;
|
||||
GLuint allocate();
|
||||
public:
|
||||
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer, GLuint externalId);
|
||||
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer, bool transferrable);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -38,7 +38,13 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transf
|
|||
return GL41Texture::sync<GL41Texture>(*this, texture, transfer);
|
||||
}
|
||||
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable) : GLTexture(backend, texture, allocate(), transferrable) {}
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId)
|
||||
: GLTexture(backend, texture, externalId) {
|
||||
}
|
||||
|
||||
GL41Texture::GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable)
|
||||
: GLTexture(backend, texture, allocate(), transferrable) {
|
||||
}
|
||||
|
||||
void GL41Texture::generateMips() const {
|
||||
withPreservedTexture([&] {
|
||||
|
|
|
@ -33,6 +33,7 @@ public:
|
|||
static const uint32_t DEFAULT_PAGE_DIMENSION = 128;
|
||||
static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF;
|
||||
public:
|
||||
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId);
|
||||
GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable);
|
||||
~GL45Texture();
|
||||
|
||||
|
|
|
@ -243,6 +243,10 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) {
|
|||
return GL45Texture::getId<GL45Texture>(*this, texture, transfer);
|
||||
}
|
||||
|
||||
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint externalId)
|
||||
: GLTexture(backend, texture, externalId), _sparseInfo(*this), _transferState(*this) {
|
||||
}
|
||||
|
||||
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable)
|
||||
: GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) {
|
||||
|
||||
|
@ -252,7 +256,10 @@ GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture&
|
|||
}
|
||||
|
||||
GL45Texture::~GL45Texture() {
|
||||
qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str();
|
||||
// External textures cycle very quickly, so don't spam the log with messages about them.
|
||||
if (!_gpuObject.getUsage().isExternal()) {
|
||||
qCDebug(gpugl45logging) << "Destroying texture " << _id << " from source " << _source.c_str();
|
||||
}
|
||||
if (_sparseInfo.sparse) {
|
||||
// Remove this texture from the candidate list of derezzable textures
|
||||
{
|
||||
|
|
|
@ -294,6 +294,11 @@ void Batch::setUniformBuffer(uint32 slot, const BufferView& view) {
|
|||
|
||||
|
||||
void Batch::setResourceTexture(uint32 slot, const TexturePointer& texture) {
|
||||
if (texture && texture->getUsage().isExternal()) {
|
||||
auto recycler = texture->getExternalRecycler();
|
||||
Q_ASSERT(recycler);
|
||||
}
|
||||
|
||||
ADD_COMMAND(setResourceTexture);
|
||||
|
||||
_params.emplace_back(_textures.cache(texture));
|
||||
|
@ -506,18 +511,6 @@ void Batch::popProfileRange() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#define GL_TEXTURE0 0x84C0
|
||||
|
||||
void Batch::_glActiveBindTexture(uint32 unit, uint32 target, uint32 texture) {
|
||||
// clean the cache on the texture unit we are going to use so the next call to setResourceTexture() at the same slot works fine
|
||||
setResourceTexture(unit - GL_TEXTURE0, nullptr);
|
||||
|
||||
ADD_COMMAND(glActiveBindTexture);
|
||||
_params.emplace_back(texture);
|
||||
_params.emplace_back(target);
|
||||
_params.emplace_back(unit);
|
||||
}
|
||||
|
||||
void Batch::_glUniform1i(int32 location, int32 v0) {
|
||||
if (location < 0) {
|
||||
return;
|
||||
|
@ -680,4 +673,4 @@ void Batch::flush() {
|
|||
}
|
||||
buffer->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,9 +229,6 @@ public:
|
|||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
// For now, instead of calling the raw gl Call, use the equivalent call on the batch so the call is beeing recorded
|
||||
// THe implementation of these functions is in GLBackend.cpp
|
||||
|
||||
void _glActiveBindTexture(unsigned int unit, unsigned int target, unsigned int texture);
|
||||
|
||||
void _glUniform1i(int location, int v0);
|
||||
void _glUniform1f(int location, float v0);
|
||||
void _glUniform2f(int location, float v0, float v1);
|
||||
|
@ -314,8 +311,6 @@ public:
|
|||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
COMMAND_glActiveBindTexture,
|
||||
|
||||
COMMAND_glUniform1i,
|
||||
COMMAND_glUniform1f,
|
||||
COMMAND_glUniform2f,
|
||||
|
|
|
@ -238,6 +238,16 @@ bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Si
|
|||
return allocated == size;
|
||||
}
|
||||
|
||||
Texture* Texture::createExternal2D(const ExternalRecycler& recycler, const Sampler& sampler) {
|
||||
Texture* tex = new Texture();
|
||||
tex->_type = TEX_2D;
|
||||
tex->_maxMip = 0;
|
||||
tex->_sampler = sampler;
|
||||
tex->setUsage(Usage::Builder().withExternal().withColor());
|
||||
tex->setExternalRecycler(recycler);
|
||||
return tex;
|
||||
}
|
||||
|
||||
Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) {
|
||||
return create(TEX_1D, texelFormat, width, 1, 1, 1, 1, sampler);
|
||||
}
|
||||
|
@ -925,3 +935,16 @@ Vec3u Texture::evalMipDimensions(uint16 level) const {
|
|||
return glm::max(dimensions, Vec3u(1));
|
||||
}
|
||||
|
||||
void Texture::setExternalTexture(uint32 externalId, void* externalFence) {
|
||||
Lock lock(_externalMutex);
|
||||
_externalUpdates.push_back({ externalId, externalFence });
|
||||
}
|
||||
|
||||
Texture::ExternalUpdates Texture::getUpdates() const {
|
||||
Texture::ExternalUpdates result;
|
||||
{
|
||||
Lock lock(_externalMutex);
|
||||
_externalUpdates.swap(result);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -163,6 +163,10 @@ public:
|
|||
static void setEnableSparseTextures(bool enabled);
|
||||
static void setEnableIncrementalTextureTransfers(bool enabled);
|
||||
|
||||
using ExternalRecycler = std::function<void(uint32, void*)>;
|
||||
using ExternalIdAndFence = std::pair<uint32, void*>;
|
||||
using ExternalUpdates = std::list<ExternalIdAndFence>;
|
||||
|
||||
class Usage {
|
||||
public:
|
||||
enum FlagBit {
|
||||
|
@ -170,7 +174,7 @@ public:
|
|||
NORMAL, // Texture is a normal map
|
||||
ALPHA, // Texture has an alpha channel
|
||||
ALPHA_MASK, // Texture alpha channel is a Mask 0/1
|
||||
|
||||
EXTERNAL,
|
||||
NUM_FLAGS,
|
||||
};
|
||||
typedef std::bitset<NUM_FLAGS> Flags;
|
||||
|
@ -196,6 +200,7 @@ public:
|
|||
Builder& withNormal() { _flags.set(NORMAL); return (*this); }
|
||||
Builder& withAlpha() { _flags.set(ALPHA); return (*this); }
|
||||
Builder& withAlphaMask() { _flags.set(ALPHA_MASK); return (*this); }
|
||||
Builder& withExternal() { _flags.set(EXTERNAL); return (*this); }
|
||||
};
|
||||
Usage(const Builder& builder) : Usage(builder._flags) {}
|
||||
|
||||
|
@ -204,6 +209,7 @@ public:
|
|||
|
||||
bool isAlpha() const { return _flags[ALPHA]; }
|
||||
bool isAlphaMask() const { return _flags[ALPHA_MASK]; }
|
||||
bool isExternal() const { return _flags[EXTERNAL]; }
|
||||
|
||||
|
||||
bool operator==(const Usage& usage) { return (_flags == usage._flags); }
|
||||
|
@ -293,6 +299,7 @@ public:
|
|||
static Texture* create2D(const Element& texelFormat, uint16 width, uint16 height, const Sampler& sampler = Sampler());
|
||||
static Texture* create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, const Sampler& sampler = Sampler());
|
||||
static Texture* createCube(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler());
|
||||
static Texture* createExternal2D(const ExternalRecycler& recycler, const Sampler& sampler = Sampler());
|
||||
|
||||
Texture();
|
||||
Texture(const Texture& buf); // deep copy of the sysmem texture
|
||||
|
@ -458,9 +465,21 @@ public:
|
|||
// Only callable by the Backend
|
||||
void notifyMipFaceGPULoaded(uint16 level, uint8 face = 0) const { return _storage->notifyMipFaceGPULoaded(level, face); }
|
||||
|
||||
void setExternalTexture(uint32 externalId, void* externalFence);
|
||||
void setExternalRecycler(const ExternalRecycler& recycler) { _externalRecycler = recycler; }
|
||||
ExternalRecycler getExternalRecycler() const { return _externalRecycler; }
|
||||
|
||||
const GPUObjectPointer gpuObject {};
|
||||
|
||||
ExternalUpdates getUpdates() const;
|
||||
|
||||
protected:
|
||||
// Should only be accessed internally or by the backend sync function
|
||||
mutable Mutex _externalMutex;
|
||||
mutable std::list<ExternalIdAndFence> _externalUpdates;
|
||||
ExternalRecycler _externalRecycler;
|
||||
|
||||
|
||||
// Not strictly necessary, but incredibly useful for debugging
|
||||
std::string _source;
|
||||
std::unique_ptr< Storage > _storage;
|
||||
|
|
|
@ -62,7 +62,6 @@ public:
|
|||
MenuWrapper* getMenu(const QString& menuName);
|
||||
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
|
||||
|
||||
void triggerOption(const QString& menuOption);
|
||||
QAction* getActionForOption(const QString& menuOption);
|
||||
|
||||
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
|
@ -112,6 +111,8 @@ public slots:
|
|||
|
||||
void toggleDeveloperMenus();
|
||||
void toggleAdvancedMenus();
|
||||
|
||||
void triggerOption(const QString& menuOption);
|
||||
|
||||
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
|
||||
|
||||
|
|
95
scripts/developer/tests/performance/crowd-agent.js
Normal file
95
scripts/developer/tests/performance/crowd-agent.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
"use strict";
|
||||
/*jslint vars: true, plusplus: true*/
|
||||
/*global Agent, Avatar, Script, Entities, Vec3, Quat, print*/
|
||||
//
|
||||
// crowd-agent.js
|
||||
// scripts/developer/tests/performance/
|
||||
//
|
||||
// Created by Howard Stearns on 9/29/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
|
||||
//
|
||||
// Add this to domain-settings scripts url with n instances. It will lie dormant until
|
||||
// a script like summon.js calls up to n avatars to be around you.
|
||||
|
||||
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
|
||||
|
||||
print('crowd-agent version 1');
|
||||
|
||||
/* Observations:
|
||||
- File urls for AC scripts silently fail. Use a local server (e.g., python SimpleHTTPServer) for development.
|
||||
- URLs are cached regardless of server headers. Must use cache-defeating query parameters.
|
||||
- JSON.stringify(Avatar) silently fails (even when Agent.isAvatar)
|
||||
*/
|
||||
|
||||
function startAgent(parameters) { // Can also be used to update.
|
||||
print('crowd-agent starting params', JSON.stringify(parameters), JSON.stringify(Agent));
|
||||
Agent.isAvatar = true;
|
||||
if (parameters.position) {
|
||||
Avatar.position = parameters.position;
|
||||
}
|
||||
if (parameters.orientation) {
|
||||
Avatar.orientation = parameters.orientation;
|
||||
}
|
||||
if (parameters.skeletonModelURL) {
|
||||
Avatar.skeletonModelURL = parameters.skeletonModelURL;
|
||||
}
|
||||
if (parameters.animationData) {
|
||||
data = parameters.animationData;
|
||||
Avatar.startAnimation(data.url, data.fps || 30, 1.0, (data.loopFlag === undefined) ? true : data.loopFlag, false, data.startFrame || 0, data.endFrame);
|
||||
}
|
||||
print('crowd-agent avatars started');
|
||||
}
|
||||
function stopAgent(parameters) {
|
||||
Agent.isAvatar = false;
|
||||
print('crowd-agent stopped', JSON.stringify(parameters), JSON.stringify(Agent));
|
||||
}
|
||||
|
||||
function messageSend(message) {
|
||||
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
|
||||
}
|
||||
function messageHandler(channel, messageString, senderID) {
|
||||
if (channel !== MESSAGE_CHANNEL) {
|
||||
return;
|
||||
}
|
||||
print('crowd-agent message', channel, messageString, senderID);
|
||||
if (Agent.sessionUUID === senderID) { // ignore my own
|
||||
return;
|
||||
}
|
||||
var message = {};
|
||||
try {
|
||||
message = JSON.parse(messageString);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
switch (message.key) {
|
||||
case "HELO":
|
||||
messageSend({key: 'hello'}); // Allow the coordinator to count responses and make assignments.
|
||||
break;
|
||||
case 'hello': // ignore responses (e.g., from other agents)
|
||||
break;
|
||||
case "SUMMON":
|
||||
if (message.rcpt === Agent.sessionUUID) {
|
||||
startAgent(message);
|
||||
}
|
||||
break;
|
||||
case "STOP":
|
||||
if (message.rcpt === Agent.sessionUUID) {
|
||||
stopAgent(message);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("crowd-agent received unrecognized message:", channel, messageString, senderID);
|
||||
}
|
||||
}
|
||||
Messages.subscribe(MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.connect(messageHandler);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
print('crowd-agent shutting down');
|
||||
Messages.messageReceived.disconnect(messageHandler);
|
||||
Messages.unsubscribe(MESSAGE_CHANNEL);
|
||||
print('crowd-agent unsubscribed');
|
||||
});
|
|
@ -20,15 +20,18 @@ var EXPECTED_HMD_FRAMERATE = 90;
|
|||
var MAXIMUM_LOAD_TIME = 60; // seconds
|
||||
var MINIMUM_AVATARS = 25; // FIXME: not implemented yet. Requires agent scripts. Idea is to have them organize themselves to the right number.
|
||||
|
||||
var version = 1;
|
||||
var version = 2;
|
||||
function debug() {
|
||||
print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify)));
|
||||
}
|
||||
|
||||
var emptyishPlace = 'empty';
|
||||
var cachePlaces = ['localhost', 'Welcome'];
|
||||
var isInCachePlace = cachePlaces.indexOf(location.hostname) >= 0;
|
||||
var defaultPlace = isInCachePlace ? 'Playa' : location.hostname;
|
||||
function isNowIn(place) { // true if currently in specified place
|
||||
return location.hostname.toLowerCase() === place.toLowerCase();
|
||||
}
|
||||
|
||||
var cachePlaces = ['dev-Welcome', 'localhost']; // For now, list the lighter weight one first.
|
||||
var isInCachePlace = cachePlaces.some(isNowIn);
|
||||
var defaultPlace = isInCachePlace ? 'dev-Playa' : location.hostname;
|
||||
var prompt = "domain-check.js version " + version + "\n\nWhat place should we enter?";
|
||||
debug(cachePlaces, isInCachePlace, defaultPlace, prompt);
|
||||
var entryPlace = Window.prompt(prompt, defaultPlace);
|
||||
|
@ -73,10 +76,17 @@ function startTwirl(targetRotation, degreesPerUpdate, interval, strafeDistance,
|
|||
|
||||
function doLoad(place, continuationWithLoadTime) { // Go to place and call continuationWithLoadTime(loadTimeInSeconds)
|
||||
var start = Date.now(), timeout, onDownloadUpdate, finishedTwirl = false, loadTime;
|
||||
// There are two ways to learn of changes: connect to change signals, or poll.
|
||||
// Until we get reliable results, we'll poll.
|
||||
var POLL_INTERVAL = 500, poll;
|
||||
function setHandlers() {
|
||||
//Stats.downloadsPendingChanged.connect(onDownloadUpdate); downloadsChanged, and physics...
|
||||
poll = Script.setInterval(onDownloadUpdate, POLL_INTERVAL);
|
||||
}
|
||||
function clearHandlers() {
|
||||
debug('clearHandlers');
|
||||
Stats.downloadsPendingChanged.disconnect(onDownloadUpdate);
|
||||
Stats.downloadsChanged.disconnect(onDownloadUpdate);
|
||||
//Stats.downloadsPendingChanged.disconnect(onDownloadUpdate); downloadsChanged, and physics..
|
||||
Script.clearInterval(poll);
|
||||
}
|
||||
function waitForLoad(flag) {
|
||||
debug('entry', place, 'initial downloads/pending', Stats.downloads, Stats.downloadsPending);
|
||||
|
@ -93,13 +103,11 @@ function doLoad(place, continuationWithLoadTime) { // Go to place and call conti
|
|||
continuationWithLoadTime(loadTime);
|
||||
}
|
||||
});
|
||||
Stats.downloadsPendingChanged.connect(onDownloadUpdate);
|
||||
Stats.downloadsChanged.connect(onDownloadUpdate);
|
||||
setHandlers();
|
||||
}
|
||||
function isLoading() {
|
||||
// FIXME: This tells us when download are completed, but it doesn't tell us when the objects are parsed and loaded.
|
||||
// We really want something like _physicsEnabled, but that isn't signalled.
|
||||
return Stats.downloads || Stats.downloadsPending;
|
||||
// FIXME: We should also confirm that textures have loaded.
|
||||
return Stats.downloads || Stats.downloadsPending || !Window.isPhysicsEnabled();
|
||||
}
|
||||
onDownloadUpdate = function onDownloadUpdate() {
|
||||
debug('update downloads/pending', Stats.downloads, Stats.downloadsPending);
|
||||
|
@ -114,17 +122,9 @@ function doLoad(place, continuationWithLoadTime) { // Go to place and call conti
|
|||
}
|
||||
};
|
||||
|
||||
function doit() {
|
||||
debug('go', place);
|
||||
location.hostChanged.connect(waitForLoad);
|
||||
location.handleLookupString(place);
|
||||
}
|
||||
if (location.placename.toLowerCase() === place.toLowerCase()) {
|
||||
location.handleLookupString(emptyishPlace);
|
||||
Script.setTimeout(doit, 1000);
|
||||
} else {
|
||||
doit();
|
||||
}
|
||||
debug('go', place);
|
||||
location.hostChanged.connect(waitForLoad);
|
||||
location.handleLookupString(place);
|
||||
}
|
||||
|
||||
var config = Render.getConfig("Stats");
|
||||
|
@ -144,6 +144,7 @@ function doRender(continuation) {
|
|||
});
|
||||
}
|
||||
|
||||
var TELEPORT_PAUSE = 500;
|
||||
function maybePrepareCache(continuation) {
|
||||
var prepareCache = Window.confirm("Prepare cache?\n\n\
|
||||
Should we start with all and only those items cached that are encountered when visiting:\n" + cachePlaces.join(', ') + "\n\
|
||||
|
@ -151,8 +152,6 @@ If 'yes', cache will be cleared and we will visit these two, with a turn in each
|
|||
You would want to say 'no' (and make other preparations) if you were testing these places.");
|
||||
|
||||
if (prepareCache) {
|
||||
location.handleLookupString(emptyishPlace);
|
||||
Window.alert("Please do menu Edit->Reload Content (Clears all caches) and THEN press 'ok'.");
|
||||
function loadNext() {
|
||||
var place = cachePlaces.shift();
|
||||
doLoad(place, function (prepTime) {
|
||||
|
@ -164,16 +163,19 @@ You would want to say 'no' (and make other preparations) if you were testing the
|
|||
}
|
||||
});
|
||||
}
|
||||
loadNext();
|
||||
location.handleLookupString(cachePlaces[cachePlaces.length - 1]);
|
||||
Menu.triggerOption("Reload Content (Clears all caches)");
|
||||
Script.setTimeout(loadNext, TELEPORT_PAUSE);
|
||||
} else {
|
||||
continuation();
|
||||
location.handleLookupString(isNowIn(cachePlaces[0]) ? cachePlaces[1] : cachePlaces[0]);
|
||||
Script.setTimeout(continuation, TELEPORT_PAUSE);
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRunTribbles(continuation) {
|
||||
if (Window.confirm("Run tribbles?\n\n\
|
||||
At most, only one participant should say yes.")) {
|
||||
Script.load('http://howard-stearns.github.io/models/scripts/tests/performance/tribbles.js'); // FIXME: replace with AWS
|
||||
Script.load('http://cdn.highfidelity.com/davidkelly/production/scripts/tests/performance/tribbles.js');
|
||||
Script.setTimeout(continuation, 3000);
|
||||
} else {
|
||||
continuation();
|
||||
|
|
103
scripts/developer/tests/performance/summon.js
Normal file
103
scripts/developer/tests/performance/summon.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
"use strict";
|
||||
/*jslint vars: true, plusplus: true*/
|
||||
/*global Agent, Avatar, Script, Entities, Vec3, Quat, print*/
|
||||
//
|
||||
// crowd-agent.js
|
||||
// scripts/developer/tests/performance/
|
||||
//
|
||||
// Created by Howard Stearns on 9/29/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
|
||||
//
|
||||
// See crowd-agent.js
|
||||
|
||||
var version = 1;
|
||||
var label = "summon";
|
||||
function debug() {
|
||||
print.apply(null, [].concat.apply([label, version], [].map.call(arguments, JSON.stringify)));
|
||||
}
|
||||
var MINIMUM_AVATARS = 25;
|
||||
var DENSITY = 0.3; // square meters per person. Some say 10 sq ft is arm's length (0.9m^2), 4.5 is crowd (0.4m^2), 2.5 is mosh pit (0.2m^2).
|
||||
var spread = Math.sqrt(MINIMUM_AVATARS * DENSITY); // meters
|
||||
var turnSpread = 90; // How many degrees should turn from front range over.
|
||||
|
||||
function coord() { return (Math.random() * spread) - (spread / 2); } // randomly distribute a coordinate zero += spread/2.
|
||||
|
||||
|
||||
var summonedAgents = [];
|
||||
var MESSAGE_CHANNEL = "io.highfidelity.summon-crowd";
|
||||
function messageSend(message) {
|
||||
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
|
||||
}
|
||||
function messageHandler(channel, messageString, senderID) {
|
||||
if (channel !== MESSAGE_CHANNEL) {
|
||||
return;
|
||||
}
|
||||
debug('message', channel, messageString, senderID);
|
||||
if (MyAvatar.sessionUUID === senderID) { // ignore my own
|
||||
return;
|
||||
}
|
||||
var message = {}, avatarIdentifiers;
|
||||
try {
|
||||
message = JSON.parse(messageString);
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
switch (message.key) {
|
||||
case "hello":
|
||||
// There can be avatars we've summoned that do not yet appear in the AvatarList.
|
||||
avatarIdentifiers = AvatarList.getAvatarIdentifiers().filter(function (id) { return summonedAgents.indexOf(id) === -1; });
|
||||
debug('present', avatarIdentifiers, summonedAgents);
|
||||
if ((summonedAgents.length + avatarIdentifiers.length) < MINIMUM_AVATARS ) {
|
||||
summonedAgents.push(senderID);
|
||||
messageSend({
|
||||
key: 'SUMMON',
|
||||
rcpt: senderID,
|
||||
position: Vec3.sum(MyAvatar.position, {x: coord(), y: 0, z: coord()}),
|
||||
orientation: Quat.fromPitchYawRollDegrees(0, Quat.safeEulerAngles(MyAvatar.orientation).y + (turnSpread * (Math.random() - 0.5)), 0)/*,
|
||||
// No need to specify skeletonModelURL
|
||||
//skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/being_of_light/being_of_light.fbx",
|
||||
//skeletonModelURL: "file:///c:/Program Files/High Fidelity Release/resources/meshes/defaultAvatar_full.fst"/,
|
||||
animationData: { // T-pose until we get animations working again.
|
||||
"url": "file:///C:/Program Files/High Fidelity Release/resources/avatar/animations/idle.fbx",
|
||||
//"url": "file:///c:/Program Files/High Fidelity Release/resources/avatar/animations/walk_fwd.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 300.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
}*/
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "HELO":
|
||||
Window.alert("Someone else is summoning avatars.");
|
||||
break;
|
||||
default:
|
||||
print("crowd-agent received unrecognized message:", messageString);
|
||||
}
|
||||
}
|
||||
Messages.subscribe(MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.connect(messageHandler);
|
||||
Script.scriptEnding.connect(function () {
|
||||
debug('stopping agents', summonedAgents);
|
||||
summonedAgents.forEach(function (id) { messageSend({key: 'STOP', rcpt: id}); });
|
||||
debug('agents stopped');
|
||||
Script.setTimeout(function () {
|
||||
Messages.messageReceived.disconnect(messageHandler);
|
||||
Messages.unsubscribe(MESSAGE_CHANNEL);
|
||||
debug('unsubscribed');
|
||||
}, 500);
|
||||
});
|
||||
|
||||
messageSend({key: 'HELO'}); // Ask agents to report in now.
|
||||
Script.setTimeout(function () {
|
||||
if (0 === summonedAgents.length) {
|
||||
Window.alert("No agents reported.\n\Please run " + MINIMUM_AVATARS + " instances of\n\
|
||||
http://cdn.highfidelity.com/davidkelly/production/scripts/tests/performance/crowd-agent.js\n\
|
||||
on your domain server.");
|
||||
} else if (summonedAgents.length < MINIMUM_AVATARS) {
|
||||
Window.alert("Only " + summonedAgents.length + " of the expected " + MINIMUM_AVATARS + " agents reported in.");
|
||||
}
|
||||
}, 5000);
|
|
@ -54,42 +54,46 @@ function randomVector(range) {
|
|||
};
|
||||
}
|
||||
|
||||
Script.setInterval(function () {
|
||||
if (!Entities.serversExist() || !Entities.canRez()) {
|
||||
return;
|
||||
}
|
||||
if (totalCreated >= NUMBER_TO_CREATE) {
|
||||
print("Created " + totalCreated + " tribbles.");
|
||||
Script.stop();
|
||||
}
|
||||
if (!Entities.canRezTmp()) {
|
||||
Window.alert("Cannot create temp objects here.");
|
||||
Script.stop();
|
||||
} else {
|
||||
Script.setInterval(function () {
|
||||
if (!Entities.serversExist()) {
|
||||
return;
|
||||
}
|
||||
if (totalCreated >= NUMBER_TO_CREATE) {
|
||||
print("Created " + totalCreated + " tribbles.");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
var i, numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
||||
var parameters = JSON.stringify({
|
||||
moveTimeout: MOVE_TIMEOUT,
|
||||
moveRate: MOVE_RATE,
|
||||
editTimeout: EDIT_TIMEOUT,
|
||||
editRate: EDIT_RATE,
|
||||
debug: {flow: false, send: false, receive: false}
|
||||
});
|
||||
for (i = 0; (i < numToCreate) && (totalCreated < NUMBER_TO_CREATE); i++) {
|
||||
Entities.addEntity({
|
||||
userData: parameters,
|
||||
type: TYPE,
|
||||
name: "tribble-" + totalCreated,
|
||||
position: Vec3.sum(center, randomVector({ x: RANGE, y: RANGE, z: RANGE })),
|
||||
dimensions: {x: SIZE, y: SIZE, z: SIZE},
|
||||
color: {red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255},
|
||||
velocity: VELOCITY,
|
||||
angularVelocity: Vec3.multiply(Math.random(), ANGULAR_VELOCITY),
|
||||
damping: DAMPING,
|
||||
angularDamping: ANGULAR_DAMPING,
|
||||
gravity: GRAVITY,
|
||||
collisionsWillMove: true,
|
||||
lifetime: LIFETIME,
|
||||
script: Script.resolvePath("tribbleEntity.js")
|
||||
var i, numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
||||
var parameters = JSON.stringify({
|
||||
moveTimeout: MOVE_TIMEOUT,
|
||||
moveRate: MOVE_RATE,
|
||||
editTimeout: EDIT_TIMEOUT,
|
||||
editRate: EDIT_RATE,
|
||||
debug: {flow: false, send: false, receive: false}
|
||||
});
|
||||
for (i = 0; (i < numToCreate) && (totalCreated < NUMBER_TO_CREATE); i++) {
|
||||
Entities.addEntity({
|
||||
userData: parameters,
|
||||
type: TYPE,
|
||||
name: "tribble-" + totalCreated,
|
||||
position: Vec3.sum(center, randomVector({ x: RANGE, y: RANGE, z: RANGE })),
|
||||
dimensions: {x: SIZE, y: SIZE, z: SIZE},
|
||||
color: {red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255},
|
||||
velocity: VELOCITY,
|
||||
angularVelocity: Vec3.multiply(Math.random(), ANGULAR_VELOCITY),
|
||||
damping: DAMPING,
|
||||
angularDamping: ANGULAR_DAMPING,
|
||||
gravity: GRAVITY,
|
||||
collisionsWillMove: true,
|
||||
lifetime: LIFETIME,
|
||||
script: Script.resolvePath("tribbleEntity.js")
|
||||
});
|
||||
|
||||
totalCreated++;
|
||||
}
|
||||
}, SCRIPT_INTERVAL);
|
||||
|
||||
totalCreated++;
|
||||
}
|
||||
}, SCRIPT_INTERVAL);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue