mirror of
https://github.com/overte-org/overte.git
synced 2025-04-18 14:57:00 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into orange
This commit is contained in:
commit
5f80d7e53e
114 changed files with 2518 additions and 2288 deletions
|
@ -124,7 +124,7 @@ AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
|
|||
|
||||
|
||||
int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent,
|
||||
int& truePacketsSent) {
|
||||
int& truePacketsSent, bool dontSuppressDuplicate) {
|
||||
OctreeServer::didHandlePacketSend(this);
|
||||
|
||||
// if we're shutting down, then exit early
|
||||
|
@ -141,7 +141,7 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
|||
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
|
||||
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
|
||||
// this rate control savings.
|
||||
if (nodeData->shouldSuppressDuplicatePacket()) {
|
||||
if (!dontSuppressDuplicate && nodeData->shouldSuppressDuplicatePacket()) {
|
||||
nodeData->resetOctreePacket(); // we still need to reset it though!
|
||||
return packetsSent; // without sending...
|
||||
}
|
||||
|
@ -356,7 +356,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
//unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
||||
//unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
||||
|
||||
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent, isFullScene);
|
||||
packetsSentThisInterval += packetsJustSent;
|
||||
|
||||
// If we're starting a full scene, then definitely we want to empty the elementBag
|
||||
|
@ -582,6 +582,18 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
if (nodeData->elementBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
|
||||
// If this was a full scene then make sure we really send out a stats packet at this point so that
|
||||
// the clients will know the scene is stable
|
||||
if (isFullScene) {
|
||||
int thisTrueBytesSent = 0;
|
||||
int thisTruePacketsSent = 0;
|
||||
nodeData->stats.sceneCompleted();
|
||||
int packetsJustSent = handlePacketSend(node, nodeData, thisTrueBytesSent, thisTruePacketsSent, true);
|
||||
_totalBytes += thisTrueBytesSent;
|
||||
_totalPackets += thisTruePacketsSent;
|
||||
truePacketsSent += packetsJustSent;
|
||||
}
|
||||
}
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
|
|
|
@ -50,7 +50,7 @@ protected:
|
|||
virtual bool process();
|
||||
|
||||
private:
|
||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent);
|
||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent, bool dontSuppressDuplicate = false);
|
||||
int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged);
|
||||
|
||||
|
||||
|
|
|
@ -981,7 +981,7 @@ void OctreeServer::readConfiguration() {
|
|||
_settings = settingsSectionObject; // keep this for later
|
||||
|
||||
if (!readOptionString(QString("statusHost"), settingsSectionObject, _statusHost) || _statusHost.isEmpty()) {
|
||||
_statusHost = getLocalAddress().toString();
|
||||
_statusHost = getGuessedLocalAddress().toString();
|
||||
}
|
||||
qDebug("statusHost=%s", qPrintable(_statusHost));
|
||||
|
||||
|
|
|
@ -89,6 +89,9 @@ var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
|
|||
var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode";
|
||||
var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";
|
||||
|
||||
|
||||
// marketplace info, etc. not quite ready yet.
|
||||
var SHOULD_SHOW_PROPERTY_MENU = false;
|
||||
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."
|
||||
var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."
|
||||
|
||||
|
@ -342,6 +345,7 @@ var toolBar = (function() {
|
|||
if (active && !Entities.canAdjustLocks()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
} else {
|
||||
Messages.sendLocalMessage("edit-events", JSON.stringify({enabled: active}));
|
||||
isActive = active;
|
||||
if (!isActive) {
|
||||
entityListTool.setVisible(false);
|
||||
|
@ -1022,6 +1026,9 @@ function mouseClickEvent(event) {
|
|||
} else if (event.isRightButton) {
|
||||
var result = findClickedEntity(event);
|
||||
if (result) {
|
||||
if (SHOULD_SHOW_PROPERTY_MENU !== true) {
|
||||
return;
|
||||
}
|
||||
var properties = Entities.getEntityProperties(result.entityID);
|
||||
if (properties.marketplaceID) {
|
||||
propertyMenu.marketplaceID = properties.marketplaceID;
|
||||
|
@ -1895,9 +1902,11 @@ PopupMenu = function() {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
var propertyMenu = PopupMenu();
|
||||
|
||||
propertyMenu.onSelectMenuItem = function(name) {
|
||||
|
||||
if (propertyMenu.marketplaceID) {
|
||||
showMarketplace(propertyMenu.marketplaceID);
|
||||
}
|
||||
|
@ -1941,4 +1950,4 @@ entityListTool.webView.eventBridge.webEventReceived.connect(function(data) {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,6 +49,8 @@ var IDENTITY_QUAT = {
|
|||
};
|
||||
var ACTION_TTL = 10; // seconds
|
||||
|
||||
var enabled = true;
|
||||
|
||||
function getTag() {
|
||||
return "grab-" + MyAvatar.sessionUUID;
|
||||
}
|
||||
|
@ -306,6 +308,10 @@ Grabber.prototype.computeNewGrabPlane = function() {
|
|||
}
|
||||
|
||||
Grabber.prototype.pressEvent = function(event) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) {
|
||||
return;
|
||||
}
|
||||
|
@ -559,8 +565,29 @@ function keyReleaseEvent(event) {
|
|||
grabber.keyReleaseEvent(event);
|
||||
}
|
||||
|
||||
function editEvent(channel, message, sender, localOnly) {
|
||||
if (channel != "edit-events") {
|
||||
return;
|
||||
}
|
||||
if (sender != MyAvatar.sessionUUID) {
|
||||
return;
|
||||
}
|
||||
if (!localOnly) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
if ("enabled" in data) {
|
||||
enabled = !data["enabled"];
|
||||
}
|
||||
} catch(e) {
|
||||
}
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(pressEvent);
|
||||
Controller.mouseMoveEvent.connect(moveEvent);
|
||||
Controller.mouseReleaseEvent.connect(releaseEvent);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Messages.subscribe("edit-events");
|
||||
Messages.messageReceived.connect(editEvent);
|
||||
|
|
|
@ -1516,7 +1516,7 @@
|
|||
</div>
|
||||
|
||||
<div class="model-section zone-section property">
|
||||
<div class="label">Shape Type</div>
|
||||
<div class="label">Collision Shape Type</div>
|
||||
<div class="value">
|
||||
<select name="SelectShapeType" id="property-shape-type">
|
||||
<option value='none'>none</option>
|
||||
|
|
|
@ -30,9 +30,9 @@ var pingPongGun = Entities.addEntity({
|
|||
script: scriptURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.21,
|
||||
z: 0.47
|
||||
x: 0.125,
|
||||
y: 0.3875,
|
||||
z: 0.9931
|
||||
},
|
||||
dynamic: true,
|
||||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
//if the trigger value goes below this value, reload the gun.
|
||||
var RELOAD_THRESHOLD = 0.95;
|
||||
var GUN_TIP_FWD_OFFSET = -0.35;
|
||||
var GUN_TIP_UP_OFFSET = 0.040;
|
||||
var GUN_TIP_UP_OFFSET = 0.12;
|
||||
var GUN_FORCE = 9;
|
||||
var BALL_RESTITUTION = 0.6;
|
||||
var BALL_LINEAR_DAMPING = 0.4;
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
{ "from": "Hydra.RB", "to": "Standard.RB" },
|
||||
{ "from": "Hydra.RS", "to": "Standard.RS" },
|
||||
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L0" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
{ "from": "Hydra.L0", "to": "Standard.Back" },
|
||||
{ "from": "Hydra.R0", "to": "Standard.Start" },
|
||||
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R0" ], "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
|
|
|
@ -27,14 +27,11 @@
|
|||
|
||||
{ "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"},
|
||||
|
||||
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
|
||||
|
||||
{ "from": [ "Standard.DU", "Standard.DL", "Standard.DR", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": "Standard.Back", "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "Standard.Start", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Standard.LeftSecondaryThumb", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.RightSecondaryThumb", "to": "Actions.ContextMenu" },
|
||||
|
||||
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
|
||||
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{ "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" },
|
||||
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" },
|
||||
{ "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" },
|
||||
{ "from": [ "Standard.B", "Standard.Y", "Standard.RightPrimaryThumb", "Standard.LeftPrimaryThumb" ], "to": "Actions.UiNavBack" },
|
||||
{ "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" },
|
||||
{
|
||||
"from": [ "Standard.RT", "Standard.LT" ],
|
||||
"to": "Actions.UiNavSelect",
|
||||
|
|
|
@ -15,10 +15,8 @@
|
|||
{ "from": "Vive.RB", "to": "Standard.RB" },
|
||||
{ "from": "Vive.RS", "to": "Standard.RS" },
|
||||
|
||||
{ "from": "Vive.LeftPrimaryThumb", "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": "Vive.RightPrimaryThumb", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "Vive.LeftSecondaryThumb", "to": "Standard.LeftSecondaryThumb" },
|
||||
{ "from": "Vive.RightSecondaryThumb", "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": "Vive.LeftApplicationMenu", "to": "Standard.Back" },
|
||||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.Start" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand" }
|
||||
|
|
|
@ -50,7 +50,23 @@ Windows.Window {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle message traffic from the script that launched us to the loaded QML
|
||||
function fromScript(message) {
|
||||
if (root.dynamicContent && root.dynamicContent.fromScript) {
|
||||
root.dynamicContent.fromScript(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle message traffic from our loaded QML to the script that launched us
|
||||
signal sendToScript(var message);
|
||||
onDynamicContentChanged: {
|
||||
if (dynamicContent && dynamicContent.sendToScript) {
|
||||
dynamicContent.sendToScript.connect(sendToScript);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: contentHolder
|
||||
anchors.fill: parent
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
#include <recording/Recorder.h>
|
||||
#include <QmlWebWindowClass.h>
|
||||
#include <Preferences.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
||||
#include "AnimDebugDraw.h"
|
||||
#include "AudioClient.h"
|
||||
|
@ -376,6 +377,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
|
||||
DependencyManager::set<CompositorHelper>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -770,7 +772,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
}
|
||||
|
||||
if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
|
||||
auto reticlePos = _compositor.getReticlePosition();
|
||||
auto reticlePos = getApplicationCompositor().getReticlePosition();
|
||||
QPoint globalPos(reticlePos.x, reticlePos.y);
|
||||
|
||||
// FIXME - it would be nice if this was self contained in the _compositor or Reticle class
|
||||
|
@ -793,14 +795,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
|
||||
cycleCamera();
|
||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||
getApplicationCompositor().setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
|
||||
toggleOverlays();
|
||||
}
|
||||
|
@ -1253,17 +1255,17 @@ void Application::initializeUi() {
|
|||
rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
|
||||
rootContext->setContextProperty("Reticle", _compositor.getReticleInterface());
|
||||
rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
|
||||
|
||||
rootContext->setContextProperty("ApplicationCompositor", &_compositor);
|
||||
rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
|
||||
|
||||
_glWidget->installEventFilter(offscreenUi.data());
|
||||
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
|
||||
QPointF result = pt;
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
if (displayPlugin->isHmd()) {
|
||||
_compositor.handleRealMouseMoveEvent(false);
|
||||
auto resultVec = _compositor.getReticlePosition();
|
||||
getApplicationCompositor().handleRealMouseMoveEvent(false);
|
||||
auto resultVec = getApplicationCompositor().getReticlePosition();
|
||||
result = QPointF(resultVec.x, resultVec.y);
|
||||
}
|
||||
return result.toPoint();
|
||||
|
@ -1287,7 +1289,15 @@ void Application::initializeUi() {
|
|||
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
|
||||
}
|
||||
}
|
||||
Menu::setInstance();
|
||||
updateInputModes();
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] {
|
||||
if (isHMDMode()) {
|
||||
showCursor(compositorHelper->getAllowMouseCapture() ? Qt::BlankCursor : Qt::ArrowCursor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Application::paintGL() {
|
||||
|
@ -1381,9 +1391,10 @@ void Application::paintGL() {
|
|||
QSize size = getDeviceSize();
|
||||
renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height());
|
||||
_applicationOverlay.renderOverlay(&renderArgs);
|
||||
gpu::FramebufferPointer overlayFramebuffer = _applicationOverlay.getOverlayFramebuffer();
|
||||
|
||||
|
||||
auto overlayTexture = _applicationOverlay.acquireOverlay();
|
||||
if (overlayTexture) {
|
||||
displayPlugin->submitOverlayTexture(overlayTexture);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 boomOffset;
|
||||
|
@ -1484,6 +1495,8 @@ void Application::paintGL() {
|
|||
myAvatar->endCapture();
|
||||
}
|
||||
|
||||
getApplicationCompositor().setFrameInfo(_frameCount, _myCamera.getTransform());
|
||||
|
||||
// Primary rendering pass
|
||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
const QSize size = framebufferCache->getFrameBufferSize();
|
||||
|
@ -1536,7 +1549,7 @@ void Application::paintGL() {
|
|||
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale);
|
||||
eyeOffsets[eye] = eyeOffsetTransform;
|
||||
|
||||
displayPlugin->setEyeRenderPose(_frameCount, eye, headPose);
|
||||
displayPlugin->setEyeRenderPose(_frameCount, eye, headPose * glm::inverse(eyeOffsetTransform));
|
||||
|
||||
eyeProjections[eye] = displayPlugin->getEyeProjection(eye, baseProjection);
|
||||
});
|
||||
|
@ -1550,44 +1563,12 @@ void Application::paintGL() {
|
|||
renderArgs._context->enableStereo(false);
|
||||
}
|
||||
|
||||
// Overlay Composition, needs to occur after screen space effects have completed
|
||||
// FIXME migrate composition into the display plugins
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/compositor");
|
||||
PerformanceTimer perfTimer("compositor");
|
||||
|
||||
auto primaryFbo = finalFramebuffer;
|
||||
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo));
|
||||
if (displayPlugin->isStereo()) {
|
||||
QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height()));
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
for_each_eye([&](Eye eye) {
|
||||
renderArgs._viewport = toGlm(currentViewport);
|
||||
if (displayPlugin->isHmd()) {
|
||||
_compositor.displayOverlayTextureHmd(&renderArgs, eye);
|
||||
} else {
|
||||
_compositor.displayOverlayTexture(&renderArgs);
|
||||
}
|
||||
}, [&] {
|
||||
currentViewport.moveLeft(currentViewport.width());
|
||||
});
|
||||
} else {
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
_compositor.displayOverlayTexture(&renderArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// deliver final composited scene to the display plugin
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
|
||||
PerformanceTimer perfTimer("pluginOutput");
|
||||
|
||||
auto finalTexturePointer = finalFramebuffer->getRenderBuffer(0);
|
||||
|
||||
GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer);
|
||||
Q_ASSERT(0 != finalTexture);
|
||||
|
||||
auto finalTexture = finalFramebuffer->getRenderBuffer(0);
|
||||
Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture));
|
||||
_lockedFramebufferMap[finalTexture] = finalFramebuffer;
|
||||
|
||||
|
@ -1595,10 +1576,9 @@ void Application::paintGL() {
|
|||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene");
|
||||
PerformanceTimer perfTimer("pluginSubmitScene");
|
||||
displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size));
|
||||
displayPlugin->submitSceneTexture(_frameCount, finalTexture);
|
||||
}
|
||||
Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1795,7 +1775,7 @@ bool Application::event(QEvent* event) {
|
|||
bool Application::eventFilter(QObject* object, QEvent* event) {
|
||||
|
||||
if (event->type() == QEvent::Leave) {
|
||||
_compositor.handleLeaveEvent();
|
||||
getApplicationCompositor().handleLeaveEvent();
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::ShortcutOverride) {
|
||||
|
@ -2083,7 +2063,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
|
||||
}
|
||||
|
||||
|
@ -2172,13 +2152,6 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/// called by ApplicationCompositor when in HMD mode and we're faking our mouse movement
|
||||
void Application::fakeMouseEvent(QMouseEvent* event) {
|
||||
_fakedMouseEvent = true;
|
||||
sendEvent(_glWidget, event);
|
||||
_fakedMouseEvent = false;
|
||||
}
|
||||
|
||||
void Application::mouseMoveEvent(QMouseEvent* event) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
|
@ -2188,17 +2161,16 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
|
|||
|
||||
maybeToggleMenuVisible(event);
|
||||
|
||||
auto& compositor = getApplicationCompositor();
|
||||
// if this is a real mouse event, and we're in HMD mode, then we should use it to move the
|
||||
// compositor reticle
|
||||
if (!_fakedMouseEvent) {
|
||||
// handleRealMouseMoveEvent() will return true, if we shouldn't process the event further
|
||||
if (_compositor.handleRealMouseMoveEvent()) {
|
||||
return; // bail
|
||||
}
|
||||
// handleRealMouseMoveEvent() will return true, if we shouldn't process the event further
|
||||
if (!compositor.fakeEventActive() && compositor.handleRealMouseMoveEvent()) {
|
||||
return; // bail
|
||||
}
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto eventPosition = _compositor.getMouseEventPosition(event);
|
||||
auto eventPosition = compositor.getMouseEventPosition(event);
|
||||
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
|
||||
auto button = event->button();
|
||||
auto buttons = event->buttons();
|
||||
|
@ -2240,7 +2212,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
|
|||
// will consume all keyboard events.
|
||||
offscreenUi->unfocusWindows();
|
||||
|
||||
auto eventPosition = _compositor.getMouseEventPosition(event);
|
||||
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
|
||||
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
|
||||
QMouseEvent mappedEvent(event->type(),
|
||||
transformedPos,
|
||||
|
@ -2286,7 +2258,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
|
|||
void Application::mouseReleaseEvent(QMouseEvent* event) {
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto eventPosition = _compositor.getMouseEventPosition(event);
|
||||
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
|
||||
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
|
||||
QMouseEvent mappedEvent(event->type(),
|
||||
transformedPos,
|
||||
|
@ -2569,7 +2541,7 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
|||
}
|
||||
|
||||
ivec2 Application::getMouse() {
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
|
||||
// in the HMD, the reticlePosition is the mouse position
|
||||
if (isHMDMode()) {
|
||||
|
@ -4208,7 +4180,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get());
|
||||
|
||||
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
scriptEngine->registerGlobalObject("Reticle", _compositor.getReticleInterface());
|
||||
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
|
@ -4709,7 +4681,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
auto action = menu->addActionToQMenuAndActionHash(parent,
|
||||
name, 0, qApp,
|
||||
SLOT(updateDisplayMode()),
|
||||
QAction::NoRole, UNSPECIFIED_POSITION, groupingMenu);
|
||||
QAction::NoRole, Menu::UNSPECIFIED_POSITION, groupingMenu);
|
||||
|
||||
action->setCheckable(true);
|
||||
action->setChecked(active);
|
||||
|
@ -4814,6 +4786,7 @@ void Application::updateDisplayMode() {
|
|||
|
||||
oldDisplayPlugin = _displayPlugin;
|
||||
_displayPlugin = newDisplayPlugin;
|
||||
getApplicationCompositor().setDisplayPlugin(_displayPlugin);
|
||||
|
||||
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
|
||||
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
|
||||
|
@ -5031,3 +5004,7 @@ void Application::showDesktop() {
|
|||
_overlayConductor.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
CompositorHelper& Application::getApplicationCompositor() const {
|
||||
return *DependencyManager::get<CompositorHelper>();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
#include "render/Engine.h"
|
||||
#include "scripting/ControllerScriptingInterface.h"
|
||||
#include "scripting/DialogsManagerScriptingInterface.h"
|
||||
#include "ui/ApplicationCompositor.h"
|
||||
#include "ui/ApplicationOverlay.h"
|
||||
#include "ui/AudioStatsDialog.h"
|
||||
#include "ui/BandwidthDialog.h"
|
||||
|
@ -67,6 +66,7 @@ class GLCanvas;
|
|||
class FaceTracker;
|
||||
class MainWindow;
|
||||
class AssetUpload;
|
||||
class CompositorHelper;
|
||||
|
||||
namespace controller {
|
||||
class StateController;
|
||||
|
@ -125,6 +125,7 @@ public:
|
|||
bool isThrottleRendering() const;
|
||||
|
||||
Camera* getCamera() { return &_myCamera; }
|
||||
const Camera* getCamera() const { return &_myCamera; }
|
||||
// Represents the current view frustum of the avatar.
|
||||
ViewFrustum* getViewFrustum();
|
||||
const ViewFrustum* getViewFrustum() const;
|
||||
|
@ -149,8 +150,7 @@ public:
|
|||
|
||||
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
|
||||
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
||||
ApplicationCompositor& getApplicationCompositor() { return _compositor; }
|
||||
const ApplicationCompositor& getApplicationCompositor() const { return _compositor; }
|
||||
CompositorHelper& getApplicationCompositor() const;
|
||||
|
||||
Overlays& getOverlays() { return _overlays; }
|
||||
|
||||
|
@ -221,8 +221,6 @@ public:
|
|||
|
||||
float getAverageSimsPerSecond();
|
||||
|
||||
void fakeMouseEvent(QMouseEvent* event);
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -390,7 +388,7 @@ private:
|
|||
InputPluginList _activeInputPlugins;
|
||||
|
||||
bool _activatingDisplayPlugin { false };
|
||||
QMap<uint32_t, gpu::FramebufferPointer> _lockedFramebufferMap;
|
||||
QMap<gpu::TexturePointer, gpu::FramebufferPointer> _lockedFramebufferMap;
|
||||
|
||||
MainWindow* _window;
|
||||
|
||||
|
@ -484,7 +482,6 @@ private:
|
|||
|
||||
Overlays _overlays;
|
||||
ApplicationOverlay _applicationOverlay;
|
||||
ApplicationCompositor _compositor;
|
||||
OverlayConductor _overlayConductor;
|
||||
|
||||
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
||||
|
|
|
@ -88,11 +88,11 @@ void Bookmarks::persistToFile() {
|
|||
|
||||
void Bookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
// Add menus/actions
|
||||
menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkLocation, 0,
|
||||
this, SLOT(bookmarkLocation()));
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkLocation);
|
||||
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(bookmarkLocation()), Qt::QueuedConnection);
|
||||
_bookmarksMenu = menu->addMenu(MenuOption::Bookmarks);
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteBookmark, 0,
|
||||
this, SLOT(deleteBookmark()));
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteBookmark);
|
||||
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
|
||||
|
||||
// Enable/Disable menus as needed
|
||||
enableMenuItems(_bookmarks.count() > 0);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include "Menu.h"
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
|
|||
|
||||
QString getLogRollerFilename() {
|
||||
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
|
||||
QHostAddress clientAddress = getLocalAddress();
|
||||
QHostAddress clientAddress = getGuessedLocalAddress();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
|
||||
return result;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <UserActivityLogger.h>
|
||||
#include <VrMenu.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <MenuItemProperties.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AccountManager.h"
|
||||
|
@ -45,11 +46,15 @@
|
|||
|
||||
#include "Menu.h"
|
||||
|
||||
// Fixme make static member of Menu
|
||||
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
|
||||
|
||||
void Menu::setInstance() {
|
||||
globalInstance<Menu>(MENU_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
Menu* Menu::getInstance() {
|
||||
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
|
||||
return instance;
|
||||
return static_cast<Menu*>(ui::Menu::getInstance());
|
||||
}
|
||||
|
||||
Menu::Menu() {
|
||||
|
@ -621,418 +626,6 @@ Menu::Menu() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void Menu::toggleAdvancedMenus() {
|
||||
setGroupingIsVisible("Advanced", !getGroupingIsVisible("Advanced"));
|
||||
}
|
||||
|
||||
void Menu::toggleDeveloperMenus() {
|
||||
setGroupingIsVisible("Developer", !getGroupingIsVisible("Developer"));
|
||||
}
|
||||
|
||||
void Menu::loadSettings() {
|
||||
scanMenuBar(&Menu::loadAction);
|
||||
}
|
||||
|
||||
void Menu::saveSettings() {
|
||||
scanMenuBar(&Menu::saveAction);
|
||||
}
|
||||
|
||||
void Menu::loadAction(Settings& settings, QAction& action) {
|
||||
if (action.isChecked() != settings.value(action.text(), action.isChecked()).toBool()) {
|
||||
action.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::saveAction(Settings& settings, QAction& action) {
|
||||
settings.setValue(action.text(), action.isChecked());
|
||||
}
|
||||
|
||||
void Menu::scanMenuBar(settingsAction modifySetting) {
|
||||
Settings settings;
|
||||
foreach (QMenu* menu, findChildren<QMenu*>()) {
|
||||
scanMenu(*menu, modifySetting, settings);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings) {
|
||||
settings.beginGroup(menu.title());
|
||||
foreach (QAction* action, menu.actions()) {
|
||||
if (action->menu()) {
|
||||
scanMenu(*action->menu(), modifySetting, settings);
|
||||
} else if (action->isCheckable()) {
|
||||
modifySetting(settings, *action);
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Menu::addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName,
|
||||
int menuItemLocation, const QString& grouping) {
|
||||
QAction* actionBefore = NULL;
|
||||
QAction* separator;
|
||||
QAction* separatorText;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
if (actionBefore) {
|
||||
separator = new QAction("",destinationMenu);
|
||||
destinationMenu->insertAction(actionBefore, separator);
|
||||
separator->setSeparator(true);
|
||||
|
||||
separatorText = new QAction(actionName,destinationMenu);
|
||||
separatorText->setEnabled(false);
|
||||
destinationMenu->insertAction(actionBefore, separatorText);
|
||||
|
||||
} else {
|
||||
separator = destinationMenu->addSeparator();
|
||||
separatorText = destinationMenu->addAction(actionName);
|
||||
separatorText->setEnabled(false);
|
||||
}
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << separator;
|
||||
_groupingActions[grouping] << separatorText;
|
||||
bool isVisible = getGroupingIsVisible(grouping);
|
||||
separator->setVisible(isVisible);
|
||||
separatorText->setVisible(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
const QObject* receiver,
|
||||
const char* member,
|
||||
QAction::MenuRole role,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
QAction* action = NULL;
|
||||
QAction* actionBefore = NULL;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
|
||||
if (!actionBefore) {
|
||||
if (receiver && member) {
|
||||
action = destinationMenu->addAction(actionName, receiver, member, shortcut);
|
||||
} else {
|
||||
action = destinationMenu->addAction(actionName);
|
||||
action->setShortcut(shortcut);
|
||||
}
|
||||
} else {
|
||||
action = new QAction(actionName, destinationMenu);
|
||||
action->setShortcut(shortcut);
|
||||
destinationMenu->insertAction(actionBefore, action);
|
||||
|
||||
if (receiver && member) {
|
||||
connect(action, SIGNAL(triggered()), receiver, member);
|
||||
}
|
||||
}
|
||||
action->setMenuRole(role);
|
||||
|
||||
_actionHash.insert(actionName, action);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
QAction* action,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
QAction::MenuRole role,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
QAction* actionBefore = NULL;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
|
||||
if (!actionName.isEmpty()) {
|
||||
action->setText(actionName);
|
||||
}
|
||||
|
||||
if (shortcut != 0) {
|
||||
action->setShortcut(shortcut);
|
||||
}
|
||||
|
||||
if (role != QAction::NoRole) {
|
||||
action->setMenuRole(role);
|
||||
}
|
||||
|
||||
if (!actionBefore) {
|
||||
destinationMenu->addAction(action);
|
||||
} else {
|
||||
destinationMenu->insertAction(actionBefore, action);
|
||||
}
|
||||
|
||||
_actionHash.insert(action->text(), action);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
const bool checked,
|
||||
const QObject* receiver,
|
||||
const char* member,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
|
||||
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
|
||||
QAction::NoRole, menuItemLocation);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(checked);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
void Menu::removeAction(MenuWrapper* menu, const QString& actionName) {
|
||||
auto action = _actionHash.value(actionName);
|
||||
menu->removeAction(action);
|
||||
_actionHash.remove(actionName);
|
||||
for (auto& grouping : _groupingActions) {
|
||||
grouping.remove(action);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, menuOption),
|
||||
Q_ARG(bool, isChecked));
|
||||
return;
|
||||
}
|
||||
QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
menu->setChecked(isChecked);
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::isOptionChecked(const QString& menuOption) const {
|
||||
const QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
return menu->isChecked();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::triggerOption(const QString& menuOption) {
|
||||
QAction* action = _actionHash.value(menuOption);
|
||||
if (action) {
|
||||
action->trigger();
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "NULL Action for menuOption '" << menuOption << "'";
|
||||
}
|
||||
}
|
||||
|
||||
QAction* Menu::getActionForOption(const QString& menuOption) {
|
||||
return _actionHash.value(menuOption);
|
||||
}
|
||||
|
||||
QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) {
|
||||
QList<QAction*> menuActions;
|
||||
if (menu) {
|
||||
menuActions = menu->actions();
|
||||
} else {
|
||||
menuActions = actions();
|
||||
}
|
||||
|
||||
foreach (QAction* menuAction, menuActions) {
|
||||
QString actionText = menuAction->text();
|
||||
if (menuName == menuAction->text()) {
|
||||
return menuAction;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) {
|
||||
QAction* action = getActionFromName(menuName, menu);
|
||||
if (action) {
|
||||
return MenuWrapper::fromMenu(action->menu());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
parent = menu;
|
||||
finalMenuPart = menuTreePart.trimmed();
|
||||
menu = getSubMenuFromName(finalMenuPart, parent);
|
||||
if (!menu) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getMenu(const QString& menuName) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
int item = 0;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
menu = getSubMenuFromName(menuTreePart.trimmed(), parent);
|
||||
if (!menu) {
|
||||
break;
|
||||
}
|
||||
parent = menu;
|
||||
item++;
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
QAction* Menu::getMenuAction(const QString& menuName) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
QAction* action = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
action = getActionFromName(menuTreePart.trimmed(), parent);
|
||||
if (!action) {
|
||||
break;
|
||||
}
|
||||
parent = MenuWrapper::fromMenu(action->menu());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
int Menu::findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem) {
|
||||
int position = 0;
|
||||
foreach(QAction* action, menu->actions()) {
|
||||
if (action->text() == searchMenuItem) {
|
||||
return position;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
return UNSPECIFIED_POSITION; // not found
|
||||
}
|
||||
|
||||
int Menu::positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition) {
|
||||
QList<QAction*> menuActions = menu->actions();
|
||||
if (requestedPosition > 1 && requestedPosition < menuActions.size()) {
|
||||
QAction* beforeRequested = menuActions[requestedPosition - 1];
|
||||
if (beforeRequested->isSeparator()) {
|
||||
requestedPosition--;
|
||||
}
|
||||
}
|
||||
return requestedPosition;
|
||||
}
|
||||
|
||||
bool Menu::_isSomeSubmenuShown = false;
|
||||
|
||||
MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* addTo = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
menu = getSubMenuFromName(menuTreePart.trimmed(), addTo);
|
||||
if (!menu) {
|
||||
if (!addTo) {
|
||||
menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed()));
|
||||
} else {
|
||||
menu = addTo->addMenu(menuTreePart.trimmed());
|
||||
}
|
||||
}
|
||||
addTo = menu;
|
||||
}
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
auto action = getMenuAction(menuName);
|
||||
if (action) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
|
||||
// hook our show/hide for popup menus, so we can keep track of whether or not one
|
||||
// of our submenus is currently showing.
|
||||
connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; });
|
||||
connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; });
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void Menu::removeMenu(const QString& menuName) {
|
||||
QAction* action = getMenuAction(menuName);
|
||||
|
||||
// only proceed if the menu actually exists
|
||||
if (action) {
|
||||
QString finalMenuPart;
|
||||
MenuWrapper* parent = getMenuParent(menuName, finalMenuPart);
|
||||
if (parent) {
|
||||
parent->removeAction(action);
|
||||
} else {
|
||||
QMenuBar::removeAction(action);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::menuExists(const QString& menuName) {
|
||||
QAction* action = getMenuAction(menuName);
|
||||
|
||||
// only proceed if the menu actually exists
|
||||
if (action) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) {
|
||||
MenuWrapper* menuObj = getMenu(menuName);
|
||||
if (menuObj) {
|
||||
addDisabledActionAndSeparator(menuObj, separatorName);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeSeparator(const QString& menuName, const QString& separatorName) {
|
||||
MenuWrapper* menu = getMenu(menuName);
|
||||
bool separatorRemoved = false;
|
||||
if (menu) {
|
||||
int textAt = findPositionOfMenuItem(menu, separatorName);
|
||||
QList<QAction*> menuActions = menu->actions();
|
||||
QAction* separatorText = menuActions[textAt];
|
||||
if (textAt > 0 && textAt < menuActions.size()) {
|
||||
QAction* separatorLine = menuActions[textAt - 1];
|
||||
if (separatorLine) {
|
||||
if (separatorLine->isSeparator()) {
|
||||
menu->removeAction(separatorText);
|
||||
menu->removeAction(separatorLine);
|
||||
separatorRemoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (separatorRemoved) {
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::addMenuItem(const MenuItemProperties& properties) {
|
||||
MenuWrapper* menuObj = getMenu(properties.menuName);
|
||||
if (menuObj) {
|
||||
|
@ -1075,126 +668,3 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
|
|||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeMenuItem(const QString& menu, const QString& menuitem) {
|
||||
MenuWrapper* menuObj = getMenu(menu);
|
||||
if (menuObj) {
|
||||
removeAction(menuObj, menuitem);
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::menuItemExists(const QString& menu, const QString& menuitem) {
|
||||
QAction* menuItemAction = _actionHash.value(menuitem);
|
||||
if (menuItemAction) {
|
||||
return (getMenu(menu) != NULL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Menu::getGroupingIsVisible(const QString& grouping) {
|
||||
if (grouping.isEmpty() || grouping.isNull()) {
|
||||
return true;
|
||||
}
|
||||
if (_groupingVisible.contains(grouping)) {
|
||||
return _groupingVisible[grouping];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
|
||||
// NOTE: Default grouping always visible
|
||||
if (grouping.isEmpty() || grouping.isNull()) {
|
||||
return;
|
||||
}
|
||||
_groupingVisible[grouping] = isVisible;
|
||||
|
||||
for (auto action: _groupingActions[grouping]) {
|
||||
action->setVisible(isVisible);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected) {
|
||||
auto menu = addMenu(groupName);
|
||||
|
||||
QActionGroup* actionGroup = new QActionGroup(menu);
|
||||
actionGroup->setExclusive(true);
|
||||
|
||||
auto menuScriptingInterface = MenuScriptingInterface::getInstance();
|
||||
for (auto action : actionList) {
|
||||
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected,
|
||||
menuScriptingInterface,
|
||||
SLOT(menuItemTriggered()));
|
||||
actionGroup->addAction(item);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::removeActionGroup(const QString& groupName) {
|
||||
removeMenu(groupName);
|
||||
}
|
||||
|
||||
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addMenu(menu);
|
||||
});
|
||||
_backMap[menu] = this;
|
||||
}
|
||||
|
||||
QList<QAction*> MenuWrapper::actions() {
|
||||
return _realMenu->actions();
|
||||
}
|
||||
|
||||
MenuWrapper* MenuWrapper::addMenu(const QString& menuName) {
|
||||
return new MenuWrapper(_realMenu->addMenu(menuName));
|
||||
}
|
||||
|
||||
void MenuWrapper::setEnabled(bool enabled) {
|
||||
_realMenu->setEnabled(enabled);
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addSeparator() {
|
||||
return _realMenu->addSeparator();
|
||||
}
|
||||
|
||||
void MenuWrapper::addAction(QAction* action) {
|
||||
_realMenu->addAction(action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addAction(const QString& menuName) {
|
||||
QAction* action = _realMenu->addAction(menuName);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) {
|
||||
QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
void MenuWrapper::removeAction(QAction* action) {
|
||||
_realMenu->removeAction(action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->removeAction(action);
|
||||
});
|
||||
}
|
||||
|
||||
void MenuWrapper::insertAction(QAction* before, QAction* action) {
|
||||
_realMenu->insertAction(before, action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->insertAction(before, action);
|
||||
});
|
||||
}
|
||||
|
||||
QHash<QMenu*, MenuWrapper*> MenuWrapper::_backMap;
|
||||
|
|
|
@ -9,144 +9,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <ui/Menu.h>
|
||||
|
||||
#ifndef hifi_Menu_h
|
||||
#define hifi_Menu_h
|
||||
|
||||
#include <QDir>
|
||||
#include <QMenuBar>
|
||||
#include <QHash>
|
||||
#include <QKeySequence>
|
||||
#include <QPointer>
|
||||
#include <QStandardPaths>
|
||||
class MenuItemProperties;
|
||||
|
||||
#include <MenuItemProperties.h>
|
||||
|
||||
#include "DiscoverabilityManager.h"
|
||||
|
||||
class Settings;
|
||||
|
||||
class MenuWrapper : public QObject {
|
||||
public:
|
||||
QList<QAction*> actions();
|
||||
MenuWrapper* addMenu(const QString& menuName);
|
||||
void setEnabled(bool enabled = true);
|
||||
QAction* addSeparator();
|
||||
void addAction(QAction* action);
|
||||
|
||||
QAction* addAction(const QString& menuName);
|
||||
void insertAction(QAction* before, QAction* menuName);
|
||||
|
||||
QAction* addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut = 0);
|
||||
void removeAction(QAction* action);
|
||||
|
||||
QAction* newAction() {
|
||||
return new QAction(_realMenu);
|
||||
}
|
||||
|
||||
private:
|
||||
MenuWrapper(QMenu* menu);
|
||||
|
||||
static MenuWrapper* fromMenu(QMenu* menu) {
|
||||
return _backMap[menu];
|
||||
}
|
||||
|
||||
QMenu* const _realMenu;
|
||||
static QHash<QMenu*, MenuWrapper*> _backMap;
|
||||
friend class Menu;
|
||||
};
|
||||
|
||||
class Menu : public QMenuBar {
|
||||
class Menu : public ui::Menu {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Menu();
|
||||
static void setInstance();
|
||||
static Menu* getInstance();
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
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,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut = 0,
|
||||
const QObject* receiver = NULL,
|
||||
const char* member = NULL,
|
||||
QAction::MenuRole role = QAction::NoRole,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
QAction* action,
|
||||
const QString& actionName = QString(),
|
||||
const QKeySequence& shortcut = 0,
|
||||
QAction::MenuRole role = QAction::NoRole,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut = 0,
|
||||
const bool checked = false,
|
||||
const QObject* receiver = NULL,
|
||||
const char* member = NULL,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
void removeAction(MenuWrapper* menu, const QString& actionName);
|
||||
|
||||
public slots:
|
||||
MenuWrapper* addMenu(const QString& menuName, const QString& grouping = QString());
|
||||
void removeMenu(const QString& menuName);
|
||||
bool menuExists(const QString& menuName);
|
||||
void addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping = QString());
|
||||
void removeSeparator(const QString& menuName, const QString& separatorName);
|
||||
void addMenuItem(const MenuItemProperties& properties);
|
||||
void removeMenuItem(const QString& menuName, const QString& menuitem);
|
||||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString());
|
||||
void removeActionGroup(const QString& groupName);
|
||||
bool isOptionChecked(const QString& menuOption) const;
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
bool getGroupingIsVisible(const QString& grouping);
|
||||
void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible
|
||||
|
||||
void toggleDeveloperMenus();
|
||||
void toggleAdvancedMenus();
|
||||
|
||||
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
|
||||
|
||||
private:
|
||||
typedef void(*settingsAction)(Settings&, QAction&);
|
||||
static void loadAction(Settings& settings, QAction& action);
|
||||
static void saveAction(Settings& settings, QAction& action);
|
||||
void scanMenuBar(settingsAction modifySetting);
|
||||
void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings);
|
||||
|
||||
/// helper method to have separators with labels that are also compatible with OS X
|
||||
void addDisabledActionAndSeparator(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* getActionFromName(const QString& menuName, MenuWrapper* menu);
|
||||
MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart);
|
||||
|
||||
QAction* getMenuAction(const QString& menuName);
|
||||
int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem);
|
||||
int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition);
|
||||
|
||||
QHash<QString, QAction*> _actionHash;
|
||||
|
||||
bool isValidGrouping(const QString& grouping) const { return grouping == "Advanced" || grouping == "Developer"; }
|
||||
QHash<QString, bool> _groupingVisible;
|
||||
QHash<QString, QSet<QAction*>> _groupingActions;
|
||||
|
||||
static bool _isSomeSubmenuShown;
|
||||
Menu();
|
||||
Q_INVOKABLE void addMenuItem(const MenuItemProperties& properties);
|
||||
};
|
||||
|
||||
namespace MenuOption {
|
||||
|
@ -302,3 +179,4 @@ namespace MenuOption {
|
|||
}
|
||||
|
||||
#endif // hifi_Menu_h
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
#include "ModelSelector.h"
|
||||
|
||||
static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
|
||||
static const QString AVATAR_BODY_STRING = "Avatar Body Only";
|
||||
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
|
||||
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
|
||||
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
||||
|
@ -35,8 +33,7 @@ ModelSelector::ModelSelector() {
|
|||
form->addRow("Model File:", _browseButton);
|
||||
|
||||
_modelType = new QComboBox(this);
|
||||
_modelType->addItem(AVATAR_HEAD_STRING);
|
||||
_modelType->addItem(AVATAR_BODY_STRING);
|
||||
|
||||
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||
|
@ -55,11 +52,7 @@ QFileInfo ModelSelector::getFileInfo() const {
|
|||
FSTReader::ModelType ModelSelector::getModelType() const {
|
||||
QString text = _modelType->currentText();
|
||||
|
||||
if (text == AVATAR_HEAD_STRING) {
|
||||
return FSTReader::HEAD_MODEL;
|
||||
} else if (text == AVATAR_BODY_STRING) {
|
||||
return FSTReader::BODY_ONLY_MODEL;
|
||||
} else if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
return FSTReader::HEAD_AND_BODY_MODEL;
|
||||
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||
return FSTReader::ATTACHMENT_MODEL;
|
||||
|
|
|
@ -170,7 +170,7 @@ bool PluginContainerProxy::makeRenderingContextCurrent() {
|
|||
return qApp->_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
void PluginContainerProxy::releaseSceneTexture(uint32_t texture) {
|
||||
void PluginContainerProxy::releaseSceneTexture(const gpu::TexturePointer& texture) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
auto& framebufferMap = qApp->_lockedFramebufferMap;
|
||||
Q_ASSERT(framebufferMap.contains(texture));
|
||||
|
@ -180,8 +180,8 @@ void PluginContainerProxy::releaseSceneTexture(uint32_t texture) {
|
|||
framebufferCache->releaseFramebuffer(framebufferPointer);
|
||||
}
|
||||
|
||||
void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) {
|
||||
// FIXME implement present thread compositing
|
||||
void PluginContainerProxy::releaseOverlayTexture(const gpu::TexturePointer& texture) {
|
||||
qApp->_applicationOverlay.releaseOverlay(texture);
|
||||
}
|
||||
|
||||
/// settings interface
|
||||
|
|
|
@ -25,8 +25,8 @@ class PluginContainerProxy : public QObject, PluginContainer {
|
|||
virtual void showDisplayPluginsTools() override;
|
||||
virtual void requestReset() override;
|
||||
virtual bool makeRenderingContextCurrent() override;
|
||||
virtual void releaseSceneTexture(uint32_t texture) override;
|
||||
virtual void releaseOverlayTexture(uint32_t texture) override;
|
||||
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override;
|
||||
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override;
|
||||
virtual GLWidget* getPrimaryWidget() override;
|
||||
virtual QWindow* getPrimaryWindow() override;
|
||||
virtual QOpenGLContext* getPrimaryContext() override;
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
|
||||
#include <QtScript/QScriptContext>
|
||||
|
||||
#include "display-plugins/DisplayPlugin.h"
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include "Application.h"
|
||||
|
||||
|
|
|
@ -86,10 +86,13 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString&
|
|||
|
||||
void MenuScriptingInterface::addActionGroup(const QString& groupName, const QStringList& actionList,
|
||||
const QString& selected) {
|
||||
static const char* slot = SLOT(menuItemTriggered());
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "addActionGroup",
|
||||
Q_ARG(const QString&, groupName),
|
||||
Q_ARG(const QStringList&, actionList),
|
||||
Q_ARG(const QString&, selected));
|
||||
Q_ARG(const QString&, selected),
|
||||
Q_ARG(QObject*, this),
|
||||
Q_ARG(const char*, slot));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::removeActionGroup(const QString& groupName) {
|
||||
|
|
|
@ -1,704 +0,0 @@
|
|||
//
|
||||
// ApplicationCompositor.cpp
|
||||
// interface/src/ui/overlays
|
||||
//
|
||||
// Created by Benjamin Arnold on 5/27/14.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#include "ApplicationCompositor.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "CursorManager.h"
|
||||
#include "Tooltip.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include <controllers/InputDevice.h>
|
||||
|
||||
|
||||
// Used to animate the magnification windows
|
||||
|
||||
static const quint64 MSECS_TO_USECS = 1000ULL;
|
||||
static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
|
||||
|
||||
static const float reticleSize = TWO_PI / 100.0f;
|
||||
|
||||
static const float CURSOR_PIXEL_SIZE = 32.0f;
|
||||
|
||||
static gpu::BufferPointer _hemiVertices;
|
||||
static gpu::BufferPointer _hemiIndices;
|
||||
static int _hemiIndexCount{ 0 };
|
||||
EntityItemID ApplicationCompositor::_noItemId;
|
||||
static QString _tooltipId;
|
||||
|
||||
// Return a point's cartesian coordinates on a sphere from pitch and yaw
|
||||
glm::vec3 getPoint(float yaw, float pitch) {
|
||||
return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)),
|
||||
glm::sin(-pitch),
|
||||
glm::cos(-pitch) * (-glm::cos(yaw)));
|
||||
}
|
||||
|
||||
//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should
|
||||
//be multiplied by dir and added to origin to get the location of the collision
|
||||
bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result)
|
||||
{
|
||||
//Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
|
||||
|
||||
//Compute A, B and C coefficients
|
||||
float a = glm::dot(dir, dir);
|
||||
float b = 2 * glm::dot(dir, origin);
|
||||
float c = glm::dot(origin, origin) - (r * r);
|
||||
|
||||
//Find discriminant
|
||||
float disc = b * b - 4 * a * c;
|
||||
|
||||
// if discriminant is negative there are no real roots, so return
|
||||
// false as ray misses sphere
|
||||
if (disc < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compute q as described above
|
||||
float distSqrt = sqrtf(disc);
|
||||
float q;
|
||||
if (b < 0) {
|
||||
q = (-b - distSqrt) / 2.0f;
|
||||
} else {
|
||||
q = (-b + distSqrt) / 2.0f;
|
||||
}
|
||||
|
||||
// compute t0 and t1
|
||||
float t0 = q / a;
|
||||
float t1 = c / q;
|
||||
|
||||
// make sure t0 is smaller than t1
|
||||
if (t0 > t1) {
|
||||
// if t0 is bigger than t1 swap them around
|
||||
float temp = t0;
|
||||
t0 = t1;
|
||||
t1 = temp;
|
||||
}
|
||||
|
||||
// if t1 is less than zero, the object is in the ray's negative direction
|
||||
// and consequently the ray misses the sphere
|
||||
if (t1 < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if t0 is less than zero, the intersection point is at t1
|
||||
if (t0 < 0) {
|
||||
*result = t1;
|
||||
return true;
|
||||
} else { // else the intersection point is at t0
|
||||
*result = t0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationCompositor::ApplicationCompositor() :
|
||||
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
|
||||
_reticleInterface(new ReticleInterface(this))
|
||||
|
||||
{
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
_reticleQuad = geometryCache->allocateID();
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
||||
if (_hoverItemId != entityItemID) {
|
||||
_hoverItemId = entityItemID;
|
||||
_hoverItemEnterUsecs = usecTimestampNow();
|
||||
auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId);
|
||||
|
||||
// check the format of this href string before we parse it
|
||||
QString hrefString = properties.getHref();
|
||||
|
||||
auto cursor = Cursor::Manager::instance().getCursor();
|
||||
if (!hrefString.isEmpty()) {
|
||||
if (!hrefString.startsWith("hifi:")) {
|
||||
hrefString.prepend("hifi://");
|
||||
}
|
||||
|
||||
// parse out a QUrl from the hrefString
|
||||
QUrl href = QUrl(hrefString);
|
||||
|
||||
_hoverItemTitle = href.host();
|
||||
_hoverItemDescription = properties.getDescription();
|
||||
cursor->setIcon(Cursor::Icon::LINK);
|
||||
} else {
|
||||
_hoverItemTitle.clear();
|
||||
_hoverItemDescription.clear();
|
||||
cursor->setIcon(Cursor::Icon::DEFAULT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
||||
if (_hoverItemId == entityItemID) {
|
||||
_hoverItemId = _noItemId;
|
||||
|
||||
_hoverItemTitle.clear();
|
||||
_hoverItemDescription.clear();
|
||||
|
||||
auto cursor = Cursor::Manager::instance().getCursor();
|
||||
cursor->setIcon(Cursor::Icon::DEFAULT);
|
||||
if (!_tooltipId.isEmpty()) {
|
||||
qDebug() << "Closing tooltip " << _tooltipId;
|
||||
Tooltip::closeTip(_tooltipId);
|
||||
_tooltipId.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_alphaPropertyAnimation.reset(new QPropertyAnimation(this, "alpha"));
|
||||
}
|
||||
|
||||
ApplicationCompositor::~ApplicationCompositor() {
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorIndex) {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
auto cursor = cursorManager.getCursor(cursorIndex);
|
||||
auto iconId = cursor->getIcon();
|
||||
if (!_cursors.count(iconId)) {
|
||||
auto iconPath = cursorManager.getIconImage(cursor->getIcon());
|
||||
_cursors[iconId] = DependencyManager::get<TextureCache>()->
|
||||
getImageTexture(iconPath);
|
||||
}
|
||||
batch.setResourceTexture(0, _cursors[iconId]);
|
||||
}
|
||||
|
||||
// Draws the FBO texture for the screen
|
||||
void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
if (_alpha <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer overlayFramebuffer = qApp->getApplicationOverlay().getOverlayFramebuffer();
|
||||
if (!overlayFramebuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateTooltips();
|
||||
|
||||
//Handle fading and deactivation/activation of UI
|
||||
gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) {
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
batch.setViewportTransform(renderArgs->_viewport);
|
||||
batch.setModelTransform(Transform());
|
||||
batch.setViewTransform(Transform());
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
|
||||
geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha));
|
||||
|
||||
//draw the mouse pointer
|
||||
if (getReticleVisible()) {
|
||||
// Get the mouse coordinates and convert to NDC [-1, 1]
|
||||
vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas...
|
||||
vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize);
|
||||
// Invert the Y axis
|
||||
mousePosition.y *= -1.0f;
|
||||
|
||||
Transform model;
|
||||
model.setTranslation(vec3(mousePosition, 0));
|
||||
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
|
||||
model.setScale(vec3(mouseSize, 1.0f));
|
||||
batch.setModelTransform(model);
|
||||
bindCursorTexture(batch);
|
||||
geometryCache->renderUnitQuad(batch, vec4(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Draws the FBO texture for Oculus rift.
|
||||
void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int eye) {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
if (_alpha <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer overlayFramebuffer = qApp->getApplicationOverlay().getOverlayFramebuffer();
|
||||
if (!overlayFramebuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateTooltips();
|
||||
|
||||
glm::uvec2 screenSize = qApp->getUiSize(); // HMD use virtual screen size
|
||||
vec2 canvasSize = screenSize;
|
||||
_textureAspectRatio = aspect(canvasSize);
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
gpu::doInBatch(renderArgs->_context, [&](gpu::Batch& batch) {
|
||||
geometryCache->useSimpleDrawPipeline(batch);
|
||||
|
||||
batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0));
|
||||
|
||||
mat4 camMat;
|
||||
_cameraBaseTransform.getMatrix(camMat);
|
||||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
auto headPose = qApp->getHMDSensorPose();
|
||||
auto eyeToHead = displayPlugin->getEyeToHeadTransform((Eye)eye);
|
||||
camMat = (headPose * eyeToHead) * camMat; // FIXME - why are not all transforms are doing this aditioanl eyeToHead
|
||||
batch.setViewportTransform(renderArgs->_viewport);
|
||||
batch.setViewTransform(camMat);
|
||||
batch.setProjectionTransform(qApp->getEyeProjection(eye));
|
||||
|
||||
#ifdef DEBUG_OVERLAY
|
||||
{
|
||||
batch.setModelTransform(glm::translate(mat4(), vec3(0, 0, -2)));
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1));
|
||||
}
|
||||
#else
|
||||
{
|
||||
batch.setModelTransform(_modelTransform);
|
||||
drawSphereSection(batch);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
|
||||
|
||||
bindCursorTexture(batch);
|
||||
|
||||
//Mouse Pointer
|
||||
if (getReticleVisible()) {
|
||||
if (getReticleDepth() != 1.0f) {
|
||||
// calculate the "apparent location" based on the depth and the current ray
|
||||
glm::vec3 origin, direction;
|
||||
auto reticlePosition = getReticlePosition();
|
||||
computeHmdPickRay(reticlePosition, origin, direction);
|
||||
auto apparentPosition = origin + (direction * getReticleDepth());
|
||||
|
||||
// same code as used to render for apparent location
|
||||
auto myCamera = qApp->getCamera();
|
||||
mat4 cameraMat = myCamera->getTransform();
|
||||
auto UITransform = cameraMat * glm::inverse(headPose);
|
||||
auto relativePosition4 = glm::inverse(UITransform) * vec4(apparentPosition, 1);
|
||||
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
|
||||
auto relativeDistance = glm::length(relativePosition);
|
||||
|
||||
// look at borrowed from overlays
|
||||
float elevation = -asinf(relativePosition.y / glm::length(relativePosition));
|
||||
float azimuth = atan2f(relativePosition.x, relativePosition.z);
|
||||
glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, -PI, 0)); // this extra *quat(vec3(0,-PI,0)) was required to get the quad to flip this seems like we could optimize
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(relativePosition);
|
||||
transform.setScale(reticleScale);
|
||||
transform.postScale(relativeDistance); // scale not quite working, distant things too large
|
||||
transform.setRotation(faceCamera);
|
||||
batch.setModelTransform(transform);
|
||||
} else {
|
||||
glm::mat4 overlayXfm;
|
||||
_modelTransform.getMatrix(overlayXfm);
|
||||
|
||||
auto reticlePosition = getReticlePosition();
|
||||
glm::vec2 projection = overlayToSpherical(reticlePosition);
|
||||
mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
mat4 reticleXfm = overlayXfm * pointerXfm;
|
||||
reticleXfm = glm::scale(reticleXfm, reticleScale);
|
||||
batch.setModelTransform(reticleXfm);
|
||||
}
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) {
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
||||
}
|
||||
return event->localPos();
|
||||
}
|
||||
|
||||
bool ApplicationCompositor::shouldCaptureMouse() const {
|
||||
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
|
||||
return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown();
|
||||
}
|
||||
|
||||
void ApplicationCompositor::setAllowMouseCapture(bool capture) {
|
||||
if (qApp->isHMDMode()) {
|
||||
if (capture) {
|
||||
qApp->showCursor(Qt::BlankCursor);
|
||||
} else {
|
||||
qApp->showCursor(Qt::ArrowCursor);
|
||||
}
|
||||
}
|
||||
_allowMouseCapture = capture;
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::handleLeaveEvent() {
|
||||
|
||||
if (shouldCaptureMouse()) {
|
||||
QWidget* mainWidget = (QWidget*)qApp->getWindow();
|
||||
QRect mainWidgetFrame = qApp->getRenderingGeometry();
|
||||
QRect uncoveredRect = mainWidgetFrame;
|
||||
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
|
||||
if (widget->isWindow() && widget->isVisible() && widget != mainWidget) {
|
||||
QRect widgetFrame = widget->frameGeometry();
|
||||
if (widgetFrame.intersects(uncoveredRect)) {
|
||||
QRect intersection = uncoveredRect & widgetFrame;
|
||||
if (intersection.top() > uncoveredRect.top()) {
|
||||
uncoveredRect.setBottom(intersection.top() - 1);
|
||||
} else if (intersection.bottom() < uncoveredRect.bottom()) {
|
||||
uncoveredRect.setTop(intersection.bottom() + 1);
|
||||
}
|
||||
|
||||
if (intersection.left() > uncoveredRect.left()) {
|
||||
uncoveredRect.setRight(intersection.left() - 1);
|
||||
} else if (intersection.right() < uncoveredRect.right()) {
|
||||
uncoveredRect.setLeft(intersection.right() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ignoreMouseMove = true;
|
||||
auto sendToPos = uncoveredRect.center();
|
||||
QCursor::setPos(sendToPos);
|
||||
_lastKnownRealMouse = sendToPos;
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplicationCompositor::handleRealMouseMoveEvent(bool sendFakeEvent) {
|
||||
|
||||
// If the mouse move came from a capture mouse related move, we completely ignore it.
|
||||
if (_ignoreMouseMove) {
|
||||
_ignoreMouseMove = false;
|
||||
return true; // swallow the event
|
||||
}
|
||||
|
||||
// If we're in HMD mode
|
||||
if (shouldCaptureMouse()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
auto newPosition = QCursor::pos();
|
||||
auto changeInRealMouse = newPosition - _lastKnownRealMouse;
|
||||
auto newReticlePosition = _reticlePositionInHMD + toGlm(changeInRealMouse);
|
||||
setReticlePosition(newReticlePosition, sendFakeEvent);
|
||||
_ignoreMouseMove = true;
|
||||
QCursor::setPos(QPoint(_lastKnownRealMouse.x(), _lastKnownRealMouse.y())); // move cursor back to where it was
|
||||
return true; // swallow the event
|
||||
} else {
|
||||
_lastKnownRealMouse = QCursor::pos();
|
||||
}
|
||||
return false; // let the caller know to process the event
|
||||
}
|
||||
|
||||
glm::vec2 ApplicationCompositor::getReticlePosition() const {
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
return _reticlePositionInHMD;
|
||||
}
|
||||
return toGlm(QCursor::pos());
|
||||
}
|
||||
|
||||
bool ApplicationCompositor::getReticleOverDesktop() const {
|
||||
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
|
||||
// but... if we're outside of the overlay area, we also want to call ourselves
|
||||
// as being over the desktop.
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = qApp->getUiSize();
|
||||
if (_reticlePositionInHMD.x < 0 || _reticlePositionInHMD.y < 0 ||
|
||||
_reticlePositionInHMD.x > maxOverlayPosition.x || _reticlePositionInHMD.y > maxOverlayPosition.y) {
|
||||
return true; // we are outside the overlay area, consider ourselves over the desktop
|
||||
}
|
||||
}
|
||||
return _isOverDesktop;
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFakeEvent) {
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
const float MOUSE_EXTENTS_VERT_ANGULAR_SIZE = 170.0f; // 5deg from poles
|
||||
const float MOUSE_EXTENTS_VERT_PIXELS = VIRTUAL_SCREEN_SIZE_Y * (MOUSE_EXTENTS_VERT_ANGULAR_SIZE / DEFAULT_HMD_UI_VERT_ANGULAR_SIZE);
|
||||
const float MOUSE_EXTENTS_HORZ_ANGULAR_SIZE = 360.0f; // full sphere
|
||||
const float MOUSE_EXTENTS_HORZ_PIXELS = VIRTUAL_SCREEN_SIZE_X * (MOUSE_EXTENTS_HORZ_ANGULAR_SIZE / DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE);
|
||||
|
||||
glm::vec2 maxOverlayPosition = qApp->getUiSize();
|
||||
float extaPixelsX = (MOUSE_EXTENTS_HORZ_PIXELS - maxOverlayPosition.x) / 2.0f;
|
||||
float extaPixelsY = (MOUSE_EXTENTS_VERT_PIXELS - maxOverlayPosition.y) / 2.0f;
|
||||
glm::vec2 mouseExtra { extaPixelsX, extaPixelsY };
|
||||
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
||||
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
|
||||
|
||||
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
|
||||
|
||||
if (sendFakeEvent) {
|
||||
// in HMD mode we need to fake our mouse moves...
|
||||
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
||||
auto button = Qt::NoButton;
|
||||
auto buttons = QApplication::mouseButtons();
|
||||
auto modifiers = QApplication::keyboardModifiers();
|
||||
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
|
||||
qApp->fakeMouseEvent(&event);
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPos = toGlm(QCursor::pos());
|
||||
auto distance = glm::distance(oldPos, position);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
|
||||
}
|
||||
|
||||
QCursor::setPos(position.x, position.y);
|
||||
}
|
||||
}
|
||||
|
||||
#include <QDesktopWidget>
|
||||
|
||||
glm::vec2 ApplicationCompositor::getReticleMaximumPosition() const {
|
||||
glm::vec2 result;
|
||||
if (qApp->isHMDMode()) {
|
||||
result = glm::vec2(VIRTUAL_SCREEN_SIZE_X, VIRTUAL_SCREEN_SIZE_Y);
|
||||
} else {
|
||||
QRect rec = QApplication::desktop()->screenGeometry();
|
||||
result = glm::vec2(rec.right(), rec.bottom());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const {
|
||||
auto surfacePointAt = sphereSurfaceFromOverlay(cursorPos); // in world space
|
||||
glm::vec3 worldSpaceCameraPosition = qApp->getCamera()->getPosition();
|
||||
origin = worldSpaceCameraPosition;
|
||||
direction = glm::normalize(surfacePointAt - worldSpaceCameraPosition);
|
||||
}
|
||||
|
||||
//Finds the collision point of a world space ray
|
||||
bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
|
||||
auto headPose = qApp->getHMDSensorPose();
|
||||
auto myCamera = qApp->getCamera();
|
||||
mat4 cameraMat = myCamera->getTransform();
|
||||
auto UITransform = cameraMat * glm::inverse(headPose);
|
||||
auto relativePosition4 = glm::inverse(UITransform) * vec4(position, 1);
|
||||
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
|
||||
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
|
||||
|
||||
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
|
||||
|
||||
float instersectionDistance;
|
||||
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
|
||||
result = position + glm::normalize(direction) * instersectionDistance;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApplicationCompositor::buildHemiVertices(
|
||||
const float fov, const float aspectRatio, const int slices, const int stacks) {
|
||||
static float textureFOV = 0.0f, textureAspectRatio = 1.0f;
|
||||
if (textureFOV == fov && textureAspectRatio == aspectRatio) {
|
||||
return;
|
||||
}
|
||||
|
||||
textureFOV = fov;
|
||||
textureAspectRatio = aspectRatio;
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
_hemiVertices = std::make_shared<gpu::Buffer>();
|
||||
_hemiIndices = std::make_shared<gpu::Buffer>();
|
||||
|
||||
|
||||
if (fov >= PI) {
|
||||
qDebug() << "TexturedHemisphere::buildVBO(): FOV greater or equal than Pi will create issues";
|
||||
}
|
||||
|
||||
//UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm
|
||||
|
||||
vec3 pos;
|
||||
vec2 uv;
|
||||
// Compute vertices positions and texture UV coordinate
|
||||
// Create and write to buffer
|
||||
for (int i = 0; i < stacks; i++) {
|
||||
uv.y = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f
|
||||
// abs(theta) <= fov / 2.0f
|
||||
float pitch = -fov * (uv.y - 0.5f);
|
||||
for (int j = 0; j < slices; j++) {
|
||||
uv.x = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f
|
||||
// abs(phi) <= fov * aspectRatio / 2.0f
|
||||
float yaw = -fov * aspectRatio * (uv.x - 0.5f);
|
||||
pos = getPoint(yaw, pitch);
|
||||
static const vec4 color(1);
|
||||
_hemiVertices->append(sizeof(pos), (gpu::Byte*)&pos);
|
||||
_hemiVertices->append(sizeof(vec2), (gpu::Byte*)&uv);
|
||||
_hemiVertices->append(sizeof(vec4), (gpu::Byte*)&color);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute number of indices needed
|
||||
static const int VERTEX_PER_TRANGLE = 3;
|
||||
static const int TRIANGLE_PER_RECTANGLE = 2;
|
||||
int numberOfRectangles = (slices - 1) * (stacks - 1);
|
||||
_hemiIndexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE;
|
||||
|
||||
// Compute indices order
|
||||
std::vector<GLushort> indices;
|
||||
for (int i = 0; i < stacks - 1; i++) {
|
||||
for (int j = 0; j < slices - 1; j++) {
|
||||
GLushort bottomLeftIndex = i * slices + j;
|
||||
GLushort bottomRightIndex = bottomLeftIndex + 1;
|
||||
GLushort topLeftIndex = bottomLeftIndex + slices;
|
||||
GLushort topRightIndex = topLeftIndex + 1;
|
||||
// FIXME make a z-order curve for better vertex cache locality
|
||||
indices.push_back(topLeftIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(topRightIndex);
|
||||
|
||||
indices.push_back(topRightIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(bottomRightIndex);
|
||||
}
|
||||
}
|
||||
_hemiIndices->append(sizeof(GLushort) * indices.size(), (gpu::Byte*)&indices[0]);
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::drawSphereSection(gpu::Batch& batch) {
|
||||
buildHemiVertices(_textureFov, _textureAspectRatio, 80, 80);
|
||||
static const int VERTEX_DATA_SLOT = 0;
|
||||
static const int TEXTURE_DATA_SLOT = 1;
|
||||
static const int COLOR_DATA_SLOT = 2;
|
||||
auto streamFormat = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
|
||||
streamFormat->setAttribute(gpu::Stream::POSITION, VERTEX_DATA_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
streamFormat->setAttribute(gpu::Stream::TEXCOORD, TEXTURE_DATA_SLOT, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||
streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_DATA_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA));
|
||||
batch.setInputFormat(streamFormat);
|
||||
|
||||
static const int VERTEX_STRIDE = sizeof(vec3) + sizeof(vec2) + sizeof(vec4);
|
||||
|
||||
if (_prevAlpha != _alpha) {
|
||||
// adjust alpha by munging vertex color alpha.
|
||||
// FIXME we should probably just use a uniform for this.
|
||||
float* floatPtr = reinterpret_cast<float*>(_hemiVertices->editData());
|
||||
const auto ALPHA_FLOAT_OFFSET = (sizeof(vec3) + sizeof(vec2) + sizeof(vec3)) / sizeof(float);
|
||||
const auto VERTEX_FLOAT_STRIDE = (sizeof(vec3) + sizeof(vec2) + sizeof(vec4)) / sizeof(float);
|
||||
const auto NUM_VERTS = _hemiVertices->getSize() / VERTEX_STRIDE;
|
||||
for (size_t i = 0; i < NUM_VERTS; i++) {
|
||||
floatPtr[i * VERTEX_FLOAT_STRIDE + ALPHA_FLOAT_OFFSET] = _alpha;
|
||||
}
|
||||
}
|
||||
|
||||
gpu::BufferView posView(_hemiVertices, 0, _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::POSITION)._element);
|
||||
gpu::BufferView uvView(_hemiVertices, sizeof(vec3), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::TEXCOORD)._element);
|
||||
gpu::BufferView colView(_hemiVertices, sizeof(vec3) + sizeof(vec2), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::COLOR)._element);
|
||||
batch.setInputBuffer(VERTEX_DATA_SLOT, posView);
|
||||
batch.setInputBuffer(TEXTURE_DATA_SLOT, uvView);
|
||||
batch.setInputBuffer(COLOR_DATA_SLOT, colView);
|
||||
batch.setIndexBuffer(gpu::UINT16, _hemiIndices, 0);
|
||||
batch.drawIndexed(gpu::TRIANGLES, _hemiIndexCount);
|
||||
}
|
||||
|
||||
glm::vec2 ApplicationCompositor::sphericalToOverlay(const glm::vec2& sphericalPos) const {
|
||||
glm::vec2 result = sphericalPos;
|
||||
result.x *= -1.0f;
|
||||
result /= _textureFov;
|
||||
result.x /= _textureAspectRatio;
|
||||
result += 0.5f;
|
||||
result *= qApp->getUiSize();
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 ApplicationCompositor::overlayToSpherical(const glm::vec2& overlayPos) const {
|
||||
glm::vec2 result = overlayPos;
|
||||
result /= qApp->getUiSize();
|
||||
result -= 0.5f;
|
||||
result *= _textureFov;
|
||||
result.x *= _textureAspectRatio;
|
||||
result.x *= -1.0f;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 ApplicationCompositor::overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const {
|
||||
auto headPose = qApp->getHMDSensorPose();
|
||||
auto myCamera = qApp->getCamera();
|
||||
mat4 cameraMat = myCamera->getTransform();
|
||||
auto UITransform = cameraMat * glm::inverse(headPose);
|
||||
auto relativePosition4 = glm::inverse(UITransform) * vec4(sphereSurfacePoint, 1);
|
||||
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
|
||||
auto center = vec3(0); // center of HUD in HUD space
|
||||
auto direction = relativePosition - center; // direction to relative position in HUD space
|
||||
glm::vec2 polar = glm::vec2(glm::atan(direction.x, -direction.z), glm::asin(direction.y)) * -1.0f;
|
||||
auto overlayPos = sphericalToOverlay(polar);
|
||||
return overlayPos;
|
||||
}
|
||||
|
||||
glm::vec3 ApplicationCompositor::sphereSurfaceFromOverlay(const glm::vec2& overlay) const {
|
||||
auto spherical = overlayToSpherical(overlay);
|
||||
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
|
||||
auto headPose = qApp->getHMDSensorPose();
|
||||
auto myCamera = qApp->getCamera();
|
||||
mat4 cameraMat = myCamera->getTransform();
|
||||
auto UITransform = cameraMat * glm::inverse(headPose);
|
||||
auto position4 = UITransform * vec4(sphereSurfacePoint, 1);
|
||||
auto position = vec3(position4) / position4.w;
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::updateTooltips() {
|
||||
if (_hoverItemId != _noItemId) {
|
||||
quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs;
|
||||
if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemTitle.isEmpty() && hoverDuration > TOOLTIP_DELAY) {
|
||||
// TODO Enable and position the tooltip
|
||||
_hoverItemEnterUsecs = UINT64_MAX;
|
||||
_tooltipId = Tooltip::showTip(_hoverItemTitle, _hoverItemDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const float FADE_DURATION = 500.0f;
|
||||
void ApplicationCompositor::fadeIn() {
|
||||
_fadeInAlpha = true;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(1.0f);
|
||||
_alphaPropertyAnimation->start();
|
||||
}
|
||||
void ApplicationCompositor::fadeOut() {
|
||||
_fadeInAlpha = false;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(0.0f);
|
||||
_alphaPropertyAnimation->start();
|
||||
}
|
||||
|
||||
void ApplicationCompositor::toggle() {
|
||||
if (_fadeInAlpha) {
|
||||
fadeOut();
|
||||
} else {
|
||||
fadeIn();
|
||||
}
|
||||
}
|
|
@ -244,34 +244,69 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder(RenderArgs* renderAr
|
|||
}
|
||||
}
|
||||
|
||||
static const auto COLOR_FORMAT = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
|
||||
static const auto DEFAULT_SAMPLER = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
|
||||
static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
|
||||
|
||||
std::mutex _textureGuard;
|
||||
using Lock = std::unique_lock<std::mutex>;
|
||||
std::queue<gpu::TexturePointer> _availableTextures;
|
||||
|
||||
void ApplicationOverlay::buildFramebufferObject() {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
auto uiSize = qApp->getUiSize();
|
||||
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
|
||||
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
|
||||
}
|
||||
|
||||
if (_overlayFramebuffer && uiSize == _overlayFramebuffer->getSize()) {
|
||||
// Already built
|
||||
return;
|
||||
auto width = uiSize.x;
|
||||
auto height = uiSize.y;
|
||||
if (!_overlayFramebuffer->getDepthStencilBuffer()) {
|
||||
auto overlayDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(DEPTH_FORMAT, width, height, DEFAULT_SAMPLER));
|
||||
_overlayFramebuffer->setDepthStencilBuffer(overlayDepthTexture, DEPTH_FORMAT);
|
||||
}
|
||||
|
||||
if (!_overlayFramebuffer->getRenderBuffer(0)) {
|
||||
gpu::TexturePointer newColorAttachment;
|
||||
{
|
||||
Lock lock(_textureGuard);
|
||||
if (!_availableTextures.empty()) {
|
||||
newColorAttachment = _availableTextures.front();
|
||||
_availableTextures.pop();
|
||||
}
|
||||
}
|
||||
if (newColorAttachment) {
|
||||
newColorAttachment->resize2D(width, height, newColorAttachment->getNumSamples());
|
||||
_overlayFramebuffer->setRenderBuffer(0, newColorAttachment);
|
||||
}
|
||||
}
|
||||
|
||||
if (_overlayFramebuffer) {
|
||||
_overlayFramebuffer.reset();
|
||||
_overlayDepthTexture.reset();
|
||||
_overlayColorTexture.reset();
|
||||
// If the overlay framebuffer still has no color attachment, no textures were available for rendering, so build a new one
|
||||
if (!_overlayFramebuffer->getRenderBuffer(0)) {
|
||||
auto colorBuffer = gpu::TexturePointer(gpu::Texture::create2D(COLOR_FORMAT, width, height, DEFAULT_SAMPLER));
|
||||
_overlayFramebuffer->setRenderBuffer(0, colorBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
gpu::TexturePointer ApplicationOverlay::acquireOverlay() {
|
||||
if (!_overlayFramebuffer) {
|
||||
return gpu::TexturePointer();
|
||||
}
|
||||
auto result = _overlayFramebuffer->getRenderBuffer(0);
|
||||
auto textureId = gpu::GLBackend::getTextureID(result, false);
|
||||
if (!textureId) {
|
||||
qDebug() << "Missing texture";
|
||||
}
|
||||
_overlayFramebuffer->setRenderBuffer(0, gpu::TexturePointer());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ApplicationOverlay::releaseOverlay(gpu::TexturePointer texture) {
|
||||
if (texture) {
|
||||
Lock lock(_textureGuard);
|
||||
_availableTextures.push(texture);
|
||||
} else {
|
||||
qWarning() << "Attempted to release null texture";
|
||||
}
|
||||
|
||||
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
|
||||
|
||||
auto colorFormat = gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA);
|
||||
auto width = uiSize.x;
|
||||
auto height = uiSize.y;
|
||||
|
||||
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
|
||||
_overlayColorTexture = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width, height, defaultSampler));
|
||||
_overlayFramebuffer->setRenderBuffer(0, _overlayColorTexture);
|
||||
|
||||
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
|
||||
_overlayDepthTexture = gpu::TexturePointer(gpu::Texture::create2D(depthFormat, width, height, defaultSampler));
|
||||
|
||||
_overlayFramebuffer->setDepthStencilBuffer(_overlayDepthTexture, depthFormat);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ public:
|
|||
|
||||
void renderOverlay(RenderArgs* renderArgs);
|
||||
|
||||
gpu::FramebufferPointer getOverlayFramebuffer() const { return _overlayFramebuffer; }
|
||||
gpu::TexturePointer acquireOverlay();
|
||||
void releaseOverlay(gpu::TexturePointer pointer);
|
||||
|
||||
private:
|
||||
void renderStatsAndLogs(RenderArgs* renderArgs);
|
||||
|
|
|
@ -220,6 +220,11 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) {
|
|||
} else {
|
||||
sendingMode << "S";
|
||||
}
|
||||
if (stats.isFullScene()) {
|
||||
sendingMode << "F";
|
||||
} else {
|
||||
sendingMode << "p";
|
||||
}
|
||||
}
|
||||
});
|
||||
sendingMode << " - " << serverCount << " servers";
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
//
|
||||
|
||||
#include <OffscreenUi.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <ScriptEngines.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <Preferences.h>
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "DialogsManager.h"
|
||||
|
|
|
@ -224,6 +224,12 @@ void Stats::updateStats(bool force) {
|
|||
} else {
|
||||
sendingModeStream << "S";
|
||||
}
|
||||
if (stats.isFullScene()) {
|
||||
sendingModeStream << "F";
|
||||
}
|
||||
else {
|
||||
sendingModeStream << "p";
|
||||
}
|
||||
}
|
||||
|
||||
// calculate server node totals
|
||||
|
|
|
@ -231,23 +231,19 @@ bool Overlays::editOverlay(unsigned int id, const QVariant& properties) {
|
|||
|
||||
Overlay::Pointer thisOverlay = getOverlay(id);
|
||||
if (thisOverlay) {
|
||||
thisOverlay->setProperties(properties.toMap());
|
||||
|
||||
if (thisOverlay->is3D()) {
|
||||
render::ItemKey oldItemKey = render::payloadGetKey(thisOverlay);
|
||||
|
||||
thisOverlay->setProperties(properties.toMap());
|
||||
|
||||
render::ItemKey itemKey = render::payloadGetKey(thisOverlay);
|
||||
if (itemKey != oldItemKey) {
|
||||
auto itemID = thisOverlay->getRenderItemID();
|
||||
if (render::Item::isValidID(itemID)) {
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
auto itemID = thisOverlay->getRenderItemID();
|
||||
if (render::Item::isValidID(itemID)) {
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
const render::Item& item = scene->getItem(itemID);
|
||||
if (item.getKey() != render::payloadGetKey(thisOverlay)) {
|
||||
render::PendingChanges pendingChanges;
|
||||
pendingChanges.resortItem(itemID, oldItemKey, itemKey);
|
||||
pendingChanges.updateItem(itemID);
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
thisOverlay->setProperties(properties.toMap());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -601,4 +597,4 @@ float Overlays::width() const {
|
|||
float Overlays::height() const {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->getWindow()->size().height();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
set(TARGET_NAME display-plugins)
|
||||
setup_hifi_library(OpenGL)
|
||||
link_hifi_libraries(shared plugins gl)
|
||||
link_hifi_libraries(shared plugins gl ui)
|
||||
|
||||
target_opengl()
|
||||
|
||||
GroupSources("src/display-plugins")
|
||||
|
||||
target_oglplus()
|
||||
|
||||
if (WIN32)
|
||||
add_dependency_external_projects(OpenVR)
|
||||
find_package(OpenVR REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES})
|
||||
endif()
|
||||
|
|
|
@ -35,9 +35,9 @@ void Basic2DWindowOpenGLDisplayPlugin::activate() {
|
|||
updateFramerate();
|
||||
}
|
||||
|
||||
void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
|
||||
void Basic2DWindowOpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
|
||||
_wantVsync = true; // always
|
||||
WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture, sceneSize);
|
||||
WindowOpenGLDisplayPlugin::submitSceneTexture(frameIndex, sceneTexture);
|
||||
}
|
||||
|
||||
void Basic2DWindowOpenGLDisplayPlugin::internalPresent() {
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
|
||||
virtual void activate() override;
|
||||
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
|
||||
|
||||
virtual void internalPresent() override;
|
||||
|
||||
|
|
|
@ -0,0 +1,476 @@
|
|||
//
|
||||
// Created by Benjamin Arnold on 5/27/14.
|
||||
// Copyright 2014 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
|
||||
//
|
||||
|
||||
#include "CompositorHelper.h"
|
||||
|
||||
#include <memory>
|
||||
#include <math.h>
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include <ui/Menu.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <CursorManager.h>
|
||||
#include <gl/GLWidget.h>
|
||||
|
||||
// Used to animate the magnification windows
|
||||
|
||||
//static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
|
||||
|
||||
static const float reticleSize = TWO_PI / 100.0f;
|
||||
|
||||
//EntityItemID CompositorHelper::_noItemId;
|
||||
static QString _tooltipId;
|
||||
|
||||
const uvec2 CompositorHelper::VIRTUAL_SCREEN_SIZE = uvec2(3960, 1188); // ~10% more pixel density than old version, 72dx240d FOV
|
||||
const float CompositorHelper::VIRTUAL_UI_ASPECT_RATIO = (float)VIRTUAL_SCREEN_SIZE.x / (float)VIRTUAL_SCREEN_SIZE.y;
|
||||
const vec2 CompositorHelper::VIRTUAL_UI_TARGET_FOV = vec2(PI * 3.0f / 2.0f, PI * 3.0f / 2.0f / VIRTUAL_UI_ASPECT_RATIO);
|
||||
const vec2 CompositorHelper::MOUSE_EXTENTS_ANGULAR_SIZE = vec2(PI * 2.0f, PI * 0.95f); // horizontal: full sphere, vertical: ~5deg from poles
|
||||
const vec2 CompositorHelper::MOUSE_EXTENTS_PIXELS = vec2(VIRTUAL_SCREEN_SIZE) * (MOUSE_EXTENTS_ANGULAR_SIZE / VIRTUAL_UI_TARGET_FOV);
|
||||
|
||||
// Return a point's cartesian coordinates on a sphere from pitch and yaw
|
||||
glm::vec3 getPoint(float yaw, float pitch) {
|
||||
return glm::vec3(glm::cos(-pitch) * (-glm::sin(yaw)),
|
||||
glm::sin(-pitch),
|
||||
glm::cos(-pitch) * (-glm::cos(yaw)));
|
||||
}
|
||||
|
||||
// FIXME move to GLMHelpers
|
||||
//Checks if the given ray intersects the sphere at the origin. result will store a multiplier that should
|
||||
//be multiplied by dir and added to origin to get the location of the collision
|
||||
bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, float* result)
|
||||
{
|
||||
//Source: http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
|
||||
|
||||
//Compute A, B and C coefficients
|
||||
float a = glm::dot(dir, dir);
|
||||
float b = 2 * glm::dot(dir, origin);
|
||||
float c = glm::dot(origin, origin) - (r * r);
|
||||
|
||||
//Find discriminant
|
||||
float disc = b * b - 4 * a * c;
|
||||
|
||||
// if discriminant is negative there are no real roots, so return
|
||||
// false as ray misses sphere
|
||||
if (disc < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compute q as described above
|
||||
float distSqrt = sqrtf(disc);
|
||||
float q;
|
||||
if (b < 0) {
|
||||
q = (-b - distSqrt) / 2.0f;
|
||||
} else {
|
||||
q = (-b + distSqrt) / 2.0f;
|
||||
}
|
||||
|
||||
// compute t0 and t1
|
||||
float t0 = q / a;
|
||||
float t1 = c / q;
|
||||
|
||||
// make sure t0 is smaller than t1
|
||||
if (t0 > t1) {
|
||||
// if t0 is bigger than t1 swap them around
|
||||
float temp = t0;
|
||||
t0 = t1;
|
||||
t1 = temp;
|
||||
}
|
||||
|
||||
// if t1 is less than zero, the object is in the ray's negative direction
|
||||
// and consequently the ray misses the sphere
|
||||
if (t1 < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if t0 is less than zero, the intersection point is at t1
|
||||
if (t0 < 0) {
|
||||
*result = t1;
|
||||
return true;
|
||||
} else { // else the intersection point is at t0
|
||||
*result = t0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CompositorHelper::CompositorHelper() :
|
||||
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
|
||||
_reticleInterface(new ReticleInterface(this))
|
||||
{
|
||||
// FIX in a separate PR addressing the current mouse over entity bug
|
||||
//auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
//connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
||||
// if (_hoverItemId != entityItemID) {
|
||||
// _hoverItemId = entityItemID;
|
||||
// _hoverItemEnterUsecs = usecTimestampNow();
|
||||
// auto properties = entityScriptingInterface->getEntityProperties(_hoverItemId);
|
||||
|
||||
// // check the format of this href string before we parse it
|
||||
// QString hrefString = properties.getHref();
|
||||
|
||||
// auto cursor = Cursor::Manager::instance().getCursor();
|
||||
// if (!hrefString.isEmpty()) {
|
||||
// if (!hrefString.startsWith("hifi:")) {
|
||||
// hrefString.prepend("hifi://");
|
||||
// }
|
||||
|
||||
// // parse out a QUrl from the hrefString
|
||||
// QUrl href = QUrl(hrefString);
|
||||
|
||||
// _hoverItemTitle = href.host();
|
||||
// _hoverItemDescription = properties.getDescription();
|
||||
// cursor->setIcon(Cursor::Icon::LINK);
|
||||
// } else {
|
||||
// _hoverItemTitle.clear();
|
||||
// _hoverItemDescription.clear();
|
||||
// cursor->setIcon(Cursor::Icon::DEFAULT);
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
|
||||
//connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) {
|
||||
// if (_hoverItemId == entityItemID) {
|
||||
// _hoverItemId = _noItemId;
|
||||
|
||||
// _hoverItemTitle.clear();
|
||||
// _hoverItemDescription.clear();
|
||||
|
||||
// auto cursor = Cursor::Manager::instance().getCursor();
|
||||
// cursor->setIcon(Cursor::Icon::DEFAULT);
|
||||
// if (!_tooltipId.isEmpty()) {
|
||||
// qDebug() << "Closing tooltip " << _tooltipId;
|
||||
// Tooltip::closeTip(_tooltipId);
|
||||
// _tooltipId.clear();
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
}
|
||||
|
||||
|
||||
bool CompositorHelper::isHMD() const {
|
||||
return _currentDisplayPlugin && _currentDisplayPlugin->isHmd();
|
||||
}
|
||||
|
||||
QPointF CompositorHelper::getMouseEventPosition(QMouseEvent* event) {
|
||||
if (isHMD()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
||||
}
|
||||
return event->localPos();
|
||||
}
|
||||
|
||||
bool CompositorHelper::shouldCaptureMouse() const {
|
||||
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
|
||||
return _allowMouseCapture && isHMD() && QApplication::activeWindow() && !ui::Menu::isSomeSubmenuShown();
|
||||
}
|
||||
|
||||
void CompositorHelper::setAllowMouseCapture(bool capture) {
|
||||
if (capture != _allowMouseCapture) {
|
||||
_allowMouseCapture = capture;
|
||||
emit allowMouseCaptureChanged();
|
||||
}
|
||||
_allowMouseCapture = capture;
|
||||
}
|
||||
|
||||
void CompositorHelper::handleLeaveEvent() {
|
||||
if (shouldCaptureMouse()) {
|
||||
|
||||
//QWidget* mainWidget = (QWidget*)qApp->getWindow();
|
||||
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
|
||||
static QWidget* mainWidget = nullptr;
|
||||
if (mainWidget == nullptr) {
|
||||
mainWidget = renderingWidget->parentWidget();
|
||||
}
|
||||
QRect mainWidgetFrame;
|
||||
{
|
||||
mainWidgetFrame = renderingWidget->geometry();
|
||||
auto topLeft = mainWidgetFrame.topLeft();
|
||||
auto topLeftScreen = renderingWidget->mapToGlobal(topLeft);
|
||||
mainWidgetFrame.moveTopLeft(topLeftScreen);
|
||||
}
|
||||
QRect uncoveredRect = mainWidgetFrame;
|
||||
foreach(QWidget* widget, QApplication::topLevelWidgets()) {
|
||||
if (widget->isWindow() && widget->isVisible() && widget != mainWidget) {
|
||||
QRect widgetFrame = widget->frameGeometry();
|
||||
if (widgetFrame.intersects(uncoveredRect)) {
|
||||
QRect intersection = uncoveredRect & widgetFrame;
|
||||
if (intersection.top() > uncoveredRect.top()) {
|
||||
uncoveredRect.setBottom(intersection.top() - 1);
|
||||
} else if (intersection.bottom() < uncoveredRect.bottom()) {
|
||||
uncoveredRect.setTop(intersection.bottom() + 1);
|
||||
}
|
||||
|
||||
if (intersection.left() > uncoveredRect.left()) {
|
||||
uncoveredRect.setRight(intersection.left() - 1);
|
||||
} else if (intersection.right() < uncoveredRect.right()) {
|
||||
uncoveredRect.setLeft(intersection.right() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ignoreMouseMove = true;
|
||||
auto sendToPos = uncoveredRect.center();
|
||||
QCursor::setPos(sendToPos);
|
||||
_lastKnownRealMouse = sendToPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CompositorHelper::handleRealMouseMoveEvent(bool sendFakeEvent) {
|
||||
// If the mouse move came from a capture mouse related move, we completely ignore it.
|
||||
if (_ignoreMouseMove) {
|
||||
_ignoreMouseMove = false;
|
||||
return true; // swallow the event
|
||||
}
|
||||
|
||||
// If we're in HMD mode
|
||||
if (shouldCaptureMouse()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
auto newPosition = QCursor::pos();
|
||||
auto changeInRealMouse = newPosition - _lastKnownRealMouse;
|
||||
auto newReticlePosition = _reticlePositionInHMD + toGlm(changeInRealMouse);
|
||||
setReticlePosition(newReticlePosition, sendFakeEvent);
|
||||
_ignoreMouseMove = true;
|
||||
QCursor::setPos(QPoint(_lastKnownRealMouse.x(), _lastKnownRealMouse.y())); // move cursor back to where it was
|
||||
return true; // swallow the event
|
||||
} else {
|
||||
_lastKnownRealMouse = QCursor::pos();
|
||||
}
|
||||
return false; // let the caller know to process the event
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::getReticlePosition() const {
|
||||
if (isHMD()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
return _reticlePositionInHMD;
|
||||
}
|
||||
return toGlm(QCursor::pos());
|
||||
}
|
||||
|
||||
bool CompositorHelper::getReticleOverDesktop() const {
|
||||
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
|
||||
// but... if we're outside of the overlay area, we also want to call ourselves
|
||||
// as being over the desktop.
|
||||
if (isHMD()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
||||
static const glm::vec2 minOverlayPosition;
|
||||
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
|
||||
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return _isOverDesktop;
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::getReticleMaximumPosition() const {
|
||||
glm::vec2 result;
|
||||
if (isHMD()) {
|
||||
result = VIRTUAL_SCREEN_SIZE;
|
||||
} else {
|
||||
QRect rec = QApplication::desktop()->screenGeometry();
|
||||
result = glm::vec2(rec.right(), rec.bottom());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
|
||||
if (isHMD()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
|
||||
// FIXME don't allow negative mouseExtra
|
||||
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
|
||||
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
||||
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
|
||||
|
||||
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
|
||||
|
||||
if (sendFakeEvent) {
|
||||
// in HMD mode we need to fake our mouse moves...
|
||||
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
|
||||
auto button = Qt::NoButton;
|
||||
auto buttons = QApplication::mouseButtons();
|
||||
auto modifiers = QApplication::keyboardModifiers();
|
||||
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
|
||||
if (qApp->thread() == QThread::currentThread()) {
|
||||
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
|
||||
_fakeMouseEvent = true;
|
||||
qApp->sendEvent(renderingWidget, &event);
|
||||
_fakeMouseEvent = false;
|
||||
} else {
|
||||
qApp->postEvent(renderingWidget, new QMouseEvent(QEvent::MouseMove, globalPos, button, buttons, modifiers));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPos = toGlm(QCursor::pos());
|
||||
auto distance = glm::distance(oldPos, position);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" <<
|
||||
distance << " oldPos:" << oldPos.x << "," << oldPos.y << " newPos:" << position.x << "," << position.y;
|
||||
}
|
||||
|
||||
QCursor::setPos(position.x, position.y);
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& origin, glm::vec3& direction) const {
|
||||
auto surfacePointAt = sphereSurfaceFromOverlay(cursorPos); // in world space
|
||||
origin = vec3(_currentCamera[3]);
|
||||
direction = glm::normalize(surfacePointAt - origin);
|
||||
}
|
||||
|
||||
glm::mat4 CompositorHelper::getUiTransform() const {
|
||||
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose(_currentFrame));
|
||||
}
|
||||
|
||||
//Finds the collision point of a world space ray
|
||||
bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const {
|
||||
auto UITransform = getUiTransform();
|
||||
auto relativePosition4 = glm::inverse(UITransform) * vec4(position, 1);
|
||||
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
|
||||
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
|
||||
|
||||
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
|
||||
|
||||
float instersectionDistance;
|
||||
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
|
||||
result = position + glm::normalize(direction) * instersectionDistance;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const {
|
||||
glm::vec2 result = sphericalPos;
|
||||
result.x *= -1.0f;
|
||||
result /= _textureFov;
|
||||
result.x /= _textureAspectRatio;
|
||||
result += 0.5f;
|
||||
result *= _currentDisplayPlugin->getRecommendedUiSize();
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::overlayToSpherical(const glm::vec2& overlayPos) const {
|
||||
glm::vec2 result = overlayPos;
|
||||
result /= _currentDisplayPlugin->getRecommendedUiSize();
|
||||
result -= 0.5f;
|
||||
result *= _textureFov;
|
||||
result.x *= _textureAspectRatio;
|
||||
result.x *= -1.0f;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec2 CompositorHelper::overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const {
|
||||
auto UITransform = getUiTransform();
|
||||
auto relativePosition4 = glm::inverse(UITransform) * vec4(sphereSurfacePoint, 1);
|
||||
auto direction = vec3(relativePosition4) / relativePosition4.w;
|
||||
// FIXME use a GLMHelper cartesianToSpherical after fixing the rotation signs.
|
||||
glm::vec2 polar = glm::vec2(glm::atan(direction.x, -direction.z), glm::asin(direction.y)) * -1.0f;
|
||||
auto overlayPos = sphericalToOverlay(polar);
|
||||
return overlayPos;
|
||||
}
|
||||
|
||||
glm::vec3 CompositorHelper::sphereSurfaceFromOverlay(const glm::vec2& overlay) const {
|
||||
auto spherical = overlayToSpherical(overlay);
|
||||
// FIXME use a GLMHelper sphericalToCartesian after fixing the rotation signs.
|
||||
auto sphereSurfacePoint = getPoint(spherical.x, spherical.y);
|
||||
auto UITransform = getUiTransform();
|
||||
auto position4 = UITransform * vec4(sphereSurfacePoint, 1);
|
||||
return vec3(position4) / position4.w;
|
||||
}
|
||||
|
||||
void CompositorHelper::updateTooltips() {
|
||||
//if (_hoverItemId != _noItemId) {
|
||||
// quint64 hoverDuration = usecTimestampNow() - _hoverItemEnterUsecs;
|
||||
// if (_hoverItemEnterUsecs != UINT64_MAX && !_hoverItemTitle.isEmpty() && hoverDuration > TOOLTIP_DELAY) {
|
||||
// // TODO Enable and position the tooltip
|
||||
// _hoverItemEnterUsecs = UINT64_MAX;
|
||||
// _tooltipId = Tooltip::showTip(_hoverItemTitle, _hoverItemDescription);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
static const float FADE_DURATION = 500.0f;
|
||||
void CompositorHelper::fadeIn() {
|
||||
_fadeInAlpha = true;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(1.0f);
|
||||
_alphaPropertyAnimation->start();
|
||||
}
|
||||
|
||||
void CompositorHelper::fadeOut() {
|
||||
_fadeInAlpha = false;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(0.0f);
|
||||
_alphaPropertyAnimation->start();
|
||||
}
|
||||
|
||||
void CompositorHelper::toggle() {
|
||||
if (_fadeInAlpha) {
|
||||
fadeOut();
|
||||
} else {
|
||||
fadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const {
|
||||
glm::mat4 result;
|
||||
if (isHMD()) {
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
|
||||
auto reticlePosition = getReticlePosition();
|
||||
auto spherical = overlayToSpherical(reticlePosition);
|
||||
// The pointer transform relative to the sensor
|
||||
auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
float reticleDepth = getReticleDepth();
|
||||
if (reticleDepth != 1.0f) {
|
||||
// Cursor position in UI space
|
||||
auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w;
|
||||
// Ray to the cursor, in UI space
|
||||
auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth;
|
||||
// Move the ray to be relative to the head pose
|
||||
pointerTransform[3] = vec4(cursorRay + headPosition, 1);
|
||||
// Scale up the cursor because of distance
|
||||
reticleScale *= reticleDepth;
|
||||
}
|
||||
glm::mat4 overlayXfm;
|
||||
_modelTransform.getMatrix(overlayXfm);
|
||||
pointerTransform = overlayXfm * pointerTransform;
|
||||
pointerTransform = glm::inverse(eyePose) * pointerTransform;
|
||||
result = glm::scale(pointerTransform, reticleScale);
|
||||
} else {
|
||||
static const float CURSOR_PIXEL_SIZE = 32.0f;
|
||||
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
|
||||
const auto canvasSize = vec2(toGlm(renderingWidget->size()));;
|
||||
vec2 mousePosition = toGlm(renderingWidget->mapFromGlobal(QCursor::pos()));
|
||||
mousePosition /= canvasSize;
|
||||
mousePosition *= 2.0;
|
||||
mousePosition -= 1.0;
|
||||
mousePosition.y *= -1.0f;
|
||||
|
||||
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
|
||||
return glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f));
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -6,59 +6,55 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ApplicationCompositor_h
|
||||
#define hifi_ApplicationCompositor_h
|
||||
#ifndef hifi_display_plugins_Compositor_h
|
||||
#define hifi_display_plugins_Compositor_h
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include <QCursor>
|
||||
#include <QMouseEvent>
|
||||
#include <QObject>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QtCore/QPropertyAnimation>
|
||||
#include <QtGui/QCursor>
|
||||
#include <QtGui/QMouseEvent>
|
||||
|
||||
#include <EntityItemID.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Texture.h>
|
||||
#include <Transform.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "DisplayPlugin.h"
|
||||
|
||||
class Camera;
|
||||
class PalmData;
|
||||
class RenderArgs;
|
||||
class ReticleInterface;
|
||||
|
||||
|
||||
const float DEFAULT_RETICLE_DEPTH = 1.0f; // FIXME - probably should be based on UI radius
|
||||
|
||||
const float MAGNIFY_WIDTH = 220.0f;
|
||||
const float MAGNIFY_HEIGHT = 100.0f;
|
||||
const float MAGNIFY_MULT = 2.0f;
|
||||
|
||||
const int VIRTUAL_SCREEN_SIZE_X = 3960; // ~10% more pixel density than old version, 72dx240d FOV
|
||||
const int VIRTUAL_SCREEN_SIZE_Y = 1188; // ~10% more pixel density than old version, 72dx240d FOV
|
||||
const float DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE = 240.0f;
|
||||
const float DEFAULT_HMD_UI_VERT_ANGULAR_SIZE = DEFAULT_HMD_UI_HORZ_ANGULAR_SIZE * (float)VIRTUAL_SCREEN_SIZE_Y / (float)VIRTUAL_SCREEN_SIZE_X;
|
||||
|
||||
// Handles the drawing of the overlays to the screen
|
||||
// TODO, move divide up the rendering, displaying and input handling
|
||||
// facilities of this class
|
||||
class ApplicationCompositor : public QObject {
|
||||
class CompositorHelper : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha)
|
||||
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
|
||||
public:
|
||||
ApplicationCompositor();
|
||||
~ApplicationCompositor();
|
||||
static const uvec2 VIRTUAL_SCREEN_SIZE;
|
||||
static const float VIRTUAL_UI_ASPECT_RATIO;
|
||||
static const vec2 VIRTUAL_UI_TARGET_FOV;
|
||||
static const vec2 MOUSE_EXTENTS_ANGULAR_SIZE;
|
||||
static const vec2 MOUSE_EXTENTS_PIXELS;
|
||||
|
||||
void displayOverlayTexture(RenderArgs* renderArgs);
|
||||
void displayOverlayTextureHmd(RenderArgs* renderArgs, int eye);
|
||||
CompositorHelper();
|
||||
|
||||
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
|
||||
|
||||
float getHmdUIAngularSize() const { return _hmdUIAngularSize; }
|
||||
void setHmdUIAngularSize(float hmdUIAngularSize) { _hmdUIAngularSize = hmdUIAngularSize; }
|
||||
bool isHMD() const;
|
||||
bool fakeEventActive() const { return _fakeMouseEvent; }
|
||||
|
||||
// Converter from one frame of reference to another.
|
||||
// Frame of reference:
|
||||
|
@ -66,8 +62,7 @@ public:
|
|||
// Overlay: Position on the overlay (x,y)
|
||||
glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
|
||||
glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
|
||||
void computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const;
|
||||
uint32_t getOverlayTexture() const;
|
||||
void computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& origin, glm::vec3& direction) const;
|
||||
|
||||
glm::vec2 overlayFromSphereSurface(const glm::vec3& sphereSurfacePoint) const;
|
||||
glm::vec3 sphereSurfaceFromOverlay(const glm::vec2& overlay) const;
|
||||
|
@ -93,10 +88,12 @@ public:
|
|||
void resetReticleDepth() { _reticleDepth = DEFAULT_RETICLE_DEPTH; }
|
||||
|
||||
glm::vec2 getReticlePosition() const;
|
||||
void setReticlePosition(glm::vec2 position, bool sendFakeEvent = true);
|
||||
void setReticlePosition(const glm::vec2& position, bool sendFakeEvent = true);
|
||||
|
||||
glm::vec2 getReticleMaximumPosition() const;
|
||||
|
||||
glm::mat4 getReticleTransform(const glm::mat4& eyePose = glm::mat4(), const glm::vec3& headPosition = glm::vec3()) const;
|
||||
|
||||
ReticleInterface* getReticleInterface() { return _reticleInterface; }
|
||||
|
||||
/// return value - true means the caller should not process the event further
|
||||
|
@ -113,34 +110,38 @@ public:
|
|||
bool getReticleOverDesktop() const;
|
||||
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
|
||||
|
||||
private:
|
||||
bool _isOverDesktop { true };
|
||||
void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; }
|
||||
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; }
|
||||
|
||||
void displayOverlayTextureStereo(RenderArgs* renderArgs, float aspectRatio, float fov);
|
||||
void bindCursorTexture(gpu::Batch& batch, uint8_t cursorId = 0);
|
||||
void buildHemiVertices(const float fov, const float aspectRatio, const int slices, const int stacks);
|
||||
void drawSphereSection(gpu::Batch& batch);
|
||||
signals:
|
||||
void allowMouseCaptureChanged();
|
||||
|
||||
private:
|
||||
glm::mat4 getUiTransform() const;
|
||||
void updateTooltips();
|
||||
|
||||
// Support for hovering and tooltips
|
||||
static EntityItemID _noItemId;
|
||||
EntityItemID _hoverItemId { _noItemId };
|
||||
QString _hoverItemTitle;
|
||||
QString _hoverItemDescription;
|
||||
quint64 _hoverItemEnterUsecs { 0 };
|
||||
DisplayPluginPointer _currentDisplayPlugin;
|
||||
glm::mat4 _currentCamera;
|
||||
uint32_t _currentFrame { 0 };
|
||||
|
||||
float _hmdUIAngularSize { DEFAULT_HMD_UI_VERT_ANGULAR_SIZE };
|
||||
float _textureFov { glm::radians(DEFAULT_HMD_UI_VERT_ANGULAR_SIZE) };
|
||||
float _textureAspectRatio { 1.0f };
|
||||
int _hemiVerticesID { GeometryCache::UNKNOWN_ID };
|
||||
//// Support for hovering and tooltips
|
||||
//static EntityItemID _noItemId;
|
||||
//EntityItemID _hoverItemId { _noItemId };
|
||||
|
||||
float _alpha { 0.0f }; // hidden by default
|
||||
//QString _hoverItemTitle;
|
||||
//QString _hoverItemDescription;
|
||||
//quint64 _hoverItemEnterUsecs { 0 };
|
||||
|
||||
bool _isOverDesktop { true };
|
||||
float _hmdUIAngularSize { glm::degrees(VIRTUAL_UI_TARGET_FOV.y) };
|
||||
float _textureFov { VIRTUAL_UI_TARGET_FOV.y };
|
||||
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
|
||||
|
||||
float _alpha { 1.0f };
|
||||
float _prevAlpha { 1.0f };
|
||||
float _fadeInAlpha { true };
|
||||
float _oculusUIRadius { 1.0f };
|
||||
|
||||
QMap<uint16_t, gpu::TexturePointer> _cursors;
|
||||
|
||||
int _reticleQuad;
|
||||
|
||||
int _previousBorderWidth { -1 };
|
||||
|
@ -167,7 +168,9 @@ private:
|
|||
|
||||
bool _allowMouseCapture { true };
|
||||
|
||||
ReticleInterface* _reticleInterface;
|
||||
bool _fakeMouseEvent { false };
|
||||
|
||||
ReticleInterface* _reticleInterface { nullptr };
|
||||
};
|
||||
|
||||
// Scripting interface available to control the Reticle
|
||||
|
@ -182,7 +185,7 @@ class ReticleInterface : public QObject {
|
|||
Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay)
|
||||
|
||||
public:
|
||||
ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {}
|
||||
ReticleInterface(CompositorHelper* outer) : QObject(outer), _compositor(outer) {}
|
||||
|
||||
Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); }
|
||||
|
||||
|
@ -203,9 +206,7 @@ public:
|
|||
Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); }
|
||||
|
||||
private:
|
||||
ApplicationCompositor* _compositor;
|
||||
CompositorHelper* _compositor;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_ApplicationCompositor_h
|
||||
#endif // hifi_CompositorHelper_h
|
|
@ -22,11 +22,11 @@ bool NullDisplayPlugin::hasFocus() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
|
||||
void NullDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
|
||||
_container->releaseSceneTexture(sceneTexture);
|
||||
}
|
||||
|
||||
void NullDisplayPlugin::submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) {
|
||||
void NullDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overlayTexture) {
|
||||
_container->releaseOverlayTexture(overlayTexture);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ public:
|
|||
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
virtual bool hasFocus() const override;
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
|
||||
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override;
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
|
||||
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) override;
|
||||
virtual QImage getScreenshot() const override;
|
||||
private:
|
||||
static const QString NAME;
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
#include <gl/Config.h>
|
||||
#include <gl/GLEscrow.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <CursorManager.h>
|
||||
#include "CompositorHelper.h"
|
||||
|
||||
#if THREADED_PRESENT
|
||||
|
||||
|
@ -184,13 +187,16 @@ private:
|
|||
#endif
|
||||
|
||||
OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
|
||||
_sceneTextureEscrow.setRecycler([this](GLuint texture){
|
||||
_sceneTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture){
|
||||
cleanupForSceneTexture(texture);
|
||||
_container->releaseSceneTexture(texture);
|
||||
});
|
||||
_overlayTextureEscrow.setRecycler([this](const gpu::TexturePointer& texture) {
|
||||
_container->releaseOverlayTexture(texture);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) {
|
||||
void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) {
|
||||
Lock lock(_mutex);
|
||||
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
|
||||
_sceneTextureToFrameIndexMap.remove(sceneTexture);
|
||||
|
@ -198,15 +204,26 @@ void OpenGLDisplayPlugin::cleanupForSceneTexture(uint32_t sceneTexture) {
|
|||
|
||||
|
||||
void OpenGLDisplayPlugin::activate() {
|
||||
_vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported();
|
||||
if (!_cursorsData.size()) {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
for (const auto iconId : cursorManager.registeredIcons()) {
|
||||
auto& cursorData = _cursorsData[iconId];
|
||||
auto iconPath = cursorManager.getIconImage(iconId);
|
||||
auto image = QImage(iconPath);
|
||||
image = image.mirrored();
|
||||
image = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
cursorData.image = image;
|
||||
cursorData.size = toGlm(image.size());
|
||||
cursorData.hotSpot = vec2(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
_vsyncSupported = _container->getPrimaryWidget()->isVsyncSupported();
|
||||
#if THREADED_PRESENT
|
||||
// Start the present thread if necessary
|
||||
auto presentThread = DependencyManager::get<PresentThread>();
|
||||
if (!presentThread) {
|
||||
auto widget = _container->getPrimaryWidget();
|
||||
|
||||
|
||||
DependencyManager::set<PresentThread>();
|
||||
presentThread = DependencyManager::get<PresentThread>();
|
||||
presentThread->setObjectName("Presentation Thread");
|
||||
|
@ -242,6 +259,7 @@ void OpenGLDisplayPlugin::deactivate() {
|
|||
DisplayPlugin::deactivate();
|
||||
}
|
||||
|
||||
|
||||
void OpenGLDisplayPlugin::customizeContext() {
|
||||
#if THREADED_PRESENT
|
||||
_uncustomized = false;
|
||||
|
@ -250,6 +268,20 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
#endif
|
||||
enableVsync();
|
||||
|
||||
for (auto& cursorValue : _cursorsData) {
|
||||
auto& cursorData = cursorValue.second;
|
||||
if (!cursorData.texture) {
|
||||
const auto& image = cursorData.image;
|
||||
glGenTextures(1, &cursorData.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
using namespace oglplus;
|
||||
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
|
||||
Context::Disable(Capability::Blend);
|
||||
|
@ -257,10 +289,24 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
Context::Disable(Capability::CullFace);
|
||||
|
||||
_program = loadDefaultShader();
|
||||
|
||||
auto uniforms = _program->ActiveUniforms();
|
||||
while (!uniforms.Empty()) {
|
||||
auto uniform = uniforms.Front();
|
||||
if (uniform.Name() == "mvp") {
|
||||
_mvpUniform = uniform.Index();
|
||||
}
|
||||
uniforms.Next();
|
||||
}
|
||||
|
||||
_plane = loadPlane(_program);
|
||||
|
||||
_compositeFramebuffer = std::make_shared<BasicFramebufferWrapper>();
|
||||
_compositeFramebuffer->Init(getRecommendedRenderSize());
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::uncustomizeContext() {
|
||||
_compositeFramebuffer.reset();
|
||||
_program.reset();
|
||||
_plane.reset();
|
||||
}
|
||||
|
@ -308,7 +354,7 @@ bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) {
|
||||
void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) {
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
_sceneTextureToFrameIndexMap[sceneTexture] = frameIndex;
|
||||
|
@ -326,15 +372,27 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, uint32_t scene
|
|||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::submitOverlayTexture(GLuint overlayTexture, const glm::uvec2& overlaySize) {
|
||||
void OpenGLDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overlayTexture) {
|
||||
// Submit it to the presentation thread via escrow
|
||||
_currentOverlayTexture = overlayTexture;
|
||||
_overlayTextureEscrow.submit(overlayTexture);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateTextures() {
|
||||
auto oldSceneTexture = _currentSceneTexture;
|
||||
_currentSceneTexture = _sceneTextureEscrow.fetchAndRelease(_currentSceneTexture);
|
||||
if (oldSceneTexture != _currentSceneTexture) {
|
||||
updateFrameData();
|
||||
}
|
||||
|
||||
_currentOverlayTexture = _overlayTextureEscrow.fetchAndRelease(_currentOverlayTexture);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateFrameData() {
|
||||
Lock lock(_mutex);
|
||||
_currentRenderFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
|
||||
}
|
||||
|
||||
|
||||
void OpenGLDisplayPlugin::updateFramerate() {
|
||||
uint64_t now = usecTimestampNow();
|
||||
static uint64_t lastSwapEnd { now };
|
||||
|
@ -346,14 +404,81 @@ void OpenGLDisplayPlugin::updateFramerate() {
|
|||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeOverlay() {
|
||||
using namespace oglplus;
|
||||
// Overlay draw
|
||||
if (isStereo()) {
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
});
|
||||
} else {
|
||||
// Overlay draw
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
drawUnitQuad();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositePointer() {
|
||||
using namespace oglplus;
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
|
||||
if (isStereo()) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
});
|
||||
} else {
|
||||
drawUnitQuad();
|
||||
}
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeLayers() {
|
||||
using namespace oglplus;
|
||||
auto targetRenderSize = getRecommendedRenderSize();
|
||||
if (!_compositeFramebuffer || _compositeFramebuffer->size != targetRenderSize) {
|
||||
_compositeFramebuffer = std::make_shared<BasicFramebufferWrapper>();
|
||||
_compositeFramebuffer->Init(targetRenderSize);
|
||||
}
|
||||
_compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] {
|
||||
Context::Viewport(targetRenderSize.x, targetRenderSize.y);
|
||||
Context::Clear().DepthBuffer();
|
||||
glBindTexture(GL_TEXTURE_2D, getSceneTextureId());
|
||||
_program->Bind();
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
drawUnitQuad();
|
||||
auto overlayTextureId = getOverlayTextureId();
|
||||
if (overlayTextureId) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
|
||||
compositeOverlay();
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
if (compositorHelper->getReticleVisible()) {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
|
||||
compositePointer();
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::internalPresent() {
|
||||
using namespace oglplus;
|
||||
uvec2 size = getSurfacePixels();
|
||||
Context::Viewport(size.x, size.y);
|
||||
Context::Clear().DepthBuffer();
|
||||
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
|
||||
drawUnitQuad();
|
||||
const uvec2& srcSize = _compositeFramebuffer->size;
|
||||
uvec2 dstSize = getSurfacePixels();
|
||||
_compositeFramebuffer->Bound(FramebufferTarget::Read, [&] {
|
||||
Context::BlitFramebuffer(
|
||||
0, 0, srcSize.x, srcSize.y,
|
||||
0, 0, dstSize.x, dstSize.y,
|
||||
BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
|
||||
});
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
|
@ -361,6 +486,9 @@ void OpenGLDisplayPlugin::present() {
|
|||
incrementPresentCount();
|
||||
updateTextures();
|
||||
if (_currentSceneTexture) {
|
||||
// Write all layers to a local framebuffer
|
||||
compositeLayers();
|
||||
// Take the composite framebuffer and send it to the output device
|
||||
internalPresent();
|
||||
updateFramerate();
|
||||
}
|
||||
|
@ -379,6 +507,7 @@ float OpenGLDisplayPlugin::presentRate() {
|
|||
|
||||
void OpenGLDisplayPlugin::drawUnitQuad() {
|
||||
_program->Bind();
|
||||
_plane->Use();
|
||||
_plane->Draw();
|
||||
}
|
||||
|
||||
|
@ -436,3 +565,28 @@ void OpenGLDisplayPlugin::enableDeactivate() {
|
|||
_deactivateWait.notify_one();
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t OpenGLDisplayPlugin::getSceneTextureId() const {
|
||||
if (!_currentSceneTexture) {
|
||||
return 0;
|
||||
}
|
||||
return gpu::GLBackend::getTextureID(_currentSceneTexture, false);
|
||||
}
|
||||
|
||||
uint32_t OpenGLDisplayPlugin::getOverlayTextureId() const {
|
||||
if (!_currentOverlayTexture) {
|
||||
return 0;
|
||||
}
|
||||
return gpu::GLBackend::getTextureID(_currentOverlayTexture, false);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::eyeViewport(Eye eye) const {
|
||||
using namespace oglplus;
|
||||
uvec2 vpSize = _compositeFramebuffer->size;
|
||||
vpSize.x /= 2;
|
||||
uvec2 vpPos;
|
||||
if (eye == Eye::Right) {
|
||||
vpPos.x = vpSize.x;
|
||||
}
|
||||
Context::Viewport(vpPos.x, vpPos.y, vpSize.x, vpSize.y);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <condition_variable>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QImage>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
|
@ -25,6 +26,7 @@ protected:
|
|||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
using Condition = std::condition_variable;
|
||||
using TextureEscrow = GLEscrow<gpu::TexturePointer>;
|
||||
public:
|
||||
OpenGLDisplayPlugin();
|
||||
virtual void activate() override;
|
||||
|
@ -32,8 +34,8 @@ public:
|
|||
virtual void stop() override;
|
||||
virtual bool eventFilter(QObject* receiver, QEvent* event) override;
|
||||
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) override;
|
||||
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) override;
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) override;
|
||||
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) override;
|
||||
virtual float presentRate() override;
|
||||
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override {
|
||||
|
@ -50,7 +52,13 @@ protected:
|
|||
#if THREADED_PRESENT
|
||||
friend class PresentThread;
|
||||
#endif
|
||||
uint32_t getSceneTextureId() const;
|
||||
uint32_t getOverlayTextureId() const;
|
||||
|
||||
void compositeLayers();
|
||||
virtual void compositeOverlay();
|
||||
virtual void compositePointer();
|
||||
|
||||
virtual glm::uvec2 getSurfaceSize() const = 0;
|
||||
virtual glm::uvec2 getSurfacePixels() const = 0;
|
||||
|
||||
|
@ -61,32 +69,48 @@ protected:
|
|||
// These functions must only be called on the presentation thread
|
||||
virtual void customizeContext();
|
||||
virtual void uncustomizeContext();
|
||||
virtual void cleanupForSceneTexture(uint32_t sceneTexture);
|
||||
void withMainThreadContext(std::function<void()> f) const;
|
||||
virtual void cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture);
|
||||
// Plugin specific functionality to send the composed scene to the output window or device
|
||||
virtual void internalPresent();
|
||||
|
||||
void withMainThreadContext(std::function<void()> f) const;
|
||||
|
||||
void present();
|
||||
void updateTextures();
|
||||
void updateFramerate();
|
||||
void drawUnitQuad();
|
||||
void swapBuffers();
|
||||
// Plugin specific functionality to composite the scene and overlay and present the result
|
||||
virtual void internalPresent();
|
||||
void eyeViewport(Eye eye) const;
|
||||
|
||||
virtual void updateFrameData();
|
||||
|
||||
ProgramPtr _program;
|
||||
int32_t _mvpUniform { -1 };
|
||||
ShapeWrapperPtr _plane;
|
||||
|
||||
mutable Mutex _mutex;
|
||||
SimpleMovingAverage _usecsPerFrame { 10 };
|
||||
QMap<uint32_t, uint32_t> _sceneTextureToFrameIndexMap;
|
||||
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
|
||||
uint32_t _currentRenderFrameIndex { 0 };
|
||||
|
||||
GLuint _currentSceneTexture { 0 };
|
||||
GLuint _currentOverlayTexture { 0 };
|
||||
gpu::TexturePointer _currentSceneTexture;
|
||||
gpu::TexturePointer _currentOverlayTexture;
|
||||
|
||||
GLTextureEscrow _sceneTextureEscrow;
|
||||
TextureEscrow _sceneTextureEscrow;
|
||||
TextureEscrow _overlayTextureEscrow;
|
||||
|
||||
bool _vsyncSupported { false };
|
||||
|
||||
struct CursorData {
|
||||
QImage image;
|
||||
vec2 hotSpot;
|
||||
uvec2 size;
|
||||
uint32_t texture { 0 };
|
||||
};
|
||||
|
||||
std::map<uint16_t, CursorData> _cursorsData;
|
||||
BasicFramebufferWrapperPtr _compositeFramebuffer;
|
||||
|
||||
private:
|
||||
#if THREADED_PRESENT
|
||||
void enableDeactivate();
|
||||
|
|
|
@ -11,16 +11,25 @@
|
|||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <CursorManager.h>
|
||||
|
||||
#include "../Logging.h"
|
||||
#include "../CompositorHelper.h"
|
||||
|
||||
static const QString MONO_PREVIEW = "Mono Preview";
|
||||
static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate";
|
||||
static const bool DEFAULT_MONO_VIEW = true;
|
||||
|
||||
glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const {
|
||||
return CompositorHelper::VIRTUAL_SCREEN_SIZE;
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::activate() {
|
||||
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);
|
||||
|
||||
|
@ -30,21 +39,59 @@ void HmdDisplayPlugin::activate() {
|
|||
_container->setBoolSetting("monoPreview", _monoPreview);
|
||||
}, true, _monoPreview);
|
||||
_container->removeMenu(FRAMERATE);
|
||||
WindowOpenGLDisplayPlugin::activate();
|
||||
Parent::activate();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::deactivate() {
|
||||
WindowOpenGLDisplayPlugin::deactivate();
|
||||
Parent::deactivate();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::customizeContext() {
|
||||
WindowOpenGLDisplayPlugin::customizeContext();
|
||||
Parent::customizeContext();
|
||||
// Only enable mirroring if we know vsync is disabled
|
||||
enableVsync(false);
|
||||
_enablePreview = !isVsyncEnabled();
|
||||
_sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO);
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::uncustomizeContext() {
|
||||
_sphereSection.reset();
|
||||
_compositeFramebuffer.reset();
|
||||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::compositeOverlay() {
|
||||
using namespace oglplus;
|
||||
_sphereSection->Use();
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto modelView = glm::inverse(_currentRenderEyePoses[eye]); // *glm::translate(mat4(), vec3(0, 0, -1));
|
||||
auto mvp = _eyeProjections[eye] * modelView;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_sphereSection->Draw();
|
||||
});
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::compositePointer() {
|
||||
//Mouse Pointer
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
_plane->Use();
|
||||
// Reconstruct the headpose from the eye poses
|
||||
auto headPosition = (vec3(_currentRenderEyePoses[Left][3]) + vec3(_currentRenderEyePoses[Right][3])) / 2.0f;
|
||||
for_each_eye([&](Eye eye) {
|
||||
using namespace oglplus;
|
||||
eyeViewport(eye);
|
||||
auto reticleTransform = compositorHelper->getReticleTransform(_currentRenderEyePoses[eye], headPosition);
|
||||
auto mvp = _eyeProjections[eye] * reticleTransform;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_plane->Draw();
|
||||
});
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::internalPresent() {
|
||||
// Composite together the scene, overlay and mouse cursor
|
||||
hmdPresent();
|
||||
|
||||
// screen preview mirroring
|
||||
if (_enablePreview) {
|
||||
auto windowSize = toGlm(_window->size());
|
||||
|
@ -69,19 +116,30 @@ void HmdDisplayPlugin::internalPresent() {
|
|||
targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2;
|
||||
}
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glViewport(
|
||||
targetViewportPosition.x, targetViewportPosition.y,
|
||||
targetViewportSize.x * (_monoPreview ? 2 : 1), targetViewportSize.y);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(
|
||||
targetViewportPosition.x, targetViewportPosition.y,
|
||||
targetViewportSize.x, targetViewportSize.y);
|
||||
glBindTexture(GL_TEXTURE_2D, _currentSceneTexture);
|
||||
GLenum err = glGetError();
|
||||
Q_ASSERT(0 == err);
|
||||
drawUnitQuad();
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
using namespace oglplus;
|
||||
Context::Clear().ColorBuffer();
|
||||
auto sourceSize = _compositeFramebuffer->size;
|
||||
if (_monoPreview) {
|
||||
sourceSize.x /= 2;
|
||||
}
|
||||
_compositeFramebuffer->Bound(Framebuffer::Target::Read, [&] {
|
||||
Context::BlitFramebuffer(
|
||||
0, 0, sourceSize.x, sourceSize.y,
|
||||
targetViewportPosition.x, targetViewportPosition.y,
|
||||
targetViewportPosition.x + targetViewportSize.x, targetViewportPosition.y + targetViewportSize.y,
|
||||
BufferSelectBit::ColorBuffer, BlitFilter::Nearest);
|
||||
});
|
||||
swapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
|
||||
Lock lock(_mutex);
|
||||
_renderEyePoses[frameIndex][eye] = pose;
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::updateFrameData() {
|
||||
Parent::updateFrameData();
|
||||
Lock lock(_mutex);
|
||||
_currentRenderEyePoses = _renderEyePoses[_currentRenderFrameIndex];
|
||||
}
|
|
@ -12,34 +12,41 @@
|
|||
#include "../WindowOpenGLDisplayPlugin.h"
|
||||
|
||||
class HmdDisplayPlugin : public WindowOpenGLDisplayPlugin {
|
||||
using Parent = WindowOpenGLDisplayPlugin;
|
||||
public:
|
||||
bool isHmd() const override final { return true; }
|
||||
float getIPD() const override final { return _ipd; }
|
||||
glm::mat4 getEyeToHeadTransform(Eye eye) const override final { return _eyeOffsets[eye]; }
|
||||
glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override final { return _eyeProjections[eye]; }
|
||||
glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override final { return _cullingProjection; }
|
||||
glm::uvec2 getRecommendedUiSize() const override final {
|
||||
// FIXME - would be good to have these values sync with ApplicationCompositor in a better way.
|
||||
const int VIRTUAL_SCREEN_SIZE_X = 3960; // ~10% more pixel density than old version, 72dx240d FOV
|
||||
const int VIRTUAL_SCREEN_SIZE_Y = 1188; // ~10% more pixel density than old version, 72dx240d FOV
|
||||
auto result = uvec2(VIRTUAL_SCREEN_SIZE_X, VIRTUAL_SCREEN_SIZE_Y);
|
||||
return result;
|
||||
}
|
||||
glm::uvec2 getRecommendedUiSize() const override final;
|
||||
glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; }
|
||||
void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) override final;
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
|
||||
protected:
|
||||
virtual void hmdPresent() = 0;
|
||||
void compositeOverlay() override;
|
||||
void compositePointer() override;
|
||||
void internalPresent() override;
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void updateFrameData() override;
|
||||
|
||||
std::array<glm::mat4, 2> _eyeOffsets;
|
||||
std::array<glm::mat4, 2> _eyeProjections;
|
||||
glm::mat4 _cullingProjection;
|
||||
glm::uvec2 _renderTargetSize;
|
||||
float _ipd { 0.064f };
|
||||
using EyePoses = std::array<glm::mat4, 2>;
|
||||
QMap<uint32_t, EyePoses> _renderEyePoses;
|
||||
EyePoses _currentRenderEyePoses;
|
||||
|
||||
private:
|
||||
bool _enablePreview { false };
|
||||
bool _monoPreview { true };
|
||||
ShapeWrapperPtr _sphereSection;
|
||||
};
|
||||
|
||||
|
|
|
@ -46,13 +46,15 @@ void main() {
|
|||
|
||||
const QString InterleavedStereoDisplayPlugin::NAME("3D TV - Interleaved");
|
||||
|
||||
InterleavedStereoDisplayPlugin::InterleavedStereoDisplayPlugin() {
|
||||
}
|
||||
|
||||
void InterleavedStereoDisplayPlugin::customizeContext() {
|
||||
StereoDisplayPlugin::customizeContext();
|
||||
// Set up the stencil buffers? Or use a custom shader?
|
||||
compileProgram(_program, INTERLEAVED_TEXTURED_VS, INTERLEAVED_TEXTURED_FS);
|
||||
compileProgram(_interleavedProgram, INTERLEAVED_TEXTURED_VS, INTERLEAVED_TEXTURED_FS);
|
||||
}
|
||||
|
||||
void InterleavedStereoDisplayPlugin::uncustomizeContext() {
|
||||
_interleavedProgram.reset();
|
||||
StereoDisplayPlugin::uncustomizeContext();
|
||||
}
|
||||
|
||||
glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const {
|
||||
|
@ -64,8 +66,14 @@ glm::uvec2 InterleavedStereoDisplayPlugin::getRecommendedRenderSize() const {
|
|||
|
||||
void InterleavedStereoDisplayPlugin::internalPresent() {
|
||||
using namespace oglplus;
|
||||
_program->Bind();
|
||||
auto sceneSize = getRecommendedRenderSize();
|
||||
Uniform<ivec2>(*_program, "textureSize").SetValue(sceneSize);
|
||||
WindowOpenGLDisplayPlugin::internalPresent();
|
||||
_interleavedProgram->Bind();
|
||||
Uniform<ivec2>(*_interleavedProgram, "textureSize").SetValue(sceneSize);
|
||||
auto surfaceSize = getSurfacePixels();
|
||||
Context::Viewport(0, 0, surfaceSize.x, surfaceSize.y);
|
||||
glBindTexture(GL_TEXTURE_2D, GetName(_compositeFramebuffer->color));
|
||||
_plane->Use();
|
||||
_plane->Draw();
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,17 @@
|
|||
class InterleavedStereoDisplayPlugin : public StereoDisplayPlugin {
|
||||
Q_OBJECT
|
||||
public:
|
||||
InterleavedStereoDisplayPlugin();
|
||||
|
||||
virtual const QString& getName() const override { return NAME; }
|
||||
virtual grouping getGrouping() const override { return ADVANCED; }
|
||||
const QString& getName() const override { return NAME; }
|
||||
grouping getGrouping() const override { return ADVANCED; }
|
||||
glm::uvec2 getRecommendedRenderSize() const override;
|
||||
|
||||
protected:
|
||||
// initialize OpenGL context settings needed by the plugin
|
||||
virtual void customizeContext() override;
|
||||
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void internalPresent() override;
|
||||
|
||||
private:
|
||||
ProgramPtr _interleavedProgram;
|
||||
static const QString NAME;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
#include "SideBySideStereoDisplayPlugin.h"
|
||||
#include <GLMHelpers.h>
|
||||
#include <CursorManager.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <gl/GLWidget.h>
|
||||
#include "../CompositorHelper.h"
|
||||
|
||||
const QString SideBySideStereoDisplayPlugin::NAME("3D TV - Side by Side Stereo");
|
||||
|
||||
|
@ -19,5 +23,3 @@ glm::uvec2 SideBySideStereoDisplayPlugin::getRecommendedRenderSize() const {
|
|||
result.x *= 2;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ public:
|
|||
virtual const QString& getName() const override { return NAME; }
|
||||
virtual grouping getGrouping() const override { return ADVANCED; }
|
||||
virtual glm::uvec2 getRecommendedRenderSize() const override;
|
||||
|
||||
private:
|
||||
static const QString NAME;
|
||||
};
|
||||
|
|
|
@ -8,19 +8,18 @@
|
|||
|
||||
#include "StereoDisplayPlugin.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QAction>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtWidgets/QAction>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QDesktopWidget>
|
||||
|
||||
#include <gpu/GLBackend.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <MatrixStack.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
|
||||
StereoDisplayPlugin::StereoDisplayPlugin() {
|
||||
}
|
||||
#include <gl/GLWidget.h>
|
||||
#include <CursorManager.h>
|
||||
#include "../CompositorHelper.h"
|
||||
|
||||
bool StereoDisplayPlugin::isSupported() const {
|
||||
// FIXME this should attempt to do a scan for supported 3D output
|
||||
|
@ -76,14 +75,16 @@ void StereoDisplayPlugin::activate() {
|
|||
|
||||
_container->removeMenu(FRAMERATE);
|
||||
|
||||
_container->setFullscreen(qApp->primaryScreen());
|
||||
_screen = qApp->primaryScreen();
|
||||
_container->setFullscreen(_screen);
|
||||
WindowOpenGLDisplayPlugin::activate();
|
||||
}
|
||||
|
||||
void StereoDisplayPlugin::updateScreen() {
|
||||
for (uint32_t i = 0; i < _screenActions.size(); ++i) {
|
||||
if (_screenActions[i]->isChecked()) {
|
||||
_container->setFullscreen(qApp->screens().at(i));
|
||||
_screen = qApp->screens().at(i);
|
||||
_container->setFullscreen(_screen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -100,3 +101,4 @@ void StereoDisplayPlugin::deactivate() {
|
|||
float StereoDisplayPlugin::getRecommendedAspectRatio() const {
|
||||
return aspect(WindowOpenGLDisplayPlugin::getRecommendedRenderSize());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "../WindowOpenGLDisplayPlugin.h"
|
||||
class QScreen;
|
||||
|
||||
class StereoDisplayPlugin : public WindowOpenGLDisplayPlugin {
|
||||
Q_OBJECT
|
||||
using Parent = WindowOpenGLDisplayPlugin;
|
||||
public:
|
||||
StereoDisplayPlugin();
|
||||
virtual bool isStereo() const override final { return true; }
|
||||
virtual bool isSupported() const override final;
|
||||
|
||||
|
@ -33,4 +34,5 @@ public:
|
|||
protected:
|
||||
void updateScreen();
|
||||
float _ipd{ 0.064f };
|
||||
QScreen* _screen;
|
||||
};
|
||||
|
|
|
@ -142,6 +142,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
|
||||
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData) const {
|
||||
|
||||
// ALL this fits...
|
||||
// object ID [16 bytes]
|
||||
// ByteCountCoded(type code) [~1 byte]
|
||||
|
|
|
@ -64,6 +64,7 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons
|
|||
}
|
||||
|
||||
void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) {
|
||||
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
// Check to see if this element yet has encode data... if it doesn't create it
|
||||
|
@ -347,6 +348,10 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
#endif
|
||||
indexesOfEntitiesToInclude << i;
|
||||
numberOfEntities++;
|
||||
} else {
|
||||
// if the extra data included this entity, and we've decided to not include the entity, then
|
||||
// we can treat it as if it was completed.
|
||||
entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,6 +403,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// Only our parent can remove our extra data in these cases and only after it knows that all of its
|
||||
// children have been encoded.
|
||||
//
|
||||
// FIXME -- this comment seems wrong....
|
||||
//
|
||||
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
|
||||
// since that will signal that the entire element needs to be encoded on the next attempt
|
||||
if (appendElementState == OctreeElement::NONE) {
|
||||
|
@ -441,9 +449,12 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
appendElementState = OctreeElement::NONE;
|
||||
} else {
|
||||
if (noEntitiesFit) {
|
||||
appendElementState = OctreeElement::PARTIAL;
|
||||
//appendElementState = OctreeElement::PARTIAL;
|
||||
packetData->discardLevel(elementLevel);
|
||||
appendElementState = OctreeElement::NONE;
|
||||
} else {
|
||||
packetData->endLevel(elementLevel);
|
||||
}
|
||||
packetData->endLevel(elementLevel);
|
||||
}
|
||||
return appendElementState;
|
||||
}
|
||||
|
|
|
@ -428,17 +428,17 @@ FBXLight extractLight(const FBXNode& object) {
|
|||
return light;
|
||||
}
|
||||
|
||||
QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) {
|
||||
QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
||||
QString path = QFileInfo(url).path();
|
||||
QByteArray filename = filenameString;
|
||||
QFileInfo checkFile(path + "/" + filename.replace('\\', '/'));
|
||||
//check if the file exists at the RelativeFileName
|
||||
if (checkFile.exists() && checkFile.isFile()) {
|
||||
filename = filename.replace('\\', '/');
|
||||
} else {
|
||||
// there is no texture at the fbx dir with the filename added. Assume it is in the fbx dir.
|
||||
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
|
||||
QByteArray filename = filepath;
|
||||
QFileInfo checkFile(path + "/" + filepath);
|
||||
|
||||
// check if the file exists at the RelativeFilename
|
||||
if (!(checkFile.exists() && checkFile.isFile())) {
|
||||
// if not, assume it is in the fbx directory
|
||||
filename = filename.mid(filename.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
@ -765,7 +765,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "RelativeFilename") {
|
||||
QByteArray filename = subobject.properties.at(0).toByteArray();
|
||||
filename = fileOnUrl(filename, url);
|
||||
QByteArray filepath = filename.replace('\\', '/');
|
||||
filename = fileOnUrl(filepath, url);
|
||||
_textureFilepaths.insert(getID(object.properties), filepath);
|
||||
_textureFilenames.insert(getID(object.properties), filename);
|
||||
} else if (subobject.name == "TextureName") {
|
||||
// trim the name from the timestamp
|
||||
|
@ -849,19 +851,19 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
_textureParams.insert(getID(object.properties), tex);
|
||||
}
|
||||
} else if (object.name == "Video") {
|
||||
QByteArray filename;
|
||||
QByteArray filepath;
|
||||
QByteArray content;
|
||||
foreach (const FBXNode& subobject, object.children) {
|
||||
if (subobject.name == "RelativeFilename") {
|
||||
filename = subobject.properties.at(0).toByteArray();
|
||||
filename = fileOnUrl(filename, url);
|
||||
filepath= subobject.properties.at(0).toByteArray();
|
||||
filepath = filepath.replace('\\', '/');
|
||||
|
||||
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
|
||||
content = subobject.properties.at(0).toByteArray();
|
||||
}
|
||||
}
|
||||
if (!content.isEmpty()) {
|
||||
_textureContent.insert(filename, content);
|
||||
_textureContent.insert(filepath, content);
|
||||
}
|
||||
} else if (object.name == "Material") {
|
||||
FBXMaterial material;
|
||||
|
|
|
@ -414,7 +414,11 @@ public:
|
|||
FBXTexture getTexture(const QString& textureID);
|
||||
|
||||
QHash<QString, QString> _textureNames;
|
||||
// Hashes the original RelativeFilename of textures
|
||||
QHash<QString, QByteArray> _textureFilepaths;
|
||||
// Hashes the place to look for textures, in case they are not inlined
|
||||
QHash<QString, QByteArray> _textureFilenames;
|
||||
// Hashes texture content by filepath, in case they are inlined
|
||||
QHash<QByteArray, QByteArray> _textureContent;
|
||||
QHash<QString, TextureParam> _textureParams;
|
||||
|
||||
|
|
|
@ -28,9 +28,16 @@ bool FBXMaterial::needTangentSpace() const {
|
|||
|
||||
FBXTexture FBXReader::getTexture(const QString& textureID) {
|
||||
FBXTexture texture;
|
||||
texture.filename = _textureFilenames.value(textureID);
|
||||
const QByteArray& filepath = _textureFilepaths.value(textureID);
|
||||
texture.content = _textureContent.value(filepath);
|
||||
|
||||
if (texture.content.isEmpty()) { // the content is not inlined
|
||||
texture.filename = _textureFilenames.value(textureID);
|
||||
} else { // use supplied filepath for inlined content
|
||||
texture.filename = filepath;
|
||||
}
|
||||
|
||||
texture.name = _textureNames.value(textureID);
|
||||
texture.content = _textureContent.value(texture.filename);
|
||||
texture.transform.setIdentity();
|
||||
texture.texcoordSet = 0;
|
||||
if (_textureParams.contains(textureID)) {
|
||||
|
|
|
@ -42,13 +42,19 @@
|
|||
// in use by the GPU. Fence sync objects are used to moderate the actual release of
|
||||
// resources in either direction.
|
||||
template <
|
||||
typename T,
|
||||
// Only accept numeric types
|
||||
typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type
|
||||
typename T
|
||||
//,
|
||||
//// Only accept numeric types
|
||||
//typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type
|
||||
>
|
||||
class GLEscrow {
|
||||
public:
|
||||
static const uint64_t MAX_UNSIGNALED_TIME = USECS_PER_SECOND / 2;
|
||||
|
||||
const T& invalid() const {
|
||||
static const T INVALID_RESULT;
|
||||
return INVALID_RESULT;
|
||||
}
|
||||
|
||||
struct Item {
|
||||
const T _value;
|
||||
|
@ -133,7 +139,7 @@ public:
|
|||
// or if none is available (which could mean either the submission
|
||||
// list is empty or that the first item on the list isn't yet signaled
|
||||
T fetch() {
|
||||
T result{0};
|
||||
T result = invalid();
|
||||
// On the one hand using try_lock() reduces the chance of blocking the consumer thread,
|
||||
// but if the produce thread is going fast enough, it could effectively
|
||||
// starve the consumer out of ever actually getting resources.
|
||||
|
@ -151,7 +157,7 @@ public:
|
|||
// or if none is available (which could mean either the submission
|
||||
// list is empty or that the first item on the list isn't yet signaled
|
||||
// Also releases any previous texture held by the caller
|
||||
T fetchAndRelease(T oldValue) {
|
||||
T fetchAndRelease(const T& oldValue) {
|
||||
T result = fetch();
|
||||
if (!result) {
|
||||
return oldValue;
|
||||
|
@ -164,7 +170,7 @@ public:
|
|||
|
||||
// If fetch returns a non-zero value, it's the responsibility of the
|
||||
// client to release it at some point
|
||||
void release(T t, GLsync readSync = 0) {
|
||||
void release(const T& t, GLsync readSync = 0) {
|
||||
if (!readSync) {
|
||||
// FIXME should the release and submit actually force the creation of a fence?
|
||||
readSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
@ -240,7 +246,7 @@ private:
|
|||
// inside the locked sections, so it cannot have any latency
|
||||
if (item.signaled()) {
|
||||
// if the sync is signaled, queue it for deletion
|
||||
_trash.push_front(Item(0, item._sync));
|
||||
_trash.push_front(Item(invalid(), item._sync));
|
||||
// And change the stored value to 0 so we don't check it again
|
||||
item._sync = 0;
|
||||
return true;
|
||||
|
@ -259,6 +265,12 @@ private:
|
|||
List _trash;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline const GLuint& GLEscrow<GLuint>::invalid() const {
|
||||
static const GLuint INVALID_RESULT { 0 };
|
||||
return INVALID_RESULT;
|
||||
}
|
||||
|
||||
using GLTextureEscrow = GLEscrow<GLuint>;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -485,7 +485,9 @@ void OffscreenQmlSurface::updateQuick() {
|
|||
if (_render) {
|
||||
QMutexLocker lock(&(_renderer->_mutex));
|
||||
_renderer->post(RENDER);
|
||||
_renderer->_cond.wait(&(_renderer->_mutex));
|
||||
while (!_renderer->_cond.wait(&(_renderer->_mutex), 100)) {
|
||||
qApp->processEvents();
|
||||
}
|
||||
_render = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,14 +15,16 @@ using namespace oglplus::shapes;
|
|||
static const char * SIMPLE_TEXTURED_VS = R"VS(#version 410 core
|
||||
#pragma line __LINE__
|
||||
|
||||
uniform mat4 mvp = mat4(1);
|
||||
|
||||
in vec3 Position;
|
||||
in vec2 TexCoord;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(Position, 1);
|
||||
vTexCoord = TexCoord;
|
||||
gl_Position = mvp * vec4(Position, 1);
|
||||
vTexCoord = TexCoord ;
|
||||
}
|
||||
|
||||
)VS";
|
||||
|
|
|
@ -83,7 +83,7 @@ public:
|
|||
~GLTexture();
|
||||
};
|
||||
static GLTexture* syncGPUObject(const Texture& texture);
|
||||
static GLuint getTextureID(const TexturePointer& texture);
|
||||
static GLuint getTextureID(const TexturePointer& texture, bool sync = true);
|
||||
|
||||
// very specific for now
|
||||
static void syncSampler(const Sampler& sampler, Texture::Type type, GLTexture* object);
|
||||
|
|
|
@ -578,11 +578,16 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) {
|
|||
|
||||
|
||||
|
||||
GLuint GLBackend::getTextureID(const TexturePointer& texture) {
|
||||
GLuint GLBackend::getTextureID(const TexturePointer& texture, bool sync) {
|
||||
if (!texture) {
|
||||
return 0;
|
||||
}
|
||||
GLTexture* object = GLBackend::syncGPUObject(*texture);
|
||||
GLTexture* object { nullptr };
|
||||
if (sync) {
|
||||
object = GLBackend::syncGPUObject(*texture);
|
||||
} else {
|
||||
object = Backend::getGPUObject<GLBackend::GLTexture>(*texture);
|
||||
}
|
||||
if (object) {
|
||||
return object->_texture;
|
||||
} else {
|
||||
|
|
0
libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp
Executable file → Normal file
0
libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp
Executable file → Normal file
|
@ -124,7 +124,7 @@ QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr) {
|
|||
return dataStream;
|
||||
}
|
||||
|
||||
QHostAddress getLocalAddress() {
|
||||
QHostAddress getGuessedLocalAddress() {
|
||||
|
||||
QHostAddress localAddress;
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace std {
|
|||
}
|
||||
|
||||
|
||||
QHostAddress getLocalAddress();
|
||||
QHostAddress getGuessedLocalAddress();
|
||||
|
||||
Q_DECLARE_METATYPE(HifiSockAddr);
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
|||
// check for local socket updates every so often
|
||||
const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000;
|
||||
QTimer* localSocketUpdate = new QTimer(this);
|
||||
connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSockAddr);
|
||||
connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSocket);
|
||||
localSocketUpdate->start(LOCAL_SOCKET_UPDATE_INTERVAL_MSECS);
|
||||
|
||||
QTimer* silentNodeTimer = new QTimer(this);
|
||||
|
@ -85,7 +85,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
|
|||
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS);
|
||||
|
||||
// check the local socket right now
|
||||
updateLocalSockAddr();
|
||||
updateLocalSocket();
|
||||
|
||||
// set &PacketReceiver::handleVerifiedPacket as the verified packet callback for the udt::Socket
|
||||
_nodeSocket.setPacketHandler(
|
||||
|
@ -886,17 +886,65 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) {
|
|||
stunOccasionalTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
void LimitedNodeList::updateLocalSockAddr() {
|
||||
HifiSockAddr newSockAddr(getLocalAddress(), _nodeSocket.localPort());
|
||||
if (newSockAddr != _localSockAddr) {
|
||||
void LimitedNodeList::updateLocalSocket() {
|
||||
// when update is called, if the local socket is empty then start with the guessed local socket
|
||||
if (_localSockAddr.isNull()) {
|
||||
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
|
||||
}
|
||||
|
||||
if (_localSockAddr.isNull()) {
|
||||
qCDebug(networking) << "Local socket is" << newSockAddr;
|
||||
} else {
|
||||
qCDebug(networking) << "Local socket has changed from" << _localSockAddr << "to" << newSockAddr;
|
||||
// attempt to use Google's DNS to confirm that local IP
|
||||
static const QHostAddress RELIABLE_LOCAL_IP_CHECK_HOST = QHostAddress { "8.8.8.8" };
|
||||
static const int RELIABLE_LOCAL_IP_CHECK_PORT = 53;
|
||||
|
||||
QTcpSocket* localIPTestSocket = new QTcpSocket;
|
||||
|
||||
connect(localIPTestSocket, &QTcpSocket::connected, this, &LimitedNodeList::connectedForLocalSocketTest);
|
||||
connect(localIPTestSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorTestingLocalSocket()));
|
||||
|
||||
// attempt to connect to our reliable host
|
||||
localIPTestSocket->connectToHost(RELIABLE_LOCAL_IP_CHECK_HOST, RELIABLE_LOCAL_IP_CHECK_PORT);
|
||||
}
|
||||
|
||||
void LimitedNodeList::connectedForLocalSocketTest() {
|
||||
auto localIPTestSocket = qobject_cast<QTcpSocket*>(sender());
|
||||
|
||||
if (localIPTestSocket) {
|
||||
auto localHostAddress = localIPTestSocket->localAddress();
|
||||
|
||||
if (localHostAddress.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
_hasTCPCheckedLocalSocket = true;
|
||||
setLocalSocket(HifiSockAddr { localHostAddress, _nodeSocket.localPort() });
|
||||
}
|
||||
|
||||
_localSockAddr = newSockAddr;
|
||||
localIPTestSocket->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void LimitedNodeList::errorTestingLocalSocket() {
|
||||
auto localIPTestSocket = qobject_cast<QTcpSocket*>(sender());
|
||||
|
||||
if (localIPTestSocket) {
|
||||
|
||||
// error connecting to the test socket - if we've never set our local socket using this test socket
|
||||
// then use our possibly updated guessed local address as fallback
|
||||
if (!_hasTCPCheckedLocalSocket) {
|
||||
setLocalSocket(HifiSockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
|
||||
}
|
||||
|
||||
localIPTestSocket->deleteLater();;
|
||||
}
|
||||
}
|
||||
|
||||
void LimitedNodeList::setLocalSocket(const HifiSockAddr& sockAddr) {
|
||||
if (sockAddr != _localSockAddr) {
|
||||
|
||||
if (_localSockAddr.isNull()) {
|
||||
qCInfo(networking) << "Local socket is" << sockAddr;
|
||||
} else {
|
||||
qCInfo(networking) << "Local socket has changed from" << _localSockAddr << "to" << sockAddr;
|
||||
}
|
||||
|
||||
_localSockAddr = sockAddr;
|
||||
|
||||
emit localSockAddrChanged(_localSockAddr);
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ public slots:
|
|||
|
||||
void removeSilentNodes();
|
||||
|
||||
void updateLocalSockAddr();
|
||||
void updateLocalSocket();
|
||||
|
||||
void startSTUNPublicSocketUpdate();
|
||||
virtual void sendSTUNRequest();
|
||||
|
@ -247,6 +247,10 @@ signals:
|
|||
void isAllowedEditorChanged(bool isAllowedEditor);
|
||||
void canRezChanged(bool canRez);
|
||||
|
||||
protected slots:
|
||||
void connectedForLocalSocketTest();
|
||||
void errorTestingLocalSocket();
|
||||
|
||||
protected:
|
||||
LimitedNodeList(unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
|
||||
LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton
|
||||
|
@ -258,6 +262,8 @@ protected:
|
|||
const QUuid& connectionSecret = QUuid());
|
||||
void collectPacketStats(const NLPacket& packet);
|
||||
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
|
||||
|
||||
void setLocalSocket(const HifiSockAddr& sockAddr);
|
||||
|
||||
bool isPacketVerified(const udt::Packet& packet);
|
||||
bool packetVersionMatch(const udt::Packet& packet);
|
||||
|
@ -271,8 +277,6 @@ protected:
|
|||
void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID,
|
||||
const QUuid& peerRequestID = QUuid());
|
||||
|
||||
|
||||
|
||||
QUuid _sessionUUID;
|
||||
NodeHash _nodeHash;
|
||||
QReadWriteLock _nodeMutex;
|
||||
|
@ -281,6 +285,7 @@ protected:
|
|||
HifiSockAddr _localSockAddr;
|
||||
HifiSockAddr _publicSockAddr;
|
||||
HifiSockAddr _stunSockAddr;
|
||||
bool _hasTCPCheckedLocalSocket { false };
|
||||
|
||||
PacketReceiver* _packetReceiver;
|
||||
|
||||
|
|
|
@ -942,7 +942,6 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
|
||||
currentEncodeLevel, parentLocationThisView);
|
||||
|
||||
|
||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
||||
|
@ -1529,7 +1528,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// If we made it this far, then we've written all of our child data... if this element is the root
|
||||
// element, then we also allow the root element to write out it's data...
|
||||
if (continueThisLevel && element == _rootElement && rootElementHasData()) {
|
||||
|
||||
int bytesBeforeChild = packetData->getUncompressedSize();
|
||||
|
||||
// release the bytes we reserved...
|
||||
|
@ -1537,6 +1535,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
|
||||
LevelDetails rootDataLevelKey = packetData->startLevel();
|
||||
OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params);
|
||||
|
||||
bool partOfRootFit = (rootAppendState != OctreeElement::NONE);
|
||||
bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED);
|
||||
|
||||
|
@ -1571,7 +1570,6 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data";
|
||||
qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE....";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if we were unable to fit this level in our packet, then rewind and add it to the element bag for
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
/// a single last item will be returned by extract as a null pointer
|
||||
|
||||
void deleteAll();
|
||||
size_t size() const { return _bagElements.size(); }
|
||||
|
||||
private:
|
||||
Bag _bagElements;
|
||||
|
|
|
@ -236,6 +236,8 @@ public:
|
|||
/// the number of bytes in the packet currently reserved
|
||||
int getReservedBytes() { return _bytesReserved; }
|
||||
|
||||
int getBytesAvailable() { return _bytesAvailable; }
|
||||
|
||||
/// displays contents for debugging
|
||||
void debugContent();
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ public:
|
|||
const std::vector<unsigned char*>& getJurisdictionEndNodes() const { return _jurisdictionEndNodes; }
|
||||
|
||||
bool isMoving() const { return _isMoving; }
|
||||
bool isFullScene() const { return _isFullScene; }
|
||||
quint64 getTotalElements() const { return _totalElements; }
|
||||
quint64 getTotalInternal() const { return _totalInternal; }
|
||||
quint64 getTotalLeaves() const { return _totalLeaves; }
|
||||
|
|
|
@ -50,6 +50,11 @@ class QWindow;
|
|||
|
||||
#define AVERAGE_HUMAN_IPD 0.064f
|
||||
|
||||
namespace gpu {
|
||||
class Texture;
|
||||
using TexturePointer = std::shared_ptr<Texture>;
|
||||
}
|
||||
|
||||
class DisplayPlugin : public Plugin {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -70,12 +75,12 @@ public:
|
|||
/**
|
||||
* Sends the scene texture to the display plugin.
|
||||
*/
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, uint32_t sceneTexture, const glm::uvec2& sceneSize) = 0;
|
||||
virtual void submitSceneTexture(uint32_t frameIndex, const gpu::TexturePointer& sceneTexture) = 0;
|
||||
|
||||
/**
|
||||
* Sends the scene texture to the display plugin.
|
||||
*/
|
||||
virtual void submitOverlayTexture(uint32_t overlayTexture, const glm::uvec2& overlaySize) = 0;
|
||||
virtual void submitOverlayTexture(const gpu::TexturePointer& overlayTexture) = 0;
|
||||
|
||||
// Does the rendering surface have current focus?
|
||||
virtual bool hasFocus() const = 0;
|
||||
|
|
|
@ -23,6 +23,11 @@ class QWindow;
|
|||
|
||||
class DisplayPlugin;
|
||||
|
||||
namespace gpu {
|
||||
class Texture;
|
||||
using TexturePointer = std::shared_ptr<Texture>;
|
||||
}
|
||||
|
||||
class PluginContainer {
|
||||
public:
|
||||
static PluginContainer& getInstance();
|
||||
|
@ -39,8 +44,8 @@ public:
|
|||
virtual void showDisplayPluginsTools() = 0;
|
||||
virtual void requestReset() = 0;
|
||||
virtual bool makeRenderingContextCurrent() = 0;
|
||||
virtual void releaseSceneTexture(uint32_t texture) = 0;
|
||||
virtual void releaseOverlayTexture(uint32_t texture) = 0;
|
||||
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) = 0;
|
||||
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) = 0;
|
||||
virtual GLWidget* getPrimaryWidget() = 0;
|
||||
virtual QWindow* getPrimaryWindow() = 0;
|
||||
virtual QOpenGLContext* getPrimaryContext() = 0;
|
||||
|
|
|
@ -10,27 +10,30 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderDeferredTask.h"
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <PathUtils.h>
|
||||
#include <RenderArgs.h>
|
||||
#include <ViewFrustum.h>
|
||||
#include <gpu/Context.h>
|
||||
|
||||
#include <render/CullTask.h>
|
||||
#include <render/SortTask.h>
|
||||
#include <render/DrawTask.h>
|
||||
#include <render/DrawStatus.h>
|
||||
#include <render/DrawSceneOctree.h>
|
||||
|
||||
#include "DebugDeferredBuffer.h"
|
||||
#include "DeferredLightingEffect.h"
|
||||
#include "FramebufferCache.h"
|
||||
#include "HitEffect.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
#include "render/DrawTask.h"
|
||||
#include "render/DrawStatus.h"
|
||||
#include "render/DrawSceneOctree.h"
|
||||
#include "AmbientOcclusionEffect.h"
|
||||
#include "AntialiasingEffect.h"
|
||||
#include "ToneMappingEffect.h"
|
||||
|
||||
#include "RenderDeferredTask.h"
|
||||
|
||||
using namespace render;
|
||||
|
||||
extern void initStencilPipeline(gpu::PipelinePointer& pipeline);
|
||||
|
@ -54,34 +57,42 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto sceneFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto sceneSelection = addJob<FetchSpatialTree>("FetchSceneSelection", sceneFilter);
|
||||
const auto culledSceneSelection = addJob<CullSpatialSelection>("CullSceneSelection", sceneSelection, cullFunctor, RenderDetails::ITEM, sceneFilter);
|
||||
auto spatialFilter = ItemFilter::Builder::visibleWorldItems().withoutLayered();
|
||||
const auto spatialSelection = addJob<FetchSpatialTree>("FetchSceneSelection", spatialFilter);
|
||||
const auto culledSpatialSelection = addJob<CullSpatialSelection>("CullSceneSelection", spatialSelection, cullFunctor, RenderDetails::ITEM, spatialFilter);
|
||||
|
||||
// Overlays are not culled
|
||||
const auto nonspatialSelection = addJob<FetchNonspatialItems>("FetchOverlaySelection");
|
||||
|
||||
// Multi filter visible items into different buckets
|
||||
const int NUM_FILTERS = 3;
|
||||
const int OPAQUE_SHAPE_BUCKET = 0;
|
||||
const int TRANSPARENT_SHAPE_BUCKET = 1;
|
||||
const int LIGHT_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray triageFilters = { {
|
||||
const int BACKGROUND_BUCKET = 2;
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray spatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::light()
|
||||
} };
|
||||
const auto filteredItemsBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSceneSelection, triageFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
MultiFilterItem<NUM_FILTERS>::ItemFilterArray nonspatialFilters = { {
|
||||
ItemFilter::Builder::opaqueShape(),
|
||||
ItemFilter::Builder::transparentShape(),
|
||||
ItemFilter::Builder::background()
|
||||
} };
|
||||
const auto filteredSpatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterSceneSelection", culledSpatialSelection, spatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
const auto filteredNonspatialBuckets = addJob<MultiFilterItem<NUM_FILTERS>>("FilterOverlaySelection", nonspatialSelection, nonspatialFilters).get<MultiFilterItem<NUM_FILTERS>::ItemBoundsArray>();
|
||||
|
||||
// Extract / Sort opaques / Transparents / Lights / Overlays
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredItemsBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredItemsBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredItemsBuckets[LIGHT_BUCKET];
|
||||
const auto opaques = addJob<DepthSortItems>("DepthSortOpaque", filteredSpatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto transparents = addJob<DepthSortItems>("DepthSortTransparent", filteredSpatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto lights = filteredSpatialBuckets[LIGHT_BUCKET];
|
||||
|
||||
// Overlays are not culled because we want to make sure they are seen
|
||||
// Could be considered a bug in the current cullfunctor
|
||||
const auto overlayOpaques = addJob<FetchItems>("FetchOverlayOpaque", ItemFilter::Builder::opaqueShapeLayered());
|
||||
const auto fetchedOverlayOpaques = addJob<FetchItems>("FetchOverlayTransparents", ItemFilter::Builder::transparentShapeLayered());
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortTransparentOverlay", fetchedOverlayOpaques, DepthSortItems(false));
|
||||
const auto overlayOpaques = addJob<DepthSortItems>("DepthSortOverlayOpaque", filteredNonspatialBuckets[OPAQUE_SHAPE_BUCKET]);
|
||||
const auto overlayTransparents = addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false));
|
||||
const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET];
|
||||
|
||||
// GPU Jobs: Start preparing the deferred and lighting buffer
|
||||
// GPU jobs: Start preparing the deferred and lighting buffer
|
||||
addJob<PrepareDeferred>("PrepareDeferred");
|
||||
|
||||
// Render opaque objects in DeferredBuffer
|
||||
|
@ -91,7 +102,7 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
addJob<DrawStencilDeferred>("DrawOpaqueStencil");
|
||||
|
||||
// Use Stencil and start drawing background in Lighting buffer
|
||||
addJob<DrawBackgroundDeferred>("DrawBackgroundDeferred");
|
||||
addJob<DrawBackgroundDeferred>("DrawBackgroundDeferred", background);
|
||||
|
||||
// AO job
|
||||
addJob<AmbientOcclusionEffect>("AmbientOcclusion");
|
||||
|
@ -123,8 +134,8 @@ RenderDeferredTask::RenderDeferredTask(CullFunctor cullFunctor) {
|
|||
|
||||
// Scene Octree Debuging job
|
||||
{
|
||||
addJob<DrawSceneOctree>("DrawSceneOctree", sceneSelection);
|
||||
addJob<DrawItemSelection>("DrawItemSelection", sceneSelection);
|
||||
addJob<DrawSceneOctree>("DrawSceneOctree", spatialSelection);
|
||||
addJob<DrawItemSelection>("DrawItemSelection", spatialSelection);
|
||||
}
|
||||
|
||||
// Status icon rendering job
|
||||
|
@ -170,9 +181,9 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
|
|||
RenderArgs* args = renderContext->args;
|
||||
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
args->_batch = &batch;
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
batch.setStateScissorRect(args->_viewport);
|
||||
args->_batch = &batch;
|
||||
|
||||
config->setNumDrawn((int)inItems.size());
|
||||
emit config->numDrawnChanged();
|
||||
|
@ -222,7 +233,8 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon
|
|||
// Render the items
|
||||
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
args->_batch = &batch;
|
||||
args->_whiteTexture = DependencyManager::get<TextureCache>()->getWhiteTexture();
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
batch.setStateScissorRect(args->_viewport);
|
||||
|
||||
glm::mat4 projMat;
|
||||
Transform viewMat;
|
||||
|
@ -231,14 +243,10 @@ void DrawOverlay3D::run(const SceneContextPointer& sceneContext, const RenderCon
|
|||
|
||||
batch.setProjectionTransform(projMat);
|
||||
batch.setViewTransform(viewMat);
|
||||
batch.setViewportTransform(args->_viewport);
|
||||
batch.setStateScissorRect(args->_viewport);
|
||||
batch.setResourceTexture(0, args->_whiteTexture);
|
||||
|
||||
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
|
||||
args->_batch = nullptr;
|
||||
});
|
||||
args->_batch = nullptr;
|
||||
args->_whiteTexture.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,20 +284,10 @@ void DrawStencilDeferred::run(const SceneContextPointer& sceneContext, const Ren
|
|||
args->_batch = nullptr;
|
||||
}
|
||||
|
||||
void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
// render backgrounds
|
||||
auto& scene = sceneContext->_scene;
|
||||
auto& items = scene->getMasterBucket().at(ItemFilter::Builder::background());
|
||||
|
||||
|
||||
ItemBounds inItems;
|
||||
inItems.reserve(items.size());
|
||||
for (auto id : items) {
|
||||
inItems.emplace_back(id);
|
||||
}
|
||||
RenderArgs* args = renderContext->args;
|
||||
doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
args->_batch = &batch;
|
||||
|
|
|
@ -12,9 +12,8 @@
|
|||
#ifndef hifi_RenderDeferredTask_h
|
||||
#define hifi_RenderDeferredTask_h
|
||||
|
||||
#include "gpu/Pipeline.h"
|
||||
|
||||
#include "render/DrawTask.h"
|
||||
#include <gpu/Pipeline.h>
|
||||
#include <render/CullTask.h>
|
||||
|
||||
class SetupDeferred {
|
||||
public:
|
||||
|
@ -85,9 +84,9 @@ protected:
|
|||
|
||||
class DrawBackgroundDeferred {
|
||||
public:
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext);
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
|
||||
|
||||
using JobModel = render::Job::Model<DrawBackgroundDeferred>;
|
||||
using JobModel = render::Job::ModelI<DrawBackgroundDeferred, render::ItemBounds>;
|
||||
};
|
||||
|
||||
class DrawOverlay3DConfig : public render::Job::Config {
|
||||
|
|
|
@ -9,16 +9,20 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderShadowTask.h"
|
||||
|
||||
#include <gpu/Context.h>
|
||||
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "render/Context.h"
|
||||
#include <render/Context.h>
|
||||
#include <render/CullTask.h>
|
||||
#include <render/SortTask.h>
|
||||
#include <render/DrawTask.h>
|
||||
|
||||
#include "DeferredLightingEffect.h"
|
||||
#include "FramebufferCache.h"
|
||||
|
||||
#include "RenderShadowTask.h"
|
||||
|
||||
#include "model_shadow_vert.h"
|
||||
#include "skin_model_shadow_vert.h"
|
||||
|
||||
|
@ -28,7 +32,7 @@
|
|||
using namespace render;
|
||||
|
||||
void RenderShadowMap::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext,
|
||||
const render::ShapesIDsBounds& inShapes) {
|
||||
const render::ShapeBounds& inShapes) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
|
@ -104,20 +108,18 @@ RenderShadowTask::RenderShadowTask(CullFunctor cullFunctor) : Task(std::make_sha
|
|||
skinProgram, state);
|
||||
}
|
||||
|
||||
// CPU: Fetch shadow-casting opaques
|
||||
const auto fetchedItems = addJob<FetchItems>("FetchShadowMap");
|
||||
// CPU jobs:
|
||||
// Fetch and cull the items from the scene
|
||||
auto shadowFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered();
|
||||
const auto shadowSelection = addJob<FetchSpatialTree>("FetchShadowSelection", shadowFilter);
|
||||
const auto culledShadowSelection = addJob<CullSpatialSelection>("CullShadowSelection", shadowSelection, cullFunctor, RenderDetails::SHADOW, shadowFilter);
|
||||
|
||||
// CPU: Cull against KeyLight frustum (nearby viewing camera)
|
||||
const auto culledItems = addJob<CullItems<RenderDetails::SHADOW>>("CullShadowMap", fetchedItems, cullFunctor);
|
||||
// Sort
|
||||
const auto sortedPipelines = addJob<PipelineSortShapes>("PipelineSortShadowSort", culledShadowSelection);
|
||||
const auto sortedShapes = addJob<DepthSortShapes>("DepthSortShadowMap", sortedPipelines);
|
||||
|
||||
// CPU: Sort by pipeline
|
||||
const auto sortedShapes = addJob<PipelineSortShapes>("PipelineSortShadowSort", culledItems);
|
||||
|
||||
// CPU: Sort front to back
|
||||
const auto shadowShapes = addJob<DepthSortShapes>("DepthSortShadowMap", sortedShapes);
|
||||
|
||||
// GPU: Render to shadow map
|
||||
addJob<RenderShadowMap>("RenderShadowMap", shadowShapes, shapePlumber);
|
||||
// GPU jobs: Render to shadow map
|
||||
addJob<RenderShadowMap>("RenderShadowMap", sortedShapes, shapePlumber);
|
||||
}
|
||||
|
||||
void RenderShadowTask::configure(const Config& configuration) {
|
||||
|
|
|
@ -15,17 +15,17 @@
|
|||
#include <gpu/Framebuffer.h>
|
||||
#include <gpu/Pipeline.h>
|
||||
|
||||
#include <render/DrawTask.h>
|
||||
#include <render/CullTask.h>
|
||||
|
||||
class ViewFrustum;
|
||||
|
||||
class RenderShadowMap {
|
||||
public:
|
||||
using JobModel = render::Job::ModelI<RenderShadowMap, render::ShapesIDsBounds>;
|
||||
using JobModel = render::Job::ModelI<RenderShadowMap, render::ShapeBounds>;
|
||||
|
||||
RenderShadowMap(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {}
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext,
|
||||
const render::ShapesIDsBounds& inShapes);
|
||||
const render::ShapeBounds& inShapes);
|
||||
|
||||
protected:
|
||||
render::ShapePlumberPointer _shapePlumber;
|
||||
|
|
|
@ -62,99 +62,21 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
|
|||
details._rendered += (int)outItems.size();
|
||||
}
|
||||
|
||||
struct ItemBoundSort {
|
||||
float _centerDepth = 0.0f;
|
||||
float _nearDepth = 0.0f;
|
||||
float _farDepth = 0.0f;
|
||||
ItemID _id = 0;
|
||||
AABox _bounds;
|
||||
|
||||
ItemBoundSort() {}
|
||||
ItemBoundSort(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
|
||||
};
|
||||
|
||||
struct FrontToBackSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth < right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
struct BackToFrontSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth > right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
|
||||
|
||||
// Make a local dataset of the center distance and closest point distance
|
||||
std::vector<ItemBoundSort> itemBoundSorts;
|
||||
itemBoundSorts.reserve(outItems.size());
|
||||
|
||||
for (auto itemDetails : inItems) {
|
||||
auto item = scene->getItem(itemDetails.id);
|
||||
auto bound = itemDetails.bound; // item.getBound();
|
||||
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
|
||||
|
||||
itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound));
|
||||
}
|
||||
|
||||
// sort against Z
|
||||
if (frontToBack) {
|
||||
FrontToBackSort frontToBackSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), frontToBackSort);
|
||||
} else {
|
||||
BackToFrontSort backToFrontSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort);
|
||||
}
|
||||
|
||||
// FInally once sorted result to a list of itemID
|
||||
for (auto& item : itemBoundSorts) {
|
||||
outItems.emplace_back(ItemBound(item._id, item._bounds));
|
||||
}
|
||||
}
|
||||
|
||||
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
|
||||
}
|
||||
|
||||
|
||||
void FetchItems::configure(const Config& config) {
|
||||
}
|
||||
|
||||
void FetchItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems) {
|
||||
void FetchNonspatialItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
auto& scene = sceneContext->_scene;
|
||||
|
||||
outItems.clear();
|
||||
|
||||
const auto& bucket = scene->getMasterBucket();
|
||||
const auto& items = bucket.find(_filter);
|
||||
if (items != bucket.end()) {
|
||||
outItems.reserve(items->second.size());
|
||||
for (auto& id : items->second) {
|
||||
auto& item = scene->getItem(id);
|
||||
outItems.emplace_back(ItemBound(id, item.getBound()));
|
||||
}
|
||||
const auto& items = scene->getNonspatialSet();
|
||||
outItems.reserve(items.size());
|
||||
for (auto& id : items) {
|
||||
auto& item = scene->getItem(id);
|
||||
outItems.emplace_back(ItemBound(id, item.getBound()));
|
||||
}
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->numItems = (int)outItems.size();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FetchSpatialTree::configure(const Config& config) {
|
||||
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
|
||||
_freezeFrustum = config.freezeFrustum;
|
||||
|
|
|
@ -21,52 +21,13 @@ namespace render {
|
|||
|
||||
void cullItems(const RenderContextPointer& renderContext, const CullFunctor& cullFunctor, RenderDetails::Item& details,
|
||||
const ItemBounds& inItems, ItemBounds& outItems);
|
||||
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
|
||||
class FetchItemsConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
class FetchNonspatialItems {
|
||||
public:
|
||||
int getNumItems() { return numItems; }
|
||||
|
||||
int numItems{ 0 };
|
||||
};
|
||||
|
||||
class FetchItems {
|
||||
public:
|
||||
using Config = FetchItemsConfig;
|
||||
using JobModel = Job::ModelO<FetchItems, ItemBounds, Config>;
|
||||
|
||||
FetchItems() {}
|
||||
FetchItems(const ItemFilter& filter) : _filter(filter) {}
|
||||
|
||||
ItemFilter _filter{ ItemFilter::Builder::opaqueShape().withoutLayered() };
|
||||
|
||||
void configure(const Config& config);
|
||||
using JobModel = Job::ModelO<FetchNonspatialItems, ItemBounds>;
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, ItemBounds& outItems);
|
||||
};
|
||||
|
||||
|
||||
template<RenderDetails::Type T>
|
||||
class CullItems {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<CullItems<T>, ItemBounds, ItemBounds>;
|
||||
|
||||
CullItems(CullFunctor cullFunctor) : _cullFunctor{ cullFunctor } {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
const auto& args = renderContext->args;
|
||||
auto& details = args->_details.edit(T);
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
render::cullItems(renderContext, _cullFunctor, details, inItems, outItems);
|
||||
}
|
||||
|
||||
protected:
|
||||
CullFunctor _cullFunctor;
|
||||
};
|
||||
|
||||
|
||||
class FetchSpatialTreeConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int numItems READ getNumItems)
|
||||
|
@ -209,28 +170,19 @@ namespace render {
|
|||
outItems[i].template edit<ItemBounds>().clear();
|
||||
}
|
||||
|
||||
// For each item, filter it into the buckets
|
||||
// For each item, filter it into one bucket
|
||||
for (auto itemBound : inItems) {
|
||||
auto& item = scene->getItem(itemBound.id);
|
||||
auto itemKey = item.getKey();
|
||||
for (size_t i = 0; i < NUM_FILTERS; i++) {
|
||||
if (_filters[i].test(itemKey)) {
|
||||
outItems[i].template edit<ItemBounds>().emplace_back(itemBound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DepthSortItems {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortItems, ItemBounds, ItemBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // hifi_render_CullTask_h;
|
|
@ -73,38 +73,3 @@ void DrawLight::run(const SceneContextPointer& sceneContext, const RenderContext
|
|||
args->_batch = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
outShapes.clear();
|
||||
|
||||
for (const auto& item : inItems) {
|
||||
auto key = scene->getItem(item.id).getShapeKey();
|
||||
auto outItems = outShapes.find(key);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first;
|
||||
outItems->second.reserve(inItems.size());
|
||||
}
|
||||
|
||||
outItems->second.push_back(item);
|
||||
}
|
||||
|
||||
for (auto& items : outShapes) {
|
||||
items.second.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void DepthSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapesIDsBounds& inShapes, ShapesIDsBounds& outShapes) {
|
||||
outShapes.clear();
|
||||
outShapes.reserve(inShapes.size());
|
||||
|
||||
for (auto& pipeline : inShapes) {
|
||||
auto& inItems = pipeline.second;
|
||||
auto outItems = outShapes.find(pipeline.first);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemBounds{})).first;
|
||||
}
|
||||
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems->second);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,6 @@
|
|||
#define hifi_render_DrawTask_h
|
||||
|
||||
#include "Engine.h"
|
||||
#include "CullTask.h"
|
||||
#include "ShapePipeline.h"
|
||||
#include "gpu/Batch.h"
|
||||
|
||||
|
||||
namespace render {
|
||||
|
||||
|
@ -31,22 +27,6 @@ public:
|
|||
protected:
|
||||
};
|
||||
|
||||
class PipelineSortShapes {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<PipelineSortShapes, ItemBounds, ShapesIDsBounds>;
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapesIDsBounds& outShapes);
|
||||
};
|
||||
|
||||
class DepthSortShapes {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortShapes, ShapesIDsBounds, ShapesIDsBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortShapes(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapesIDsBounds& inShapes, ShapesIDsBounds& outShapes);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // hifi_render_DrawTask_h
|
||||
|
|
|
@ -63,6 +63,13 @@ void Item::PayloadInterface::addStatusGetters(const Status::Getters& getters) {
|
|||
}
|
||||
}
|
||||
|
||||
void Item::update(const UpdateFunctorPointer& updateFunctor) {
|
||||
if (updateFunctor) {
|
||||
_payload->update(updateFunctor);
|
||||
}
|
||||
_key = _payload->getKey();
|
||||
}
|
||||
|
||||
void Item::resetPayload(const PayloadPointer& payload) {
|
||||
if (!payload) {
|
||||
kill();
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
bool isPickable() const { return _flags[PICKABLE]; }
|
||||
|
||||
bool isLayered() const { return _flags[LAYERED]; }
|
||||
bool isSpatial() const { return !isLayered(); }
|
||||
|
||||
// Probably not public, flags used by the scene
|
||||
bool isSmall() const { return _flags[SMALLER]; }
|
||||
|
@ -312,11 +313,11 @@ public:
|
|||
Item() {}
|
||||
~Item() {}
|
||||
|
||||
// Main scene / item managment interface Reset/Update/Kill
|
||||
// Main scene / item managment interface reset/update/kill
|
||||
void resetPayload(const PayloadPointer& payload);
|
||||
void resetCell(ItemCell cell, bool _small) { _cell = cell; _key.setSmaller(_small); }
|
||||
void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload
|
||||
void kill() { _payload.reset(); _key._flags.reset(); _cell = INVALID_CELL; } // Kill means forget the payload and key and cell
|
||||
void resetCell(ItemCell cell = INVALID_CELL, bool _small = false) { _cell = cell; _key.setSmaller(_small); }
|
||||
void update(const UpdateFunctorPointer& updateFunctor); // communicate update to payload
|
||||
void kill() { _payload.reset(); resetCell(); _key._flags.reset(); } // forget the payload, key, cell
|
||||
|
||||
// Check heuristic key
|
||||
const ItemKey& getKey() const { return _key; }
|
||||
|
@ -464,7 +465,7 @@ public:
|
|||
using ItemBounds = std::vector<ItemBound>;
|
||||
|
||||
// A map of items by ShapeKey to optimize rendering pipeline assignments
|
||||
using ShapesIDsBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
|
||||
using ShapeBounds = std::unordered_map<ShapeKey, ItemBounds, ShapeKey::Hash, ShapeKey::KeyEqual>;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -15,59 +15,11 @@
|
|||
|
||||
using namespace render;
|
||||
|
||||
void ItemBucketMap::insert(const ItemID& id, const ItemKey& key) {
|
||||
// Insert the itemID in every bucket where it filters true
|
||||
for (auto& bucket : (*this)) {
|
||||
if (bucket.first.test(key)) {
|
||||
bucket.second.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ItemBucketMap::erase(const ItemID& id, const ItemKey& key) {
|
||||
// Remove the itemID in every bucket where it filters true
|
||||
for (auto& bucket : (*this)) {
|
||||
if (bucket.first.test(key)) {
|
||||
bucket.second.erase(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBucketMap::reset(const ItemID& id, const ItemKey& oldKey, const ItemKey& newKey) {
|
||||
// Reset the itemID in every bucket,
|
||||
// Remove from the buckets where oldKey filters true AND newKey filters false
|
||||
// Insert into the buckets where newKey filters true
|
||||
for (auto& bucket : (*this)) {
|
||||
if (bucket.first.test(oldKey)) {
|
||||
if (!bucket.first.test(newKey)) {
|
||||
bucket.second.erase(id);
|
||||
}
|
||||
} else if (bucket.first.test(newKey)) {
|
||||
bucket.second.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemBucketMap::allocateStandardOpaqueTranparentBuckets() {
|
||||
(*this)[ItemFilter::Builder::opaqueShape().withoutLayered()];
|
||||
(*this)[ItemFilter::Builder::transparentShape().withoutLayered()];
|
||||
(*this)[ItemFilter::Builder::light()];
|
||||
(*this)[ItemFilter::Builder::background()];
|
||||
(*this)[ItemFilter::Builder::opaqueShape().withLayered()];
|
||||
(*this)[ItemFilter::Builder::transparentShape().withLayered()];
|
||||
}
|
||||
|
||||
|
||||
void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) {
|
||||
_resetItems.push_back(id);
|
||||
_resetPayloads.push_back(payload);
|
||||
}
|
||||
|
||||
void PendingChanges::resortItem(ItemID id, ItemKey oldKey, ItemKey newKey) {
|
||||
_resortItems.push_back(id);
|
||||
_resortOldKeys.push_back(oldKey);
|
||||
_resortNewKeys.push_back(newKey);
|
||||
}
|
||||
|
||||
void PendingChanges::removeItem(ItemID id) {
|
||||
_removedItems.push_back(id);
|
||||
}
|
||||
|
@ -80,9 +32,6 @@ void PendingChanges::updateItem(ItemID id, const UpdateFunctorPointer& functor)
|
|||
void PendingChanges::merge(PendingChanges& changes) {
|
||||
_resetItems.insert(_resetItems.end(), changes._resetItems.begin(), changes._resetItems.end());
|
||||
_resetPayloads.insert(_resetPayloads.end(), changes._resetPayloads.begin(), changes._resetPayloads.end());
|
||||
_resortItems.insert(_resortItems.end(), changes._resortItems.begin(), changes._resortItems.end());
|
||||
_resortOldKeys.insert(_resortOldKeys.end(), changes._resortOldKeys.begin(), changes._resortOldKeys.end());
|
||||
_resortNewKeys.insert(_resortNewKeys.end(), changes._resortNewKeys.begin(), changes._resortNewKeys.end());
|
||||
_removedItems.insert(_removedItems.end(), changes._removedItems.begin(), changes._removedItems.end());
|
||||
_updatedItems.insert(_updatedItems.end(), changes._updatedItems.begin(), changes._updatedItems.end());
|
||||
_updateFunctors.insert(_updateFunctors.end(), changes._updateFunctors.begin(), changes._updateFunctors.end());
|
||||
|
@ -92,7 +41,6 @@ Scene::Scene(glm::vec3 origin, float size) :
|
|||
_masterSpatialTree(origin, size)
|
||||
{
|
||||
_items.push_back(Item()); // add the itemID #0 to nothing
|
||||
_masterBucketMap.allocateStandardOpaqueTranparentBuckets();
|
||||
}
|
||||
|
||||
ItemID Scene::allocateID() {
|
||||
|
@ -133,7 +81,6 @@ void Scene::processPendingChangesQueue() {
|
|||
// capture anything coming from the pendingChanges
|
||||
resetItems(consolidatedPendingChanges._resetItems, consolidatedPendingChanges._resetPayloads);
|
||||
updateItems(consolidatedPendingChanges._updatedItems, consolidatedPendingChanges._updateFunctors);
|
||||
resortItems(consolidatedPendingChanges._resortItems, consolidatedPendingChanges._resortOldKeys, consolidatedPendingChanges._resortNewKeys);
|
||||
removeItems(consolidatedPendingChanges._removedItems);
|
||||
|
||||
// ready to go back to rendering activities
|
||||
|
@ -141,7 +88,6 @@ void Scene::processPendingChangesQueue() {
|
|||
}
|
||||
|
||||
void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {
|
||||
|
||||
auto resetPayload = payloads.begin();
|
||||
for (auto resetID : ids) {
|
||||
// Access the true item
|
||||
|
@ -153,28 +99,20 @@ void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {
|
|||
item.resetPayload(*resetPayload);
|
||||
auto newKey = item.getKey();
|
||||
|
||||
|
||||
// Reset the item in the Bucket map
|
||||
_masterBucketMap.reset(resetID, oldKey, newKey);
|
||||
|
||||
// Reset the item in the spatial tree
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), resetID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
// Update the item's container
|
||||
assert((oldKey.isSpatial() == newKey.isSpatial()) || oldKey._flags.none());
|
||||
if (newKey.isSpatial()) {
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), resetID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
} else {
|
||||
_masterNonspatialSet.insert(resetID);
|
||||
}
|
||||
|
||||
// next loop
|
||||
resetPayload++;
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::resortItems(const ItemIDs& ids, ItemKeys& oldKeys, ItemKeys& newKeys) {
|
||||
auto resortID = ids.begin();
|
||||
auto oldKey = oldKeys.begin();
|
||||
auto newKey = newKeys.begin();
|
||||
for (; resortID != ids.end(); resortID++, oldKey++, newKey++) {
|
||||
_masterBucketMap.reset(*resortID, *oldKey, *newKey);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::removeItems(const ItemIDs& ids) {
|
||||
for (auto removedID :ids) {
|
||||
// Access the true item
|
||||
|
@ -182,11 +120,12 @@ void Scene::removeItems(const ItemIDs& ids) {
|
|||
auto oldCell = item.getCell();
|
||||
auto oldKey = item.getKey();
|
||||
|
||||
// Remove from Bucket map
|
||||
_masterBucketMap.erase(removedID, item.getKey());
|
||||
|
||||
// Remove from spatial tree
|
||||
_masterSpatialTree.removeItem(oldCell, oldKey, removedID);
|
||||
// Remove the item
|
||||
if (oldKey.isSpatial()) {
|
||||
_masterSpatialTree.removeItem(oldCell, oldKey, removedID);
|
||||
} else {
|
||||
_masterNonspatialSet.erase(removedID);
|
||||
}
|
||||
|
||||
// Kill it
|
||||
item.kill();
|
||||
|
@ -202,14 +141,30 @@ void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) {
|
|||
auto oldCell = item.getCell();
|
||||
auto oldKey = item.getKey();
|
||||
|
||||
// Update it
|
||||
_items[updateID].update((*updateFunctor));
|
||||
|
||||
// Update the item
|
||||
item.update((*updateFunctor));
|
||||
auto newKey = item.getKey();
|
||||
|
||||
// Update the citem in the spatial tree if needed
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
// Update the item's container
|
||||
if (oldKey.isSpatial() == newKey.isSpatial()) {
|
||||
if (newKey.isSpatial()) {
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
}
|
||||
} else {
|
||||
if (newKey.isSpatial()) {
|
||||
_masterNonspatialSet.erase(updateID);
|
||||
|
||||
auto newCell = _masterSpatialTree.resetItem(oldCell, oldKey, item.getBound(), updateID, newKey);
|
||||
item.resetCell(newCell, newKey.isSmall());
|
||||
} else {
|
||||
_masterSpatialTree.removeItem(oldCell, oldKey, updateID);
|
||||
item.resetCell();
|
||||
|
||||
_masterNonspatialSet.insert(updateID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// next loop
|
||||
updateFunctor++;
|
||||
|
|
|
@ -17,21 +17,6 @@
|
|||
|
||||
namespace render {
|
||||
|
||||
// A map of ItemIDSets allowing to create bucket lists of items which are filtered according to their key
|
||||
class ItemBucketMap : public std::map<ItemFilter, ItemIDSet, ItemFilter::Less> {
|
||||
public:
|
||||
|
||||
ItemBucketMap() {}
|
||||
|
||||
void insert(const ItemID& id, const ItemKey& key);
|
||||
void erase(const ItemID& id, const ItemKey& key);
|
||||
void reset(const ItemID& id, const ItemKey& oldKey, const ItemKey& newKey);
|
||||
|
||||
// standard builders allocating the main buckets
|
||||
void allocateStandardOpaqueTranparentBuckets();
|
||||
|
||||
};
|
||||
|
||||
class Engine;
|
||||
|
||||
class PendingChanges {
|
||||
|
@ -40,7 +25,6 @@ public:
|
|||
~PendingChanges() {}
|
||||
|
||||
void resetItem(ItemID id, const PayloadPointer& payload);
|
||||
void resortItem(ItemID id, ItemKey oldKey, ItemKey newKey);
|
||||
void removeItem(ItemID id);
|
||||
|
||||
template <class T> void updateItem(ItemID id, std::function<void(T&)> func) {
|
||||
|
@ -48,14 +32,12 @@ public:
|
|||
}
|
||||
|
||||
void updateItem(ItemID id, const UpdateFunctorPointer& functor);
|
||||
void updateItem(ItemID id) { updateItem(id, nullptr); }
|
||||
|
||||
void merge(PendingChanges& changes);
|
||||
|
||||
ItemIDs _resetItems;
|
||||
Payloads _resetPayloads;
|
||||
ItemIDs _resortItems;
|
||||
ItemKeys _resortOldKeys;
|
||||
ItemKeys _resortNewKeys;
|
||||
ItemIDs _removedItems;
|
||||
ItemIDs _updatedItems;
|
||||
UpdateFunctors _updateFunctors;
|
||||
|
@ -75,27 +57,27 @@ public:
|
|||
Scene(glm::vec3 origin, float size);
|
||||
~Scene() {}
|
||||
|
||||
/// This call is thread safe, can be called from anywhere to allocate a new ID
|
||||
// This call is thread safe, can be called from anywhere to allocate a new ID
|
||||
ItemID allocateID();
|
||||
|
||||
/// Enqueue change batch to the scene
|
||||
// Enqueue change batch to the scene
|
||||
void enqueuePendingChanges(const PendingChanges& pendingChanges);
|
||||
|
||||
// Process the penging changes equeued
|
||||
void processPendingChangesQueue();
|
||||
|
||||
/// Access a particular item form its ID
|
||||
/// WARNING, There is No check on the validity of the ID, so this could return a bad Item
|
||||
// Access a particular item form its ID
|
||||
// WARNING, There is No check on the validity of the ID, so this could return a bad Item
|
||||
const Item& getItem(const ItemID& id) const { return _items[id]; }
|
||||
|
||||
size_t getNumItems() const { return _items.size(); }
|
||||
|
||||
/// Access the main bucketmap of items
|
||||
const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; }
|
||||
|
||||
/// Access the item spatial tree
|
||||
// Access the spatialized items
|
||||
const ItemSpatialTree& getSpatialTree() const { return _masterSpatialTree; }
|
||||
|
||||
|
||||
// Access non-spatialized items (overlays, backgrounds)
|
||||
const ItemIDSet& getNonspatialSet() const { return _masterNonspatialSet; }
|
||||
|
||||
protected:
|
||||
// Thread safe elements that can be accessed from anywhere
|
||||
std::atomic<unsigned int> _IDAllocator{ 1 }; // first valid itemID will be One
|
||||
|
@ -106,12 +88,10 @@ protected:
|
|||
// database of items is protected for editing by a mutex
|
||||
std::mutex _itemsMutex;
|
||||
Item::Vector _items;
|
||||
ItemBucketMap _masterBucketMap;
|
||||
ItemSpatialTree _masterSpatialTree;
|
||||
|
||||
ItemIDSet _masterNonspatialSet;
|
||||
|
||||
void resetItems(const ItemIDs& ids, Payloads& payloads);
|
||||
void resortItems(const ItemIDs& ids, ItemKeys& oldKeys, ItemKeys& newKeys);
|
||||
void removeItems(const ItemIDs& ids);
|
||||
void updateItems(const ItemIDs& ids, UpdateFunctors& functors);
|
||||
|
||||
|
|
120
libraries/render/src/render/SortTask.cpp
Normal file
120
libraries/render/src/render/SortTask.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// CullTask.cpp
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Sam Gateau on 2/2/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
|
||||
//
|
||||
|
||||
#include "SortTask.h"
|
||||
#include "ShapePipeline.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
using namespace render;
|
||||
|
||||
struct ItemBoundSort {
|
||||
float _centerDepth = 0.0f;
|
||||
float _nearDepth = 0.0f;
|
||||
float _farDepth = 0.0f;
|
||||
ItemID _id = 0;
|
||||
AABox _bounds;
|
||||
|
||||
ItemBoundSort() {}
|
||||
ItemBoundSort(float centerDepth, float nearDepth, float farDepth, ItemID id, const AABox& bounds) : _centerDepth(centerDepth), _nearDepth(nearDepth), _farDepth(farDepth), _id(id), _bounds(bounds) {}
|
||||
};
|
||||
|
||||
struct FrontToBackSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth < right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
struct BackToFrontSort {
|
||||
bool operator() (const ItemBoundSort& left, const ItemBoundSort& right) {
|
||||
return (left._centerDepth > right._centerDepth);
|
||||
}
|
||||
};
|
||||
|
||||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
outItems.reserve(inItems.size());
|
||||
|
||||
|
||||
// Make a local dataset of the center distance and closest point distance
|
||||
std::vector<ItemBoundSort> itemBoundSorts;
|
||||
itemBoundSorts.reserve(outItems.size());
|
||||
|
||||
for (auto itemDetails : inItems) {
|
||||
auto item = scene->getItem(itemDetails.id);
|
||||
auto bound = itemDetails.bound; // item.getBound();
|
||||
float distance = args->_viewFrustum->distanceToCamera(bound.calcCenter());
|
||||
|
||||
itemBoundSorts.emplace_back(ItemBoundSort(distance, distance, distance, itemDetails.id, bound));
|
||||
}
|
||||
|
||||
// sort against Z
|
||||
if (frontToBack) {
|
||||
FrontToBackSort frontToBackSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), frontToBackSort);
|
||||
} else {
|
||||
BackToFrontSort backToFrontSort;
|
||||
std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort);
|
||||
}
|
||||
|
||||
// Finally once sorted result to a list of itemID
|
||||
for (auto& item : itemBoundSorts) {
|
||||
outItems.emplace_back(ItemBound(item._id, item._bounds));
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapeBounds& outShapes) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
outShapes.clear();
|
||||
|
||||
for (const auto& item : inItems) {
|
||||
auto key = scene->getItem(item.id).getShapeKey();
|
||||
auto outItems = outShapes.find(key);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(key, ItemBounds{})).first;
|
||||
outItems->second.reserve(inItems.size());
|
||||
}
|
||||
|
||||
outItems->second.push_back(item);
|
||||
}
|
||||
|
||||
for (auto& items : outShapes) {
|
||||
items.second.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void DepthSortShapes::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapeBounds& inShapes, ShapeBounds& outShapes) {
|
||||
outShapes.clear();
|
||||
outShapes.reserve(inShapes.size());
|
||||
|
||||
for (auto& pipeline : inShapes) {
|
||||
auto& inItems = pipeline.second;
|
||||
auto outItems = outShapes.find(pipeline.first);
|
||||
if (outItems == outShapes.end()) {
|
||||
outItems = outShapes.insert(std::make_pair(pipeline.first, ItemBounds{})).first;
|
||||
}
|
||||
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems->second);
|
||||
}
|
||||
}
|
||||
|
||||
void DepthSortItems::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
depthSortItems(sceneContext, renderContext, _frontToBack, inItems, outItems);
|
||||
}
|
47
libraries/render/src/render/SortTask.h
Normal file
47
libraries/render/src/render/SortTask.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// SortTask.h
|
||||
// render/src/render
|
||||
//
|
||||
// Created by Zach Pomerantz on 2/26/2016.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_render_SortTask_h
|
||||
#define hifi_render_SortTask_h
|
||||
|
||||
#include "Engine.h"
|
||||
|
||||
namespace render {
|
||||
void depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
|
||||
class PipelineSortShapes {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<PipelineSortShapes, ItemBounds, ShapeBounds>;
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ShapeBounds& outShapes);
|
||||
};
|
||||
|
||||
class DepthSortShapes {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortShapes, ShapeBounds, ShapeBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortShapes(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapeBounds& inShapes, ShapeBounds& outShapes);
|
||||
};
|
||||
|
||||
class DepthSortItems {
|
||||
public:
|
||||
using JobModel = Job::ModelIO<DepthSortItems, ItemBounds, ItemBounds>;
|
||||
|
||||
bool _frontToBack;
|
||||
DepthSortItems(bool frontToBack = true) : _frontToBack(frontToBack) {}
|
||||
|
||||
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems);
|
||||
};
|
||||
}
|
||||
|
||||
#endif // hifi_render_SortTask_h;
|
|
@ -1,3 +1,3 @@
|
|||
set(TARGET_NAME script-engine)
|
||||
setup_hifi_library(Gui Network Script WebSockets Widgets)
|
||||
link_hifi_libraries(shared networking octree gpu procedural model model-networking recording avatars fbx entities controllers animation audio physics)
|
||||
link_hifi_libraries(shared networking octree gpu ui procedural model model-networking recording avatars fbx entities controllers animation audio physics)
|
||||
|
|
|
@ -13,20 +13,7 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
#include "MenuItemProperties.h"
|
||||
|
||||
MenuItemProperties::MenuItemProperties() :
|
||||
menuName(""),
|
||||
menuItemName(""),
|
||||
shortcutKey(""),
|
||||
shortcutKeyEvent(),
|
||||
shortcutKeySequence(),
|
||||
position(UNSPECIFIED_POSITION),
|
||||
beforeItem(""),
|
||||
afterItem(""),
|
||||
isCheckable(false),
|
||||
isChecked(false),
|
||||
isSeparator(false)
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& menuItemName,
|
||||
const QString& shortcutKey, bool checkable, bool checked, bool separator) :
|
||||
|
@ -35,9 +22,6 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m
|
|||
shortcutKey(shortcutKey),
|
||||
shortcutKeyEvent(),
|
||||
shortcutKeySequence(shortcutKey),
|
||||
position(UNSPECIFIED_POSITION),
|
||||
beforeItem(""),
|
||||
afterItem(""),
|
||||
isCheckable(checkable),
|
||||
isChecked(checked),
|
||||
isSeparator(separator)
|
||||
|
@ -48,12 +32,8 @@ MenuItemProperties::MenuItemProperties(const QString& menuName, const QString& m
|
|||
const KeyEvent& shortcutKeyEvent, bool checkable, bool checked, bool separator) :
|
||||
menuName(menuName),
|
||||
menuItemName(menuItemName),
|
||||
shortcutKey(""),
|
||||
shortcutKeyEvent(shortcutKeyEvent),
|
||||
shortcutKeySequence(shortcutKeyEvent),
|
||||
position(UNSPECIFIED_POSITION),
|
||||
beforeItem(""),
|
||||
afterItem(""),
|
||||
isCheckable(checkable),
|
||||
isChecked(checked),
|
||||
isSeparator(separator)
|
||||
|
|
|
@ -14,35 +14,35 @@
|
|||
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <ui/Menu.h>
|
||||
#include "KeyEvent.h"
|
||||
|
||||
const int UNSPECIFIED_POSITION = -1;
|
||||
|
||||
class MenuItemProperties {
|
||||
public:
|
||||
MenuItemProperties();
|
||||
MenuItemProperties(const QString& menuName, const QString& menuItemName,
|
||||
MenuItemProperties() {}
|
||||
MenuItemProperties(const QString& menuName, const QString& menuItemName,
|
||||
const QString& shortcutKey = QString(""), bool checkable = false, bool checked = false, bool separator = false);
|
||||
MenuItemProperties(const QString& menuName, const QString& menuItemName,
|
||||
MenuItemProperties(const QString& menuName, const QString& menuItemName,
|
||||
const KeyEvent& shortcutKeyEvent, bool checkable = false, bool checked = false, bool separator = false);
|
||||
|
||||
QString menuName;
|
||||
QString menuItemName;
|
||||
|
||||
|
||||
// Shortcut key items: in order of priority
|
||||
QString shortcutKey;
|
||||
KeyEvent shortcutKeyEvent;
|
||||
QKeySequence shortcutKeySequence; // this is what we actually use, it's set from one of the above
|
||||
|
||||
// location related items: in order of priority
|
||||
int position;
|
||||
int position { ui::Menu::UNSPECIFIED_POSITION };
|
||||
QString beforeItem;
|
||||
QString afterItem;
|
||||
|
||||
// other properties
|
||||
bool isCheckable;
|
||||
bool isChecked;
|
||||
bool isSeparator;
|
||||
bool isCheckable { false };
|
||||
bool isChecked { false };
|
||||
bool isSeparator { false };
|
||||
|
||||
QString grouping; /// Either: "", "Advanced", or "Developer"
|
||||
};
|
||||
|
|
|
@ -258,9 +258,11 @@ template<typename Enum> inline void PropertyFlags<Enum>::debugDumpBits() {
|
|||
qDebug() << "_minFlag=" << _minFlag;
|
||||
qDebug() << "_maxFlag=" << _maxFlag;
|
||||
qDebug() << "_trailingFlipped=" << _trailingFlipped;
|
||||
QString bits;
|
||||
for(int i = 0; i < _flags.size(); i++) {
|
||||
qDebug() << "bit[" << i << "]=" << _flags.at(i);
|
||||
bits += (_flags.at(i) ? "1" : "0");
|
||||
}
|
||||
qDebug() << "bits:" << bits;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ namespace Cursor {
|
|||
return instance;
|
||||
}
|
||||
|
||||
QList<uint16_t> Manager::registeredIcons() const {
|
||||
return ICONS.keys();
|
||||
}
|
||||
|
||||
uint8_t Manager::getCount() {
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace Cursor {
|
|||
void setScale(float scale);
|
||||
Instance* getCursor(uint8_t index = 0);
|
||||
uint16_t registerIcon(const QString& path);
|
||||
QList<uint16_t> registeredIcons() const;
|
||||
const QString& getIconImage(uint16_t icon);
|
||||
private:
|
||||
float _scale{ 1.0f };
|
||||
|
|
|
@ -217,6 +217,13 @@ QmlWindowClass::QmlWindowClass(QObject* qmlWindow)
|
|||
qDebug() << "Created window with ID " << _windowId;
|
||||
Q_ASSERT(_qmlWindow);
|
||||
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow));
|
||||
// Forward messages received from QML on to the script
|
||||
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void QmlWindowClass::sendToQml(const QVariant& message) {
|
||||
// Forward messages received from the script on to QML
|
||||
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
}
|
||||
|
||||
QmlWindowClass::~QmlWindowClass() {
|
||||
|
|
|
@ -67,17 +67,23 @@ public slots:
|
|||
|
||||
void setTitle(const QString& title);
|
||||
|
||||
|
||||
// Ugh.... do not want to do
|
||||
Q_INVOKABLE void raise();
|
||||
Q_INVOKABLE void close();
|
||||
Q_INVOKABLE int getWindowId() const { return _windowId; };
|
||||
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
|
||||
|
||||
// Scripts can use this to send a message to the QML object
|
||||
void sendToQml(const QVariant& message);
|
||||
|
||||
signals:
|
||||
void visibilityChanged(bool visible); // Tool window
|
||||
void moved(glm::vec2 position);
|
||||
void resized(QSizeF size);
|
||||
void closed();
|
||||
// Scripts can connect to this signal to receive messages from the QML object
|
||||
void fromQml(const QVariant& message);
|
||||
|
||||
protected slots:
|
||||
void hasClosed();
|
||||
|
|
11
libraries/ui/src/ui/Logging.cpp
Normal file
11
libraries/ui/src/ui/Logging.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis 2016/03/01
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
#include "Logging.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(uiLogging, "hifi.ui")
|
16
libraries/ui/src/ui/Logging.h
Normal file
16
libraries/ui/src/ui/Logging.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis 2015/10/11
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
#ifndef hifi_Controllers_Logging_h
|
||||
#define hifi_Controllers_Logging_h
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(uiLogging)
|
||||
|
||||
#endif
|
561
libraries/ui/src/ui/Menu.cpp
Normal file
561
libraries/ui/src/ui/Menu.cpp
Normal file
|
@ -0,0 +1,561 @@
|
|||
//
|
||||
// Created by Stephen Birarda on 8/12/13.
|
||||
// Copyright 2013 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
|
||||
//
|
||||
|
||||
#include "Menu.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtWidgets/QShortcut>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#include "../VrMenu.h"
|
||||
#include "Logging.h"
|
||||
|
||||
using namespace ui;
|
||||
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
|
||||
|
||||
Menu* Menu::getInstance() {
|
||||
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
|
||||
return instance;
|
||||
}
|
||||
|
||||
Menu::Menu() {
|
||||
}
|
||||
|
||||
void Menu::toggleAdvancedMenus() {
|
||||
setGroupingIsVisible("Advanced", !getGroupingIsVisible("Advanced"));
|
||||
}
|
||||
|
||||
void Menu::toggleDeveloperMenus() {
|
||||
setGroupingIsVisible("Developer", !getGroupingIsVisible("Developer"));
|
||||
}
|
||||
|
||||
void Menu::loadSettings() {
|
||||
scanMenuBar(&Menu::loadAction);
|
||||
}
|
||||
|
||||
void Menu::saveSettings() {
|
||||
scanMenuBar(&Menu::saveAction);
|
||||
}
|
||||
|
||||
void Menu::loadAction(Settings& settings, QAction& action) {
|
||||
if (action.isChecked() != settings.value(action.text(), action.isChecked()).toBool()) {
|
||||
action.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::saveAction(Settings& settings, QAction& action) {
|
||||
settings.setValue(action.text(), action.isChecked());
|
||||
}
|
||||
|
||||
void Menu::scanMenuBar(settingsAction modifySetting) {
|
||||
Settings settings;
|
||||
foreach (QMenu* menu, findChildren<QMenu*>()) {
|
||||
scanMenu(*menu, modifySetting, settings);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings) {
|
||||
settings.beginGroup(menu.title());
|
||||
foreach (QAction* action, menu.actions()) {
|
||||
if (action->menu()) {
|
||||
scanMenu(*action->menu(), modifySetting, settings);
|
||||
} else if (action->isCheckable()) {
|
||||
modifySetting(settings, *action);
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void Menu::addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName,
|
||||
int menuItemLocation, const QString& grouping) {
|
||||
QAction* actionBefore = NULL;
|
||||
QAction* separator;
|
||||
QAction* separatorText;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
if (actionBefore) {
|
||||
separator = new QAction("",destinationMenu);
|
||||
destinationMenu->insertAction(actionBefore, separator);
|
||||
separator->setSeparator(true);
|
||||
|
||||
separatorText = new QAction(actionName,destinationMenu);
|
||||
separatorText->setEnabled(false);
|
||||
destinationMenu->insertAction(actionBefore, separatorText);
|
||||
|
||||
} else {
|
||||
separator = destinationMenu->addSeparator();
|
||||
separatorText = destinationMenu->addAction(actionName);
|
||||
separatorText->setEnabled(false);
|
||||
}
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << separator;
|
||||
_groupingActions[grouping] << separatorText;
|
||||
bool isVisible = getGroupingIsVisible(grouping);
|
||||
separator->setVisible(isVisible);
|
||||
separatorText->setVisible(isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
const QObject* receiver,
|
||||
const char* member,
|
||||
QAction::MenuRole role,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
QAction* action = NULL;
|
||||
QAction* actionBefore = NULL;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
|
||||
if (!actionBefore) {
|
||||
if (receiver && member) {
|
||||
action = destinationMenu->addAction(actionName, receiver, member, shortcut);
|
||||
} else {
|
||||
action = destinationMenu->addAction(actionName);
|
||||
action->setShortcut(shortcut);
|
||||
}
|
||||
} else {
|
||||
action = new QAction(actionName, destinationMenu);
|
||||
action->setShortcut(shortcut);
|
||||
destinationMenu->insertAction(actionBefore, action);
|
||||
|
||||
if (receiver && member) {
|
||||
connect(action, SIGNAL(triggered()), receiver, member);
|
||||
}
|
||||
}
|
||||
action->setMenuRole(role);
|
||||
|
||||
_actionHash.insert(actionName, action);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* Menu::addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
QAction* action,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
QAction::MenuRole role,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
QAction* actionBefore = NULL;
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
|
||||
if (!actionName.isEmpty()) {
|
||||
action->setText(actionName);
|
||||
}
|
||||
|
||||
if (shortcut != 0) {
|
||||
action->setShortcut(shortcut);
|
||||
}
|
||||
|
||||
if (role != QAction::NoRole) {
|
||||
action->setMenuRole(role);
|
||||
}
|
||||
|
||||
if (!actionBefore) {
|
||||
destinationMenu->addAction(action);
|
||||
} else {
|
||||
destinationMenu->insertAction(actionBefore, action);
|
||||
}
|
||||
|
||||
_actionHash.insert(action->text(), action);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* Menu::addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut,
|
||||
const bool checked,
|
||||
const QObject* receiver,
|
||||
const char* member,
|
||||
int menuItemLocation,
|
||||
const QString& grouping) {
|
||||
|
||||
QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member,
|
||||
QAction::NoRole, menuItemLocation);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(checked);
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
void Menu::removeAction(MenuWrapper* menu, const QString& actionName) {
|
||||
auto action = _actionHash.value(actionName);
|
||||
menu->removeAction(action);
|
||||
_actionHash.remove(actionName);
|
||||
for (auto& grouping : _groupingActions) {
|
||||
grouping.remove(action);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, menuOption),
|
||||
Q_ARG(bool, isChecked));
|
||||
return;
|
||||
}
|
||||
QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
menu->setChecked(isChecked);
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::isOptionChecked(const QString& menuOption) const {
|
||||
const QAction* menu = _actionHash.value(menuOption);
|
||||
if (menu) {
|
||||
return menu->isChecked();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::triggerOption(const QString& menuOption) {
|
||||
QAction* action = _actionHash.value(menuOption);
|
||||
if (action) {
|
||||
action->trigger();
|
||||
} else {
|
||||
qCDebug(uiLogging) << "NULL Action for menuOption '" << menuOption << "'";
|
||||
}
|
||||
}
|
||||
|
||||
QAction* Menu::getActionForOption(const QString& menuOption) {
|
||||
return _actionHash.value(menuOption);
|
||||
}
|
||||
|
||||
QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) {
|
||||
QList<QAction*> menuActions;
|
||||
if (menu) {
|
||||
menuActions = menu->actions();
|
||||
} else {
|
||||
menuActions = actions();
|
||||
}
|
||||
|
||||
foreach (QAction* menuAction, menuActions) {
|
||||
QString actionText = menuAction->text();
|
||||
if (menuName == menuAction->text()) {
|
||||
return menuAction;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) {
|
||||
QAction* action = getActionFromName(menuName, menu);
|
||||
if (action) {
|
||||
return MenuWrapper::fromMenu(action->menu());
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getMenuParent(const QString& menuName, QString& finalMenuPart) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
parent = menu;
|
||||
finalMenuPart = menuTreePart.trimmed();
|
||||
menu = getSubMenuFromName(finalMenuPart, parent);
|
||||
if (!menu) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
MenuWrapper* Menu::getMenu(const QString& menuName) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
int item = 0;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
menu = getSubMenuFromName(menuTreePart.trimmed(), parent);
|
||||
if (!menu) {
|
||||
break;
|
||||
}
|
||||
parent = menu;
|
||||
item++;
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
QAction* Menu::getMenuAction(const QString& menuName) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* parent = NULL;
|
||||
QAction* action = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
action = getActionFromName(menuTreePart.trimmed(), parent);
|
||||
if (!action) {
|
||||
break;
|
||||
}
|
||||
parent = MenuWrapper::fromMenu(action->menu());
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
int Menu::findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem) {
|
||||
int position = 0;
|
||||
foreach(QAction* action, menu->actions()) {
|
||||
if (action->text() == searchMenuItem) {
|
||||
return position;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
return UNSPECIFIED_POSITION; // not found
|
||||
}
|
||||
|
||||
int Menu::positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition) {
|
||||
QList<QAction*> menuActions = menu->actions();
|
||||
if (requestedPosition > 1 && requestedPosition < menuActions.size()) {
|
||||
QAction* beforeRequested = menuActions[requestedPosition - 1];
|
||||
if (beforeRequested->isSeparator()) {
|
||||
requestedPosition--;
|
||||
}
|
||||
}
|
||||
return requestedPosition;
|
||||
}
|
||||
|
||||
bool Menu::_isSomeSubmenuShown = false;
|
||||
|
||||
MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) {
|
||||
QStringList menuTree = menuName.split(">");
|
||||
MenuWrapper* addTo = NULL;
|
||||
MenuWrapper* menu = NULL;
|
||||
foreach (QString menuTreePart, menuTree) {
|
||||
menu = getSubMenuFromName(menuTreePart.trimmed(), addTo);
|
||||
if (!menu) {
|
||||
if (!addTo) {
|
||||
menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed()));
|
||||
} else {
|
||||
menu = addTo->addMenu(menuTreePart.trimmed());
|
||||
}
|
||||
}
|
||||
addTo = menu;
|
||||
}
|
||||
|
||||
if (isValidGrouping(grouping)) {
|
||||
auto action = getMenuAction(menuName);
|
||||
if (action) {
|
||||
_groupingActions[grouping] << action;
|
||||
action->setVisible(getGroupingIsVisible(grouping));
|
||||
}
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
|
||||
// hook our show/hide for popup menus, so we can keep track of whether or not one
|
||||
// of our submenus is currently showing.
|
||||
connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; });
|
||||
connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; });
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void Menu::removeMenu(const QString& menuName) {
|
||||
QAction* action = getMenuAction(menuName);
|
||||
|
||||
// only proceed if the menu actually exists
|
||||
if (action) {
|
||||
QString finalMenuPart;
|
||||
MenuWrapper* parent = getMenuParent(menuName, finalMenuPart);
|
||||
if (parent) {
|
||||
parent->removeAction(action);
|
||||
} else {
|
||||
QMenuBar::removeAction(action);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::menuExists(const QString& menuName) {
|
||||
QAction* action = getMenuAction(menuName);
|
||||
|
||||
// only proceed if the menu actually exists
|
||||
if (action) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping) {
|
||||
MenuWrapper* menuObj = getMenu(menuName);
|
||||
if (menuObj) {
|
||||
addDisabledActionAndSeparator(menuObj, separatorName);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeSeparator(const QString& menuName, const QString& separatorName) {
|
||||
MenuWrapper* menu = getMenu(menuName);
|
||||
bool separatorRemoved = false;
|
||||
if (menu) {
|
||||
int textAt = findPositionOfMenuItem(menu, separatorName);
|
||||
QList<QAction*> menuActions = menu->actions();
|
||||
QAction* separatorText = menuActions[textAt];
|
||||
if (textAt > 0 && textAt < menuActions.size()) {
|
||||
QAction* separatorLine = menuActions[textAt - 1];
|
||||
if (separatorLine) {
|
||||
if (separatorLine->isSeparator()) {
|
||||
menu->removeAction(separatorText);
|
||||
menu->removeAction(separatorLine);
|
||||
separatorRemoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (separatorRemoved) {
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::removeMenuItem(const QString& menu, const QString& menuitem) {
|
||||
MenuWrapper* menuObj = getMenu(menu);
|
||||
if (menuObj) {
|
||||
removeAction(menuObj, menuitem);
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::menuItemExists(const QString& menu, const QString& menuitem) {
|
||||
QAction* menuItemAction = _actionHash.value(menuitem);
|
||||
if (menuItemAction) {
|
||||
return (getMenu(menu) != NULL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Menu::getGroupingIsVisible(const QString& grouping) {
|
||||
if (grouping.isEmpty() || grouping.isNull()) {
|
||||
return true;
|
||||
}
|
||||
if (_groupingVisible.contains(grouping)) {
|
||||
return _groupingVisible[grouping];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::setGroupingIsVisible(const QString& grouping, bool isVisible) {
|
||||
// NOTE: Default grouping always visible
|
||||
if (grouping.isEmpty() || grouping.isNull()) {
|
||||
return;
|
||||
}
|
||||
_groupingVisible[grouping] = isVisible;
|
||||
|
||||
for (auto action: _groupingActions[grouping]) {
|
||||
action->setVisible(isVisible);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected, QObject* receiver, const char* slot) {
|
||||
auto menu = addMenu(groupName);
|
||||
|
||||
QActionGroup* actionGroup = new QActionGroup(menu);
|
||||
actionGroup->setExclusive(true);
|
||||
|
||||
for (auto action : actionList) {
|
||||
auto item = addCheckableActionToQMenuAndActionHash(menu, action, 0, action == selected, receiver, slot);
|
||||
actionGroup->addAction(item);
|
||||
}
|
||||
|
||||
QMenuBar::repaint();
|
||||
}
|
||||
|
||||
void Menu::removeActionGroup(const QString& groupName) {
|
||||
removeMenu(groupName);
|
||||
}
|
||||
|
||||
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addMenu(menu);
|
||||
});
|
||||
_backMap[menu] = this;
|
||||
}
|
||||
|
||||
QList<QAction*> MenuWrapper::actions() {
|
||||
return _realMenu->actions();
|
||||
}
|
||||
|
||||
MenuWrapper* MenuWrapper::addMenu(const QString& menuName) {
|
||||
return new MenuWrapper(_realMenu->addMenu(menuName));
|
||||
}
|
||||
|
||||
void MenuWrapper::setEnabled(bool enabled) {
|
||||
_realMenu->setEnabled(enabled);
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addSeparator() {
|
||||
return _realMenu->addSeparator();
|
||||
}
|
||||
|
||||
void MenuWrapper::addAction(QAction* action) {
|
||||
_realMenu->addAction(action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addAction(const QString& menuName) {
|
||||
QAction* action = _realMenu->addAction(menuName);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) {
|
||||
QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->addAction(_realMenu, action);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
void MenuWrapper::removeAction(QAction* action) {
|
||||
_realMenu->removeAction(action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->removeAction(action);
|
||||
});
|
||||
}
|
||||
|
||||
void MenuWrapper::insertAction(QAction* before, QAction* action) {
|
||||
_realMenu->insertAction(before, action);
|
||||
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
|
||||
vrMenu->insertAction(before, action);
|
||||
});
|
||||
}
|
||||
|
||||
QHash<QMenu*, MenuWrapper*> MenuWrapper::_backMap;
|
155
libraries/ui/src/ui/Menu.h
Normal file
155
libraries/ui/src/ui/Menu.h
Normal file
|
@ -0,0 +1,155 @@
|
|||
//
|
||||
// Created by Stephen Birarda on 8/12/13.
|
||||
// Copyright 2013 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ui_Menu_h
|
||||
#define hifi_ui_Menu_h
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtGui/QKeySequence>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
|
||||
class Settings;
|
||||
namespace ui {
|
||||
class Menu;
|
||||
}
|
||||
|
||||
class MenuWrapper : public QObject {
|
||||
public:
|
||||
|
||||
QList<QAction*> actions();
|
||||
MenuWrapper* addMenu(const QString& menuName);
|
||||
void setEnabled(bool enabled = true);
|
||||
QAction* addSeparator();
|
||||
void addAction(QAction* action);
|
||||
|
||||
QAction* addAction(const QString& menuName);
|
||||
void insertAction(QAction* before, QAction* menuName);
|
||||
|
||||
QAction* addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut = 0);
|
||||
void removeAction(QAction* action);
|
||||
|
||||
QAction* newAction() {
|
||||
return new QAction(_realMenu);
|
||||
}
|
||||
|
||||
private:
|
||||
MenuWrapper(QMenu* menu);
|
||||
|
||||
static MenuWrapper* fromMenu(QMenu* menu) {
|
||||
return _backMap[menu];
|
||||
}
|
||||
|
||||
QMenu* const _realMenu;
|
||||
static QHash<QMenu*, MenuWrapper*> _backMap;
|
||||
friend class ui::Menu;
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
class Menu : public QMenuBar {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const int UNSPECIFIED_POSITION = -1;
|
||||
|
||||
Menu();
|
||||
static Menu* getInstance();
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
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,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut = 0,
|
||||
const QObject* receiver = NULL,
|
||||
const char* member = NULL,
|
||||
QAction::MenuRole role = QAction::NoRole,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
QAction* action,
|
||||
const QString& actionName = QString(),
|
||||
const QKeySequence& shortcut = 0,
|
||||
QAction::MenuRole role = QAction::NoRole,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
const QKeySequence& shortcut = 0,
|
||||
const bool checked = false,
|
||||
const QObject* receiver = NULL,
|
||||
const char* member = NULL,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
void removeAction(MenuWrapper* menu, const QString& actionName);
|
||||
|
||||
public slots:
|
||||
MenuWrapper* addMenu(const QString& menuName, const QString& grouping = QString());
|
||||
void removeMenu(const QString& menuName);
|
||||
bool menuExists(const QString& menuName);
|
||||
void addSeparator(const QString& menuName, const QString& separatorName, const QString& grouping = QString());
|
||||
void removeSeparator(const QString& menuName, const QString& separatorName);
|
||||
void removeMenuItem(const QString& menuName, const QString& menuitem);
|
||||
bool menuItemExists(const QString& menuName, const QString& menuitem);
|
||||
void addActionGroup(const QString& groupName, const QStringList& actionList, const QString& selected = QString(),
|
||||
QObject* receiver = nullptr, const char* slot = nullptr);
|
||||
void removeActionGroup(const QString& groupName);
|
||||
bool isOptionChecked(const QString& menuOption) const;
|
||||
void setIsOptionChecked(const QString& menuOption, bool isChecked);
|
||||
|
||||
bool getGroupingIsVisible(const QString& grouping);
|
||||
void setGroupingIsVisible(const QString& grouping, bool isVisible); /// NOTE: the "" grouping is always visible
|
||||
|
||||
void toggleDeveloperMenus();
|
||||
void toggleAdvancedMenus();
|
||||
|
||||
static bool isSomeSubmenuShown() { return _isSomeSubmenuShown; }
|
||||
|
||||
protected:
|
||||
typedef void(*settingsAction)(Settings&, QAction&);
|
||||
static void loadAction(Settings& settings, QAction& action);
|
||||
static void saveAction(Settings& settings, QAction& action);
|
||||
void scanMenuBar(settingsAction modifySetting);
|
||||
void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings);
|
||||
|
||||
/// helper method to have separators with labels that are also compatible with OS X
|
||||
void addDisabledActionAndSeparator(MenuWrapper* destinationMenu,
|
||||
const QString& actionName,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION,
|
||||
const QString& grouping = QString());
|
||||
|
||||
QAction* getActionFromName(const QString& menuName, MenuWrapper* menu);
|
||||
MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart);
|
||||
|
||||
QAction* getMenuAction(const QString& menuName);
|
||||
int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem);
|
||||
int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition);
|
||||
|
||||
QHash<QString, QAction*> _actionHash;
|
||||
|
||||
bool isValidGrouping(const QString& grouping) const { return grouping == "Advanced" || grouping == "Developer"; }
|
||||
QHash<QString, bool> _groupingVisible;
|
||||
QHash<QString, QSet<QAction*>> _groupingActions;
|
||||
|
||||
static bool _isSomeSubmenuShown;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // hifi_Menu_h
|
|
@ -13,7 +13,7 @@ if (WIN32)
|
|||
|
||||
set(TARGET_NAME oculus)
|
||||
setup_hifi_plugin()
|
||||
link_hifi_libraries(shared gl plugins display-plugins)
|
||||
link_hifi_libraries(shared gl gpu controllers plugins display-plugins input-plugins)
|
||||
|
||||
include_hifi_library_headers(octree)
|
||||
|
||||
|
|
|
@ -26,31 +26,7 @@ glm::mat4 OculusBaseDisplayPlugin::getHeadPose(uint32_t frameIndex) const {
|
|||
}
|
||||
|
||||
bool OculusBaseDisplayPlugin::isSupported() const {
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
qDebug() << "OculusBaseDisplayPlugin : ovr_Initialize() failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
ovrSession session { nullptr };
|
||||
ovrGraphicsLuid luid;
|
||||
auto result = ovr_Create(&session, &luid);
|
||||
if (!OVR_SUCCESS(result)) {
|
||||
ovrErrorInfo error;
|
||||
ovr_GetLastErrorInfo(&error);
|
||||
qDebug() << "OculusBaseDisplayPlugin : ovr_Create() failed" << result << error.Result << error.ErrorString;
|
||||
ovr_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto hmdDesc = ovr_GetHmdDesc(session);
|
||||
if (hmdDesc.Type == ovrHmd_None) {
|
||||
ovr_Destroy(session);
|
||||
ovr_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
ovr_Shutdown();
|
||||
return true;
|
||||
return oculusAvailable();
|
||||
}
|
||||
|
||||
// DLL based display plugins MUST initialize GLEW inside the DLL code.
|
||||
|
@ -68,15 +44,7 @@ void OculusBaseDisplayPlugin::deinit() {
|
|||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::activate() {
|
||||
if (!OVR_SUCCESS(ovr_Initialize(nullptr))) {
|
||||
qFatal("Could not init OVR");
|
||||
}
|
||||
|
||||
if (!OVR_SUCCESS(ovr_Create(&_session, &_luid))) {
|
||||
qFatal("Failed to acquire HMD");
|
||||
}
|
||||
|
||||
HmdDisplayPlugin::activate();
|
||||
_session = acquireOculusSession();
|
||||
|
||||
_hmdDesc = ovr_GetHmdDesc(_session);
|
||||
|
||||
|
@ -123,11 +91,15 @@ void OculusBaseDisplayPlugin::activate() {
|
|||
ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) {
|
||||
qFatal("Could not attach to sensor device");
|
||||
}
|
||||
|
||||
// This must come after the initialization, so that the values calculated
|
||||
// above are available during the customizeContext call (when not running
|
||||
// in threaded present mode)
|
||||
HmdDisplayPlugin::activate();
|
||||
}
|
||||
|
||||
void OculusBaseDisplayPlugin::deactivate() {
|
||||
HmdDisplayPlugin::deactivate();
|
||||
ovr_Destroy(_session);
|
||||
releaseOculusSession();
|
||||
_session = nullptr;
|
||||
ovr_Shutdown();
|
||||
}
|
||||
|
|
|
@ -19,8 +19,3 @@ bool OculusDebugDisplayPlugin::isSupported() const {
|
|||
}
|
||||
return OculusBaseDisplayPlugin::isSupported();
|
||||
}
|
||||
|
||||
void OculusDebugDisplayPlugin::customizeContext() {
|
||||
OculusBaseDisplayPlugin::customizeContext();
|
||||
enableVsync(false);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue