Merge branch 'master' of https://github.com/highfidelity/hifi into orange

This commit is contained in:
samcake 2016-03-08 19:42:26 -08:00
commit 5f80d7e53e
114 changed files with 2518 additions and 2288 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,6 +21,7 @@
#include <QSettings>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <QtCore/QUrl>
#include "Menu.h"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -220,6 +220,11 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) {
} else {
sendingMode << "S";
}
if (stats.isFullScene()) {
sendingMode << "F";
} else {
sendingMode << "p";
}
}
});
sendingMode << " - " << serverCount << " servers";

View file

@ -9,6 +9,7 @@
//
#include <OffscreenUi.h>
#include <display-plugins/CompositorHelper.h>
#include "Application.h"
#include "avatar/AvatarManager.h"

View file

@ -16,6 +16,7 @@
#include <ScriptEngines.h>
#include <OffscreenUi.h>
#include <Preferences.h>
#include <display-plugins/CompositorHelper.h>
#include "Application.h"
#include "DialogsManager.h"

View file

@ -224,6 +224,12 @@ void Stats::updateStats(bool force) {
} else {
sendingModeStream << "S";
}
if (stats.isFullScene()) {
sendingModeStream << "F";
}
else {
sendingModeStream << "p";
}
}
// calculate server node totals

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

View file

@ -124,7 +124,7 @@ QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr) {
return dataStream;
}
QHostAddress getLocalAddress() {
QHostAddress getGuessedLocalAddress() {
QHostAddress localAddress;

View file

@ -92,7 +92,7 @@ namespace std {
}
QHostAddress getLocalAddress();
QHostAddress getGuessedLocalAddress();
Q_DECLARE_METATYPE(HifiSockAddr);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,6 +44,10 @@ namespace Cursor {
return instance;
}
QList<uint16_t> Manager::registeredIcons() const {
return ICONS.keys();
}
uint8_t Manager::getCount() {
return 1;
}

View file

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

View file

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

View file

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

View 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")

View 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

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

View file

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

View file

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

View file

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