mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 18:50:00 +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 "windows"
|
||||||
import "hifi"
|
import "hifi"
|
||||||
import "hifi/toolbars"
|
import "hifi/toolbars"
|
||||||
|
import "styles-uit" as HifiStyles
|
||||||
import "controls-uit" as HifiControls
|
import "controls-uit" as HifiControls
|
||||||
|
|
||||||
Window {
|
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 {
|
TextInput {
|
||||||
id: addressLine
|
id: addressLine
|
||||||
focus: true
|
focus: true
|
||||||
|
@ -179,14 +197,12 @@ Window {
|
||||||
right: parent.right
|
right: parent.right
|
||||||
leftMargin: forwardArrow.width
|
leftMargin: forwardArrow.width
|
||||||
rightMargin: forwardArrow.width / 2
|
rightMargin: forwardArrow.width / 2
|
||||||
topMargin: parent.inputAreaStep + hifi.layout.spacing
|
topMargin: parent.inputAreaStep + (2 * hifi.layout.spacing)
|
||||||
bottomMargin: parent.inputAreaStep + hifi.layout.spacing
|
bottomMargin: parent.inputAreaStep
|
||||||
}
|
}
|
||||||
font.pixelSize: hifi.fonts.pixelSize * root.scale * 0.75
|
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()
|
onTextChanged: filterChoicesByText()
|
||||||
|
onActiveFocusChanged: updateLocationText(focus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,12 +360,24 @@ Window {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
function updateLocationText(focus) {
|
||||||
if (visible) {
|
addressLine.text = "";
|
||||||
addressLine.forceActiveFocus()
|
if (focus) {
|
||||||
fillDestinations();
|
notice.text = "Go to a place, @user, path or network address";
|
||||||
|
notice.color = "gray";
|
||||||
} else {
|
} 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;
|
_type = ACTION_TYPE_HOLD;
|
||||||
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
|
_measuredLinearVelocities.resize(AvatarActionHold::velocitySmoothFrames);
|
||||||
|
|
||||||
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
if (myAvatar) {
|
||||||
|
myAvatar->addHoldAction(this);
|
||||||
|
}
|
||||||
|
|
||||||
#if WANT_DEBUG
|
#if WANT_DEBUG
|
||||||
qDebug() << "AvatarActionHold::AvatarActionHold";
|
qDebug() << "AvatarActionHold::AvatarActionHold" << (void*)this;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarActionHold::~AvatarActionHold() {
|
AvatarActionHold::~AvatarActionHold() {
|
||||||
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
if (myAvatar) {
|
||||||
|
myAvatar->removeHoldAction(this);
|
||||||
|
}
|
||||||
|
|
||||||
#if WANT_DEBUG
|
#if WANT_DEBUG
|
||||||
qDebug() << "AvatarActionHold::~AvatarActionHold";
|
qDebug() << "AvatarActionHold::~AvatarActionHold" << (void*)this;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,3 +471,40 @@ void AvatarActionHold::deserialize(QByteArray serializedArguments) {
|
||||||
|
|
||||||
forceBodyNonStatic();
|
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 <QUuid>
|
||||||
|
|
||||||
#include <EntityItem.h>
|
#include <EntityItem.h>
|
||||||
|
#include <AnimPose.h>
|
||||||
#include <ObjectActionSpring.h>
|
#include <ObjectActionSpring.h>
|
||||||
|
|
||||||
#include "avatar/MyAvatar.h"
|
#include "avatar/MyAvatar.h"
|
||||||
|
@ -41,6 +42,8 @@ public:
|
||||||
|
|
||||||
virtual void prepareForPhysicsSimulation() override;
|
virtual void prepareForPhysicsSimulation() override;
|
||||||
|
|
||||||
|
void lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, const AnimPose& postAvatarUpdateRoomPose);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void doKinematicUpdate(float deltaTimeStep);
|
void doKinematicUpdate(float deltaTimeStep);
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "devices/Faceshift.h"
|
#include "devices/Faceshift.h"
|
||||||
#include "AvatarManager.h"
|
#include "AvatarManager.h"
|
||||||
|
#include "AvatarActionHold.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "MyAvatar.h"
|
#include "MyAvatar.h"
|
||||||
#include "Physics.h"
|
#include "Physics.h"
|
||||||
|
@ -1309,6 +1310,8 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
||||||
} else {
|
} else {
|
||||||
_follow.deactivate();
|
_follow.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||||
|
@ -1549,8 +1552,11 @@ void MyAvatar::postUpdate(float deltaTime) {
|
||||||
|
|
||||||
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
|
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
|
||||||
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
|
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
|
||||||
}
|
|
||||||
|
|
||||||
|
AnimPose postUpdateRoomPose(_sensorToWorldMatrix);
|
||||||
|
|
||||||
|
updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose);
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
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 "MyCharacterController.h"
|
||||||
#include <ThreadSafeValueCache.h>
|
#include <ThreadSafeValueCache.h>
|
||||||
|
|
||||||
|
class AvatarActionHold;
|
||||||
class ModelItemID;
|
class ModelItemID;
|
||||||
|
|
||||||
enum DriveKeys {
|
enum DriveKeys {
|
||||||
|
@ -277,6 +278,10 @@ public:
|
||||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(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:
|
public slots:
|
||||||
void increaseSize();
|
void increaseSize();
|
||||||
void decreaseSize();
|
void decreaseSize();
|
||||||
|
@ -488,6 +493,10 @@ private:
|
||||||
|
|
||||||
bool _hmdLeanRecenterEnabled = true;
|
bool _hmdLeanRecenterEnabled = true;
|
||||||
|
|
||||||
|
AnimPose _prePhysicsRoomPose;
|
||||||
|
std::mutex _holdActionsMutex;
|
||||||
|
std::vector<AvatarActionHold*> _holdActions;
|
||||||
|
|
||||||
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
|
||||||
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
float AUDIO_ENERGY_CONSTANT { 0.000001f };
|
||||||
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };
|
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(const QString&, menuOption),
|
||||||
Q_ARG(bool, isChecked));
|
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);
|
bool isOptionChecked(const QString& menuOption);
|
||||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||||
|
|
||||||
|
void triggerOption(const QString& menuOption);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void menuItemEvent(const QString& menuItem);
|
void menuItemEvent(const QString& menuItem);
|
||||||
|
|
|
@ -206,3 +206,7 @@ void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) {
|
||||||
void WindowScriptingInterface::shareSnapshot(const QString& path) {
|
void WindowScriptingInterface::shareSnapshot(const QString& path) {
|
||||||
qApp->shareSnapshot(path);
|
qApp->shareSnapshot(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WindowScriptingInterface::isPhysicsEnabled() {
|
||||||
|
return qApp->isPhysicsEnabled();
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ public slots:
|
||||||
void copyToClipboard(const QString& text);
|
void copyToClipboard(const QString& text);
|
||||||
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
|
void takeSnapshot(bool notify = true, float aspectRatio = 0.0f);
|
||||||
void shareSnapshot(const QString& path);
|
void shareSnapshot(const QString& path);
|
||||||
|
bool isPhysicsEnabled();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void domainChanged(const QString& domainHostname);
|
void domainChanged(const QString& domainHostname);
|
||||||
|
|
|
@ -40,15 +40,6 @@ ApplicationOverlay::ApplicationOverlay()
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
_domainStatusBorder = geometryCache->allocateID();
|
_domainStatusBorder = geometryCache->allocateID();
|
||||||
_magnifierBorder = 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() {
|
ApplicationOverlay::~ApplicationOverlay() {
|
||||||
|
@ -96,18 +87,32 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
||||||
|
|
||||||
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
||||||
PROFILE_RANGE(__FUNCTION__);
|
PROFILE_RANGE(__FUNCTION__);
|
||||||
if (_uiTexture) {
|
|
||||||
gpu::Batch& batch = *renderArgs->_batch;
|
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
|
||||||
|
|
||||||
geometryCache->useSimpleDrawPipeline(batch);
|
if (!_uiTexture) {
|
||||||
batch.setProjectionTransform(mat4());
|
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D([](uint32_t recycleTexture, void* recycleFence){
|
||||||
batch.setModelTransform(Transform());
|
DependencyManager::get<OffscreenUi>()->releaseTexture({ recycleTexture, recycleFence });
|
||||||
batch.resetViewTransform();
|
}));
|
||||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _uiTexture);
|
_uiTexture->setSource(__FUNCTION__);
|
||||||
|
|
||||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
|
||||||
}
|
}
|
||||||
|
// 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) {
|
void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
|
||||||
|
|
|
@ -40,13 +40,13 @@ private:
|
||||||
|
|
||||||
float _alpha{ 1.0f };
|
float _alpha{ 1.0f };
|
||||||
float _trailingAudioLoudness{ 0.0f };
|
float _trailingAudioLoudness{ 0.0f };
|
||||||
uint32_t _uiTexture{ 0 };
|
|
||||||
|
|
||||||
int _domainStatusBorder;
|
int _domainStatusBorder;
|
||||||
int _magnifierBorder;
|
int _magnifierBorder;
|
||||||
|
|
||||||
ivec2 _previousBorderSize{ -1 };
|
ivec2 _previousBorderSize{ -1 };
|
||||||
|
|
||||||
|
gpu::TexturePointer _uiTexture;
|
||||||
gpu::TexturePointer _overlayDepthTexture;
|
gpu::TexturePointer _overlayDepthTexture;
|
||||||
gpu::TexturePointer _overlayColorTexture;
|
gpu::TexturePointer _overlayColorTexture;
|
||||||
gpu::FramebufferPointer _overlayFramebuffer;
|
gpu::FramebufferPointer _overlayFramebuffer;
|
||||||
|
|
|
@ -73,16 +73,18 @@ void Web3DOverlay::render(RenderArgs* args) {
|
||||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||||
QSurface * currentSurface = currentContext->surface();
|
QSurface * currentSurface = currentContext->surface();
|
||||||
if (!_webSurface) {
|
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->create(currentContext);
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||||
_webSurface->load("WebView.qml");
|
_webSurface->load("WebView.qml");
|
||||||
_webSurface->resume();
|
_webSurface->resume();
|
||||||
_webSurface->getRootItem()->setProperty("url", _url);
|
_webSurface->getRootItem()->setProperty("url", _url);
|
||||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
|
||||||
_texture = textureId;
|
|
||||||
});
|
|
||||||
currentContext->makeCurrent(currentSurface);
|
currentContext->makeCurrent(currentSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,14 +99,22 @@ void Web3DOverlay::render(RenderArgs* args) {
|
||||||
transform.postScale(vec3(getDimensions(), 1.0f));
|
transform.postScale(vec3(getDimensions(), 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(args->_batch);
|
if (!_texture) {
|
||||||
gpu::Batch& batch = *args->_batch;
|
auto webSurface = _webSurface;
|
||||||
if (_texture) {
|
_texture = gpu::TexturePointer(gpu::Texture::createExternal2D([webSurface](uint32_t recycleTexture, void* recycleFence) {
|
||||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
webSurface->releaseTexture({ recycleTexture, recycleFence });
|
||||||
} else {
|
}));
|
||||||
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
_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);
|
batch.setModelTransform(transform);
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
||||||
|
|
|
@ -41,9 +41,9 @@ public:
|
||||||
virtual Web3DOverlay* createClone() const override;
|
virtual Web3DOverlay* createClone() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||||
QMetaObject::Connection _connection;
|
QMetaObject::Connection _connection;
|
||||||
uint32_t _texture{ 0 };
|
gpu::TexturePointer _texture;
|
||||||
QString _url;
|
QString _url;
|
||||||
float _dpi;
|
float _dpi;
|
||||||
vec2 _resolution{ 640, 480 };
|
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
|
// Save the original GL context, because creating a QML surface will create a new context
|
||||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||||
QSurface * currentSurface = currentContext->surface();
|
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->create(currentContext);
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||||
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||||
|
@ -140,9 +152,6 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
||||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||||
_webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
|
_webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
|
||||||
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
|
|
||||||
_texture = textureId;
|
|
||||||
});
|
|
||||||
// Restore the original GL context
|
// Restore the original GL context
|
||||||
currentContext->makeCurrent(currentSurface);
|
currentContext->makeCurrent(currentSurface);
|
||||||
|
|
||||||
|
@ -217,20 +226,33 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
||||||
// without worrying about excessive overhead.
|
// without worrying about excessive overhead.
|
||||||
_webSurface->resize(QSize(windowSize.x, windowSize.y));
|
_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");
|
PerformanceTimer perfTimer("RenderableWebEntityItem::render");
|
||||||
Q_ASSERT(getType() == EntityTypes::Web);
|
Q_ASSERT(getType() == EntityTypes::Web);
|
||||||
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f);
|
||||||
|
|
||||||
Q_ASSERT(args->_batch);
|
Q_ASSERT(args->_batch);
|
||||||
gpu::Batch& batch = *args->_batch;
|
gpu::Batch& batch = *args->_batch;
|
||||||
|
|
||||||
bool success;
|
bool success;
|
||||||
batch.setModelTransform(getTransformToCenter(success));
|
batch.setModelTransform(getTransformToCenter(success));
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_texture) {
|
batch.setResourceTexture(0, _texture);
|
||||||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||||
|
|
||||||
|
@ -344,16 +366,7 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
||||||
_mouseMoveConnection = QMetaObject::Connection();
|
_mouseMoveConnection = QMetaObject::Connection();
|
||||||
QObject::disconnect(_hoverLeaveConnection);
|
QObject::disconnect(_hoverLeaveConnection);
|
||||||
_hoverLeaveConnection = QMetaObject::Connection();
|
_hoverLeaveConnection = QMetaObject::Connection();
|
||||||
|
_webSurface.reset();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,10 +78,10 @@ private:
|
||||||
void destroyWebSurface();
|
void destroyWebSurface();
|
||||||
glm::vec2 getWindowSize() const;
|
glm::vec2 getWindowSize() const;
|
||||||
|
|
||||||
OffscreenQmlSurface* _webSurface{ nullptr };
|
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||||
QMetaObject::Connection _connection;
|
QMetaObject::Connection _connection;
|
||||||
uint32_t _texture{ 0 };
|
gpu::TexturePointer _texture;
|
||||||
ivec2 _lastPress{ INT_MIN };
|
ivec2 _lastPress { INT_MIN };
|
||||||
bool _pressed{ false };
|
bool _pressed{ false };
|
||||||
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
||||||
uint64_t _lastRenderTime{ 0 };
|
uint64_t _lastRenderTime{ 0 };
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
|
|
||||||
#include "OffscreenGLCanvas.h"
|
#include "OffscreenGLCanvas.h"
|
||||||
#include "GLEscrow.h"
|
|
||||||
#include "GLHelpers.h"
|
#include "GLHelpers.h"
|
||||||
#include "GLLogging.h"
|
#include "GLLogging.h"
|
||||||
|
|
||||||
|
@ -265,6 +264,14 @@ private:
|
||||||
// Helper methods
|
// Helper methods
|
||||||
void setupFbo();
|
void setupFbo();
|
||||||
bool allowNewFrame(uint8_t fps);
|
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
|
// Rendering members
|
||||||
OffscreenGLCanvas _canvas;
|
OffscreenGLCanvas _canvas;
|
||||||
|
@ -274,7 +281,6 @@ private:
|
||||||
GLuint _fbo { 0 };
|
GLuint _fbo { 0 };
|
||||||
GLuint _depthStencil { 0 };
|
GLuint _depthStencil { 0 };
|
||||||
RawTextureRecycler _textures { true };
|
RawTextureRecycler _textures { true };
|
||||||
GLTextureEscrow _escrow;
|
|
||||||
|
|
||||||
uint64_t _lastRenderTime{ 0 };
|
uint64_t _lastRenderTime{ 0 };
|
||||||
uvec2 _size{ 1920, 1080 };
|
uvec2 _size{ 1920, 1080 };
|
||||||
|
@ -406,9 +412,6 @@ void OffscreenQmlRenderThread::init() {
|
||||||
|
|
||||||
_renderControl->initialize(_canvas.getContext());
|
_renderControl->initialize(_canvas.getContext());
|
||||||
setupFbo();
|
setupFbo();
|
||||||
_escrow.setRecycler([this](GLuint texture){
|
|
||||||
_textures.recycleTexture(texture);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenQmlRenderThread::cleanup() {
|
void OffscreenQmlRenderThread::cleanup() {
|
||||||
|
@ -485,27 +488,93 @@ void OffscreenQmlRenderThread::render() {
|
||||||
|
|
||||||
_quickWindow->setRenderTarget(_fbo, QSize(_size.x, _size.y));
|
_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 {
|
try {
|
||||||
GLuint texture = _textures.getNextTexture();
|
GLuint texture = _textures.getNextTexture();
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||||
PROFILE_RANGE("qml_render->rendercontrol")
|
PROFILE_RANGE("qml_render->rendercontrol")
|
||||||
_renderControl->render();
|
_renderControl->render();
|
||||||
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
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();
|
_quickWindow->resetOpenGLState();
|
||||||
_escrow.submit(texture);
|
|
||||||
_lastRenderTime = usecTimestampNow();
|
_lastRenderTime = usecTimestampNow();
|
||||||
} catch (std::runtime_error& error) {
|
} catch (std::runtime_error& error) {
|
||||||
qWarning() << "Failed to render QML: " << error.what();
|
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) {
|
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 minRenderInterval = USECS_PER_SECOND / fps;
|
||||||
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
auto lastInterval = usecTimestampNow() - _lastRenderTime;
|
||||||
return (lastInterval > minRenderInterval);
|
return (lastInterval > minRenderInterval);
|
||||||
|
@ -726,13 +795,18 @@ void OffscreenQmlSurface::updateQuick() {
|
||||||
// Lock the GUI size while syncing
|
// Lock the GUI size while syncing
|
||||||
QMutexLocker locker(&(_renderer->_mutex));
|
QMutexLocker locker(&(_renderer->_mutex));
|
||||||
_renderer->_queue.add(RENDER);
|
_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));
|
_renderer->_waitCondition.wait(&(_renderer->_mutex));
|
||||||
_render = false;
|
_render = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_renderer->_escrow.fetchSignaledAndRelease(_currentTexture)) {
|
bool OffscreenQmlSurface::fetchTexture(TextureAndFence& texture) {
|
||||||
emit textureUpdated(_currentTexture);
|
return _renderer->fetchTexture(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OffscreenQmlSurface::releaseTexture(const TextureAndFence& texture) {
|
||||||
|
_renderer->releaseTexture(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject) {
|
||||||
|
@ -752,7 +826,6 @@ QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QO
|
||||||
return _mouseTranslator(originalPoint);
|
return _mouseTranslator(originalPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Event handling customization
|
// Event handling customization
|
||||||
|
|
|
@ -71,8 +71,17 @@ public:
|
||||||
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
|
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
|
||||||
bool eventFilter(QObject* originalDestination, QEvent* event) override;
|
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:
|
signals:
|
||||||
void textureUpdated(unsigned int texture);
|
|
||||||
void focusObjectChanged(QObject* newFocus);
|
void focusObjectChanged(QObject* newFocus);
|
||||||
void focusTextChanged(bool focusText);
|
void focusTextChanged(bool focusText);
|
||||||
|
|
||||||
|
@ -100,7 +109,6 @@ private:
|
||||||
QQmlComponent* _qmlComponent{ nullptr };
|
QQmlComponent* _qmlComponent{ nullptr };
|
||||||
QQuickItem* _rootItem{ nullptr };
|
QQuickItem* _rootItem{ nullptr };
|
||||||
QTimer _updateTimer;
|
QTimer _updateTimer;
|
||||||
uint32_t _currentTexture{ 0 };
|
|
||||||
bool _render{ false };
|
bool _render{ false };
|
||||||
bool _polish{ true };
|
bool _polish{ true };
|
||||||
bool _paused{ true };
|
bool _paused{ true };
|
||||||
|
|
|
@ -119,8 +119,6 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
||||||
(&::gpu::gl::GLBackend::do_startNamedCall),
|
(&::gpu::gl::GLBackend::do_startNamedCall),
|
||||||
(&::gpu::gl::GLBackend::do_stopNamedCall),
|
(&::gpu::gl::GLBackend::do_stopNamedCall),
|
||||||
|
|
||||||
(&::gpu::gl::GLBackend::do_glActiveBindTexture),
|
|
||||||
|
|
||||||
(&::gpu::gl::GLBackend::do_glUniform1i),
|
(&::gpu::gl::GLBackend::do_glUniform1i),
|
||||||
(&::gpu::gl::GLBackend::do_glUniform1f),
|
(&::gpu::gl::GLBackend::do_glUniform1f),
|
||||||
(&::gpu::gl::GLBackend::do_glUniform2f),
|
(&::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
|
// 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) _pipeline._programShader->getUniformLocation(shaderUniformLoc, isStereo());
|
||||||
#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc
|
#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) {
|
void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) {
|
||||||
if (_pipeline._program == 0) {
|
if (_pipeline._program == 0) {
|
||||||
|
@ -568,6 +558,11 @@ void GLBackend::releaseBuffer(GLuint id, Size size) const {
|
||||||
_buffersTrash.push_back({ id, size });
|
_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 {
|
void GLBackend::releaseTexture(GLuint id, Size size) const {
|
||||||
Lock lock(_trashMutex);
|
Lock lock(_trashMutex);
|
||||||
_texturesTrash.push_back({ id, size });
|
_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;
|
std::list<GLuint> programsTrash;
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,8 +130,6 @@ public:
|
||||||
// TODO: As long as we have gl calls explicitely issued from interface
|
// 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
|
// 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
|
// 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_glUniform1i(const Batch& batch, size_t paramOffset) final;
|
||||||
virtual void do_glUniform1f(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;
|
virtual void do_glUniform2f(const Batch& batch, size_t paramOffset) final;
|
||||||
|
@ -170,6 +168,7 @@ public:
|
||||||
virtual bool isTextureReady(const TexturePointer& texture);
|
virtual bool isTextureReady(const TexturePointer& texture);
|
||||||
|
|
||||||
virtual void releaseBuffer(GLuint id, Size size) const;
|
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 releaseTexture(GLuint id, Size size) const;
|
||||||
virtual void releaseFramebuffer(GLuint id) const;
|
virtual void releaseFramebuffer(GLuint id) const;
|
||||||
virtual void releaseShader(GLuint id) const;
|
virtual void releaseShader(GLuint id) const;
|
||||||
|
@ -194,6 +193,7 @@ protected:
|
||||||
mutable Mutex _trashMutex;
|
mutable Mutex _trashMutex;
|
||||||
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
|
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
|
||||||
mutable std::list<std::pair<GLuint, Size>> _texturesTrash;
|
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> _framebuffersTrash;
|
||||||
mutable std::list<GLuint> _shadersTrash;
|
mutable std::list<GLuint> _shadersTrash;
|
||||||
mutable std::list<GLuint> _programsTrash;
|
mutable std::list<GLuint> _programsTrash;
|
||||||
|
|
|
@ -136,6 +136,7 @@ float GLTexture::getMemoryPressure() {
|
||||||
// Create the texture and allocate storage
|
// Create the texture and allocate storage
|
||||||
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable) :
|
GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, GLuint id, bool transferrable) :
|
||||||
GLObject(backend, texture, id),
|
GLObject(backend, texture, id),
|
||||||
|
_external(false),
|
||||||
_source(texture.source()),
|
_source(texture.source()),
|
||||||
_storageStamp(texture.getStamp()),
|
_storageStamp(texture.getStamp()),
|
||||||
_target(getGLTextureType(texture)),
|
_target(getGLTextureType(texture)),
|
||||||
|
@ -152,10 +153,41 @@ GLTexture::GLTexture(const std::weak_ptr<GLBackend>& backend, const Texture& tex
|
||||||
Backend::setGPUObject(texture, this);
|
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() {
|
GLTexture::~GLTexture() {
|
||||||
if (_id) {
|
auto backend = _backend.lock();
|
||||||
auto backend = _backend.lock();
|
if (backend) {
|
||||||
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);
|
backend->releaseTexture(_id, _size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,42 @@ public:
|
||||||
template <typename GLTextureType>
|
template <typename GLTextureType>
|
||||||
static GLTextureType* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) {
|
static GLTextureType* sync(GLBackend& backend, const TexturePointer& texturePointer, bool needTransfer) {
|
||||||
const Texture& texture = *texturePointer;
|
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()) {
|
if (!texture.isDefined()) {
|
||||||
// NO texture definition yet so let's avoid thinking
|
// NO texture definition yet so let's avoid thinking
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -110,6 +146,8 @@ public:
|
||||||
|
|
||||||
~GLTexture();
|
~GLTexture();
|
||||||
|
|
||||||
|
// Is this texture generated outside the GPU library?
|
||||||
|
const bool _external;
|
||||||
const GLuint& _texture { _id };
|
const GLuint& _texture { _id };
|
||||||
const std::string _source;
|
const std::string _source;
|
||||||
const Stamp _storageStamp;
|
const Stamp _storageStamp;
|
||||||
|
@ -159,6 +197,7 @@ protected:
|
||||||
std::atomic<GLSyncState> _syncState { GLSyncState::Idle };
|
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, bool transferrable);
|
||||||
|
GLTexture(const std::weak_ptr<gl::GLBackend>& backend, const Texture& texture, GLuint id);
|
||||||
|
|
||||||
void setSyncState(GLSyncState syncState) { _syncState = syncState; }
|
void setSyncState(GLSyncState syncState) { _syncState = syncState; }
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ public:
|
||||||
using Parent = GLTexture;
|
using Parent = GLTexture;
|
||||||
GLuint allocate();
|
GLuint allocate();
|
||||||
public:
|
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);
|
GL41Texture(const std::weak_ptr<GLBackend>& backend, const Texture& buffer, bool transferrable);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -38,7 +38,13 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texture, bool transf
|
||||||
return GL41Texture::sync<GL41Texture>(*this, texture, transfer);
|
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 {
|
void GL41Texture::generateMips() const {
|
||||||
withPreservedTexture([&] {
|
withPreservedTexture([&] {
|
||||||
|
|
|
@ -33,6 +33,7 @@ public:
|
||||||
static const uint32_t DEFAULT_PAGE_DIMENSION = 128;
|
static const uint32_t DEFAULT_PAGE_DIMENSION = 128;
|
||||||
static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF;
|
static const uint32_t DEFAULT_MAX_SPARSE_LEVEL = 0xFFFF;
|
||||||
public:
|
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(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable);
|
||||||
~GL45Texture();
|
~GL45Texture();
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,10 @@ GLuint GL45Backend::getTextureID(const TexturePointer& texture, bool transfer) {
|
||||||
return GL45Texture::getId<GL45Texture>(*this, texture, 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)
|
GL45Texture::GL45Texture(const std::weak_ptr<GLBackend>& backend, const Texture& texture, bool transferrable)
|
||||||
: GLTexture(backend, texture, allocate(texture), transferrable), _sparseInfo(*this), _transferState(*this) {
|
: 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() {
|
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) {
|
if (_sparseInfo.sparse) {
|
||||||
// Remove this texture from the candidate list of derezzable textures
|
// 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) {
|
void Batch::setResourceTexture(uint32 slot, const TexturePointer& texture) {
|
||||||
|
if (texture && texture->getUsage().isExternal()) {
|
||||||
|
auto recycler = texture->getExternalRecycler();
|
||||||
|
Q_ASSERT(recycler);
|
||||||
|
}
|
||||||
|
|
||||||
ADD_COMMAND(setResourceTexture);
|
ADD_COMMAND(setResourceTexture);
|
||||||
|
|
||||||
_params.emplace_back(_textures.cache(texture));
|
_params.emplace_back(_textures.cache(texture));
|
||||||
|
@ -506,18 +511,6 @@ void Batch::popProfileRange() {
|
||||||
#endif
|
#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) {
|
void Batch::_glUniform1i(int32 location, int32 v0) {
|
||||||
if (location < 0) {
|
if (location < 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -680,4 +673,4 @@ void Batch::flush() {
|
||||||
}
|
}
|
||||||
buffer->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
|
// 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
|
// 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
|
// 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 _glUniform1i(int location, int v0);
|
||||||
void _glUniform1f(int location, float v0);
|
void _glUniform1f(int location, float v0);
|
||||||
void _glUniform2f(int location, float v0, float v1);
|
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
|
// 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
|
// 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
|
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||||
COMMAND_glActiveBindTexture,
|
|
||||||
|
|
||||||
COMMAND_glUniform1i,
|
COMMAND_glUniform1i,
|
||||||
COMMAND_glUniform1f,
|
COMMAND_glUniform1f,
|
||||||
COMMAND_glUniform2f,
|
COMMAND_glUniform2f,
|
||||||
|
|
|
@ -238,6 +238,16 @@ bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Si
|
||||||
return allocated == size;
|
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) {
|
Texture* Texture::create1D(const Element& texelFormat, uint16 width, const Sampler& sampler) {
|
||||||
return create(TEX_1D, texelFormat, width, 1, 1, 1, 1, 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));
|
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 setEnableSparseTextures(bool enabled);
|
||||||
static void setEnableIncrementalTextureTransfers(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 {
|
class Usage {
|
||||||
public:
|
public:
|
||||||
enum FlagBit {
|
enum FlagBit {
|
||||||
|
@ -170,7 +174,7 @@ public:
|
||||||
NORMAL, // Texture is a normal map
|
NORMAL, // Texture is a normal map
|
||||||
ALPHA, // Texture has an alpha channel
|
ALPHA, // Texture has an alpha channel
|
||||||
ALPHA_MASK, // Texture alpha channel is a Mask 0/1
|
ALPHA_MASK, // Texture alpha channel is a Mask 0/1
|
||||||
|
EXTERNAL,
|
||||||
NUM_FLAGS,
|
NUM_FLAGS,
|
||||||
};
|
};
|
||||||
typedef std::bitset<NUM_FLAGS> Flags;
|
typedef std::bitset<NUM_FLAGS> Flags;
|
||||||
|
@ -196,6 +200,7 @@ public:
|
||||||
Builder& withNormal() { _flags.set(NORMAL); return (*this); }
|
Builder& withNormal() { _flags.set(NORMAL); return (*this); }
|
||||||
Builder& withAlpha() { _flags.set(ALPHA); return (*this); }
|
Builder& withAlpha() { _flags.set(ALPHA); return (*this); }
|
||||||
Builder& withAlphaMask() { _flags.set(ALPHA_MASK); return (*this); }
|
Builder& withAlphaMask() { _flags.set(ALPHA_MASK); return (*this); }
|
||||||
|
Builder& withExternal() { _flags.set(EXTERNAL); return (*this); }
|
||||||
};
|
};
|
||||||
Usage(const Builder& builder) : Usage(builder._flags) {}
|
Usage(const Builder& builder) : Usage(builder._flags) {}
|
||||||
|
|
||||||
|
@ -204,6 +209,7 @@ public:
|
||||||
|
|
||||||
bool isAlpha() const { return _flags[ALPHA]; }
|
bool isAlpha() const { return _flags[ALPHA]; }
|
||||||
bool isAlphaMask() const { return _flags[ALPHA_MASK]; }
|
bool isAlphaMask() const { return _flags[ALPHA_MASK]; }
|
||||||
|
bool isExternal() const { return _flags[EXTERNAL]; }
|
||||||
|
|
||||||
|
|
||||||
bool operator==(const Usage& usage) { return (_flags == usage._flags); }
|
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* 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* 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* createCube(const Element& texelFormat, uint16 width, const Sampler& sampler = Sampler());
|
||||||
|
static Texture* createExternal2D(const ExternalRecycler& recycler, const Sampler& sampler = Sampler());
|
||||||
|
|
||||||
Texture();
|
Texture();
|
||||||
Texture(const Texture& buf); // deep copy of the sysmem texture
|
Texture(const Texture& buf); // deep copy of the sysmem texture
|
||||||
|
@ -458,9 +465,21 @@ public:
|
||||||
// Only callable by the Backend
|
// Only callable by the Backend
|
||||||
void notifyMipFaceGPULoaded(uint16 level, uint8 face = 0) const { return _storage->notifyMipFaceGPULoaded(level, face); }
|
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 {};
|
const GPUObjectPointer gpuObject {};
|
||||||
|
|
||||||
|
ExternalUpdates getUpdates() const;
|
||||||
|
|
||||||
protected:
|
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
|
// Not strictly necessary, but incredibly useful for debugging
|
||||||
std::string _source;
|
std::string _source;
|
||||||
std::unique_ptr< Storage > _storage;
|
std::unique_ptr< Storage > _storage;
|
||||||
|
|
|
@ -62,7 +62,6 @@ public:
|
||||||
MenuWrapper* getMenu(const QString& menuName);
|
MenuWrapper* getMenu(const QString& menuName);
|
||||||
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
|
MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu);
|
||||||
|
|
||||||
void triggerOption(const QString& menuOption);
|
|
||||||
QAction* getActionForOption(const QString& menuOption);
|
QAction* getActionForOption(const QString& menuOption);
|
||||||
|
|
||||||
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||||
|
@ -112,6 +111,8 @@ public slots:
|
||||||
|
|
||||||
void toggleDeveloperMenus();
|
void toggleDeveloperMenus();
|
||||||
void toggleAdvancedMenus();
|
void toggleAdvancedMenus();
|
||||||
|
|
||||||
|
void triggerOption(const QString& menuOption);
|
||||||
|
|
||||||
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
|
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 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 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() {
|
function debug() {
|
||||||
print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify)));
|
print.apply(null, [].concat.apply(['hrs fixme', version], [].map.call(arguments, JSON.stringify)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyishPlace = 'empty';
|
function isNowIn(place) { // true if currently in specified place
|
||||||
var cachePlaces = ['localhost', 'Welcome'];
|
return location.hostname.toLowerCase() === place.toLowerCase();
|
||||||
var isInCachePlace = cachePlaces.indexOf(location.hostname) >= 0;
|
}
|
||||||
var defaultPlace = isInCachePlace ? 'Playa' : location.hostname;
|
|
||||||
|
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?";
|
var prompt = "domain-check.js version " + version + "\n\nWhat place should we enter?";
|
||||||
debug(cachePlaces, isInCachePlace, defaultPlace, prompt);
|
debug(cachePlaces, isInCachePlace, defaultPlace, prompt);
|
||||||
var entryPlace = Window.prompt(prompt, defaultPlace);
|
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)
|
function doLoad(place, continuationWithLoadTime) { // Go to place and call continuationWithLoadTime(loadTimeInSeconds)
|
||||||
var start = Date.now(), timeout, onDownloadUpdate, finishedTwirl = false, loadTime;
|
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() {
|
function clearHandlers() {
|
||||||
debug('clearHandlers');
|
debug('clearHandlers');
|
||||||
Stats.downloadsPendingChanged.disconnect(onDownloadUpdate);
|
//Stats.downloadsPendingChanged.disconnect(onDownloadUpdate); downloadsChanged, and physics..
|
||||||
Stats.downloadsChanged.disconnect(onDownloadUpdate);
|
Script.clearInterval(poll);
|
||||||
}
|
}
|
||||||
function waitForLoad(flag) {
|
function waitForLoad(flag) {
|
||||||
debug('entry', place, 'initial downloads/pending', Stats.downloads, Stats.downloadsPending);
|
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);
|
continuationWithLoadTime(loadTime);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Stats.downloadsPendingChanged.connect(onDownloadUpdate);
|
setHandlers();
|
||||||
Stats.downloadsChanged.connect(onDownloadUpdate);
|
|
||||||
}
|
}
|
||||||
function isLoading() {
|
function isLoading() {
|
||||||
// FIXME: This tells us when download are completed, but it doesn't tell us when the objects are parsed and loaded.
|
// FIXME: We should also confirm that textures have loaded.
|
||||||
// We really want something like _physicsEnabled, but that isn't signalled.
|
return Stats.downloads || Stats.downloadsPending || !Window.isPhysicsEnabled();
|
||||||
return Stats.downloads || Stats.downloadsPending;
|
|
||||||
}
|
}
|
||||||
onDownloadUpdate = function onDownloadUpdate() {
|
onDownloadUpdate = function onDownloadUpdate() {
|
||||||
debug('update downloads/pending', Stats.downloads, Stats.downloadsPending);
|
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);
|
||||||
debug('go', place);
|
location.hostChanged.connect(waitForLoad);
|
||||||
location.hostChanged.connect(waitForLoad);
|
location.handleLookupString(place);
|
||||||
location.handleLookupString(place);
|
|
||||||
}
|
|
||||||
if (location.placename.toLowerCase() === place.toLowerCase()) {
|
|
||||||
location.handleLookupString(emptyishPlace);
|
|
||||||
Script.setTimeout(doit, 1000);
|
|
||||||
} else {
|
|
||||||
doit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = Render.getConfig("Stats");
|
var config = Render.getConfig("Stats");
|
||||||
|
@ -144,6 +144,7 @@ function doRender(continuation) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TELEPORT_PAUSE = 500;
|
||||||
function maybePrepareCache(continuation) {
|
function maybePrepareCache(continuation) {
|
||||||
var prepareCache = Window.confirm("Prepare cache?\n\n\
|
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\
|
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.");
|
You would want to say 'no' (and make other preparations) if you were testing these places.");
|
||||||
|
|
||||||
if (prepareCache) {
|
if (prepareCache) {
|
||||||
location.handleLookupString(emptyishPlace);
|
|
||||||
Window.alert("Please do menu Edit->Reload Content (Clears all caches) and THEN press 'ok'.");
|
|
||||||
function loadNext() {
|
function loadNext() {
|
||||||
var place = cachePlaces.shift();
|
var place = cachePlaces.shift();
|
||||||
doLoad(place, function (prepTime) {
|
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 {
|
} else {
|
||||||
continuation();
|
location.handleLookupString(isNowIn(cachePlaces[0]) ? cachePlaces[1] : cachePlaces[0]);
|
||||||
|
Script.setTimeout(continuation, TELEPORT_PAUSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeRunTribbles(continuation) {
|
function maybeRunTribbles(continuation) {
|
||||||
if (Window.confirm("Run tribbles?\n\n\
|
if (Window.confirm("Run tribbles?\n\n\
|
||||||
At most, only one participant should say yes.")) {
|
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);
|
Script.setTimeout(continuation, 3000);
|
||||||
} else {
|
} else {
|
||||||
continuation();
|
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.canRezTmp()) {
|
||||||
if (!Entities.serversExist() || !Entities.canRez()) {
|
Window.alert("Cannot create temp objects here.");
|
||||||
return;
|
Script.stop();
|
||||||
}
|
} else {
|
||||||
if (totalCreated >= NUMBER_TO_CREATE) {
|
Script.setInterval(function () {
|
||||||
print("Created " + totalCreated + " tribbles.");
|
if (!Entities.serversExist()) {
|
||||||
Script.stop();
|
return;
|
||||||
}
|
}
|
||||||
|
if (totalCreated >= NUMBER_TO_CREATE) {
|
||||||
|
print("Created " + totalCreated + " tribbles.");
|
||||||
|
Script.stop();
|
||||||
|
}
|
||||||
|
|
||||||
var i, numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
var i, numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
||||||
var parameters = JSON.stringify({
|
var parameters = JSON.stringify({
|
||||||
moveTimeout: MOVE_TIMEOUT,
|
moveTimeout: MOVE_TIMEOUT,
|
||||||
moveRate: MOVE_RATE,
|
moveRate: MOVE_RATE,
|
||||||
editTimeout: EDIT_TIMEOUT,
|
editTimeout: EDIT_TIMEOUT,
|
||||||
editRate: EDIT_RATE,
|
editRate: EDIT_RATE,
|
||||||
debug: {flow: false, send: false, receive: false}
|
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")
|
|
||||||
});
|
});
|
||||||
|
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++;
|
totalCreated++;
|
||||||
}
|
}
|
||||||
}, SCRIPT_INTERVAL);
|
}, SCRIPT_INTERVAL);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue