fixed merge conflict

This commit is contained in:
Dante Ruiz 2017-06-21 01:10:54 +01:00
commit b7286db523
87 changed files with 1787 additions and 833 deletions

View file

@ -10,7 +10,6 @@
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
@ -38,8 +37,23 @@
</li>
<li><a href="/content/">Content</a></li>
<li><a href="/settings/">Settings</a></li>
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
</ul>
</div>
</div><!-- /.container-fluid -->
</nav>
<div class="modal fade" id="restart-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">domain-server is restarting</h4>
</div>
<div class="modal-body">
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="container-fluid">

View file

@ -1,3 +1,28 @@
function showRestartModal() {
$('#restart-modal').modal({
backdrop: 'static',
keyboard: false
});
var secondsElapsed = 0;
var numberOfSecondsToWait = 3;
var refreshSpan = $('span#refresh-time')
refreshSpan.html(numberOfSecondsToWait + " seconds");
// call ourselves every 1 second to countdown
var refreshCountdown = setInterval(function(){
secondsElapsed++;
secondsLeft = numberOfSecondsToWait - secondsElapsed
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
if (secondsElapsed == numberOfSecondsToWait) {
location.reload(true);
clearInterval(refreshCountdown);
}
}, 1000);
}
$(document).ready(function(){
var url = window.location;
// Will only work if string in href matches with location
@ -7,4 +32,10 @@ $(document).ready(function(){
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e){
$.get("/restart");
showRestartModal();
return false;
});
});

View file

@ -81,19 +81,6 @@
</div>
</div>
<div class="modal fade" id="restart-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">domain-server is restarting</h4>
</div>
<div class="modal-body">
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>

View file

@ -1680,31 +1680,6 @@ function updateDataChangedForSiblingRows(row, forceTrue) {
})
}
function showRestartModal() {
$('#restart-modal').modal({
backdrop: 'static',
keyboard: false
});
var secondsElapsed = 0;
var numberOfSecondsToWait = 3;
var refreshSpan = $('span#refresh-time')
refreshSpan.html(numberOfSecondsToWait + " seconds");
// call ourselves every 1 second to countdown
var refreshCountdown = setInterval(function(){
secondsElapsed++;
secondsLeft = numberOfSecondsToWait - secondsElapsed
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
if (secondsElapsed == numberOfSecondsToWait) {
location.reload(true);
clearInterval(refreshCountdown);
}
}, 1000);
}
function cleanupFormValues(node) {
if (node.type && node.type === 'checkbox') {
return { name: node.name, value: node.checked ? true : false };

View file

@ -1650,6 +1650,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_NODES = "/nodes";
const QString URI_SETTINGS = "/settings";
const QString URI_ENTITY_FILE_UPLOAD = "/content/upload";
const QString URI_RESTART = "/restart";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
@ -1804,6 +1805,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// send the response
connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE));
return true;
} else if (url.path() == URI_RESTART) {
connection->respond(HTTPConnection::StatusCode200);
restart();
return true;
} else {
// check if this is for json stats for a node

View file

@ -1,70 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="mic-mute-a.svg"><metadata
id="metadata22"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs20" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="852"
inkscape:window-height="480"
id="namedview18"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><style
type="text/css"
id="style4">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /><g
id="Layer_1"
style="fill:#000000;fill-opacity:1"><path
class="st0"
d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"
id="path8"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"
id="path10"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2l-0.2-0.2c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4 c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"
id="path12"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M23.4,40.2l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8 c0-1-0.8-1.8-1.8-1.8h-4.4l0-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8 c0,0.3,0,4.8,0,5.2c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"
id="path14"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1 C17.7,25.9,17.7,25,17.7,24.9z"
id="path16"
style="fill:#000000;fill-opacity:1" /></g></svg>
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,21 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<svg version="1.1"
id="svg2" inkscape:version="0.91 r13725" sodipodi:docname="mic-mute-a.svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st0{fill:#EA4C5F;}
</style>
<sodipodi:namedview bordercolor="#666666" borderopacity="1" gridtolerance="10" guidetolerance="10" id="namedview18" inkscape:current-layer="svg2" inkscape:cx="25" inkscape:cy="25" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="480" inkscape:window-maximized="0" inkscape:window-width="852" inkscape:window-x="0" inkscape:window-y="0" inkscape:zoom="4.72" objecttolerance="10" pagecolor="#ff0000" showgrid="false">
</sodipodi:namedview>
<g id="Layer_2">
</g>
<g id="Layer_1">
<path class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2l-0.2-0.2c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
<path id="path8" class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6l0,0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
<path id="path10" class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6l0,0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
<path id="path12" class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2L11,38.7c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
<path class="st0" d="M23.4,40.2l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
c0-1-0.8-1.8-1.8-1.8h-4.4l0-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8
c0,0.3,0,4.8,0,5.2c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
<path id="path14" class="st0" d="M23.4,40.2v3.4h-4.3c-1,0-1.8,0.8-1.8,1.8s0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
s-0.8-1.8-1.8-1.8H27v-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
<path id="path16" class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
C17.7,25.9,17.7,25,17.7,24.9z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -12,84 +12,21 @@ import QtQuick.Controls 1.3
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
import "./hifi/audio" as HifiAudio
Hifi.AvatarInputs {
id: root
id: root;
objectName: "AvatarInputs"
width: rootWidth
height: controls.height
x: 10; y: 5
width: audio.width;
height: audio.height;
x: 10; y: 5;
readonly property int rootWidth: 265
readonly property int iconSize: 24
readonly property int iconPadding: 5
readonly property bool shouldReposition: true;
readonly property bool shouldReposition: true
Settings {
category: "Overlay.AvatarInputs"
property alias x: root.x
property alias y: root.y
}
MouseArea {
id: hover
hoverEnabled: true
drag.target: parent
anchors.fill: parent
}
Item {
id: controls
width: root.rootWidth
height: 44
visible: root.showAudioTools
Rectangle {
anchors.fill: parent
color: "#00000000"
Item {
id: audioMeter
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.iconPadding
anchors.right: parent.right
anchors.rightMargin: root.iconPadding
height: 8
Rectangle {
id: blueRect
color: "blue"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 4
}
Rectangle {
id: greenRect
color: "green"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: blueRect.right
anchors.right: redRect.left
}
Rectangle {
id: redRect
color: "red"
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 5
}
Rectangle {
z: 100
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: (1.0 - root.audioLevel) * parent.width
color: "black"
}
}
}
HifiAudio.MicBar {
id: audio;
visible: root.showAudioTools;
standalone: true;
dragTarget: parent;
}
}

View file

@ -65,6 +65,12 @@ Windows.Window {
root.dynamicContent.fromScript(message);
}
}
function clearDebugWindow() {
if (root.dynamicContent && root.dynamicContent.clearWindow) {
root.dynamicContent.clearWindow();
}
}
// Handle message traffic from our loaded QML to the script that launched us
signal sendToScript(var message);

View file

@ -18,7 +18,7 @@ import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows"
import "./" as Audio
import "./" as AudioControls
Rectangle {
id: root;
@ -57,15 +57,16 @@ Rectangle {
x: 16; // padding does not work
spacing: 16;
Audio.CheckBox {
AudioControls.CheckBox {
text: qsTr("Mute microphone");
isRedCheck: true;
checked: Audio.muted;
onClicked: {
Audio.muted = checked;
checked = Qt.binding(function() { return Audio.muted; }); // restore binding
}
}
Audio.CheckBox {
AudioControls.CheckBox {
text: qsTr("Enable noise reduction");
checked: Audio.noiseReduction;
onClicked: {
@ -73,7 +74,7 @@ Rectangle {
checked = Qt.binding(function() { return Audio.noiseReduction; }); // restore binding
}
}
Audio.CheckBox {
AudioControls.CheckBox {
text: qsTr("Show audio level meter");
checked: AvatarInputs.showAudioTools;
onClicked: {
@ -110,7 +111,7 @@ Rectangle {
delegate: Item {
width: parent.width;
height: 36;
Audio.CheckBox {
AudioControls.CheckBox {
text: display;
checked: selected;
onClicked: {
@ -124,17 +125,23 @@ Rectangle {
Separator {}
RowLayout {
HiFiGlyphs {
text: hifi.glyphs.unmuted;
color: hifi.colors.primaryHighlight;
anchors.verticalCenter: parent.verticalCenter;
size: 36;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
size: 16;
color: hifi.colors.lightGrayText;
text: qsTr("CHOOSE OUTPUT DEVICE");
Column {
RowLayout {
HiFiGlyphs {
text: hifi.glyphs.unmuted;
color: hifi.colors.primaryHighlight;
anchors.verticalCenter: parent.verticalCenter;
size: 36;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
size: 16;
color: hifi.colors.lightGrayText;
text: qsTr("CHOOSE OUTPUT DEVICE");
}
}
PlaySampleSound { anchors { left: parent.left; leftMargin: 60 }}
}
}
@ -148,7 +155,7 @@ Rectangle {
delegate: Item {
width: parent.width;
height: 36;
Audio.CheckBox {
AudioControls.CheckBox {
text: display;
checked: selected;
onClicked: {

View file

@ -0,0 +1,223 @@
//
// MicBar.qml
// qml/hifi/audio
//
// Created by Zach Pomerantz on 6/14/2017
// Copyright 2017 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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
Rectangle {
readonly property var level: Audio.inputLevel;
property bool standalone: false;
property var dragTarget: null;
width: 240;
height: 50;
radius: 5;
color: "#00000000";
border {
width: (standalone || Audio.muted || mouseArea.containsMouse) ? 2 : 0;
color: colors.border;
}
// borders are painted over fill, so reduce the fill to fit inside the border
Rectangle {
color: standalone ? colors.fill : "#00000000";
width: 236;
height: 46;
radius: 5;
anchors {
verticalCenter: parent.verticalCenter;
horizontalCenter: parent.horizontalCenter;
}
}
MouseArea {
id: mouseArea;
anchors {
left: icon.left;
right: bar.right;
top: icon.top;
bottom: icon.bottom;
}
hoverEnabled: true;
scrollGestureEnabled: false;
onClicked: { Audio.muted = !Audio.muted; }
drag.target: dragTarget;
}
Item {
id: colors;
readonly property string unmuted: "#FFF";
readonly property string muted: "#E2334D";
readonly property string gutter: "#575757";
readonly property string greenStart: "#39A38F";
readonly property string greenEnd: "#1FC6A6";
readonly property string red: colors.muted;
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: (Audio.muted && !mouseArea.containsMouse) ? muted : unmuted;
}
Item {
id: icon;
anchors {
left: parent.left;
leftMargin: 5;
verticalCenter: parent.verticalCenter;
}
width: 40;
height: 40;
Item {
Image {
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
function exclusiveOr(a, b) { return (a || b) && !(a && b); }
id: image;
source: exclusiveOr(Audio.muted, mouseArea.containsMouse) ? mutedIcon : unmutedIcon;
width: 30;
height: 30;
anchors {
left: parent.left;
leftMargin: 5;
top: parent.top;
topMargin: 5;
}
}
ColorOverlay {
anchors { fill: image }
source: image;
color: colors.icon;
}
}
}
Item {
id: status;
readonly property string color: (Audio.muted && !mouseArea.containsMouse) ? colors.muted : colors.unmuted;
visible: Audio.muted || mouseArea.containsMouse;
anchors {
left: parent.left;
leftMargin: 50;
verticalCenter: parent.verticalCenter;
}
width: 170;
height: 8
Text {
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: parent.color;
text: Audio.muted ? (mouseArea.containsMouse ? "UNMUTE" : "MUTED") : "MUTE";
font.pointSize: 12;
}
Rectangle {
anchors {
left: parent.left;
verticalCenter: parent.verticalCenter;
}
width: 50;
height: 4;
color: parent.color;
}
Rectangle {
anchors {
right: parent.right;
verticalCenter: parent.verticalCenter;
}
width: 50;
height: 4;
color: parent.color;
}
}
Item {
id: bar;
visible: !status.visible;
anchors.fill: status;
width: status.width;
Rectangle { // base
radius: 4;
anchors { fill: parent }
color: colors.gutter;
}
Rectangle { // mask
id: mask;
width: parent.width * level;
radius: 5;
anchors {
bottom: parent.bottom;
bottomMargin: 0;
top: parent.top;
topMargin: 0;
left: parent.left;
leftMargin: 0;
}
}
LinearGradient {
anchors { fill: mask }
source: mask
start: Qt.point(0, 0);
end: Qt.point(170, 0);
gradient: Gradient {
GradientStop {
position: 0;
color: colors.greenStart;
}
GradientStop {
position: 0.8;
color: colors.greenEnd;
}
GradientStop {
position: 0.81;
color: colors.red;
}
GradientStop {
position: 1;
color: colors.red;
}
}
}
}
}

View file

@ -0,0 +1,86 @@
//
// PlaySampleSound.qml
// qml/hifi/audio
//
// Created by Zach Pomerantz on 6/13/2017
// Copyright 2017 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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../../controls-uit" as HifiControls
RowLayout {
property var sound: null;
property var sample: null;
property bool isPlaying: false;
function createSampleSound() {
var SOUND = Qt.resolvedUrl("../../../sounds/sample.wav");
sound = SoundCache.getSound(SOUND);
sample = null;
}
function playSound() {
// FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap
// FIXME: Audio.playSystemSound should not require position
sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition);
isPlaying = true;
sample.finished.connect(function() { isPlaying = false; sample = null; });
}
function stopSound() {
sample && sample.stop();
}
Component.onCompleted: createSampleSound();
Component.onDestruction: stopSound();
onVisibleChanged: {
if (!visible) {
stopSound();
}
}
HifiConstants { id: hifi; }
Button {
style: ButtonStyle {
background: Rectangle {
implicitWidth: 20;
implicitHeight: 20;
radius: hifi.buttons.radius;
gradient: Gradient {
GradientStop {
position: 0.2;
color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black];
}
GradientStop {
position: 1.0;
color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black];
}
}
}
label: HiFiGlyphs {
// absolutely position due to asymmetry in glyph
x: isPlaying ? 0 : 1;
y: 1;
size: 14;
color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white";
text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play;
}
}
onClicked: isPlaying ? stopSound() : playSound();
}
RalewayRegular {
Layout.leftMargin: 2;
size: 14;
color: "white";
text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound");
}
}

View file

@ -1,20 +1,16 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "../../styles-uit"
import "../audio" as HifiAudio
Item {
id: tablet
objectName: "tablet"
property double micLevel: 0.8
property int rowIndex: 0
property int columnIndex: 0
property int count: (flowMain.children.length - 1)
// called by C++ code to keep audio bar updated
function setMicLevel(newMicLevel) {
tablet.micLevel = newMicLevel;
}
// used to look up a button by its uuid
function findButtonIndex(uuid) {
if (!uuid) {
@ -83,6 +79,16 @@ Item {
Rectangle {
id: bgTopBar
height: 90
anchors {
top: parent.top
topMargin: 0
left: parent.left
leftMargin: 0
right: parent.right
rightMargin: 0
}
gradient: Gradient {
GradientStop {
position: 0
@ -94,108 +100,13 @@ Item {
color: "#1e1e1e"
}
}
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
anchors.topMargin: 0
anchors.top: parent.top
Item {
id: audioIcon
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 40
anchors.left: parent.left
anchors.leftMargin: 5
Image {
id: micIcon
source: "../../../icons/tablet-icons/mic.svg"
}
Item {
visible: (Audio.muted && !toggleMuteMouseArea.containsMouse)
|| (!Audio.muted && toggleMuteMouseArea.containsMouse)
Image {
id: muteIcon
source: "../../../icons/tablet-icons/mic-mute.svg"
}
ColorOverlay {
anchors.fill: muteIcon
source: muteIcon
color: toggleMuteMouseArea.containsMouse ? "#a0a0a0" : "#ff0000"
}
}
}
Item {
id: audioBar
width: 170
height: 10
anchors.left: parent.left
anchors.leftMargin: 50
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: audioBarBase
color: "#333333"
radius: 5
anchors.fill: parent
}
Rectangle {
id: audioBarMask
width: parent.width * tablet.micLevel
color: "#333333"
radius: 5
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
anchors.left: parent.left
anchors.leftMargin: 0
}
LinearGradient {
anchors.fill: audioBarMask
source: audioBarMask
start: Qt.point(0, 0)
end: Qt.point(170, 0)
gradient: Gradient {
GradientStop {
position: 0
color: "#2c8e72"
}
GradientStop {
position: 0.8
color: "#1fc6a6"
}
GradientStop {
position: 0.81
color: "#ea4c5f"
}
GradientStop {
position: 1
color: "#ea4c5f"
}
}
}
}
MouseArea {
id: toggleMuteMouseArea
HifiAudio.MicBar {
anchors {
left: audioIcon.left
right: audioBar.right
top: audioIcon.top
bottom: audioIcon.bottom
left: parent.left
leftMargin: 30
verticalCenter: parent.verticalCenter
}
hoverEnabled: true
preventStealing: true
propagateComposedEvents: false
scrollGestureEnabled: false
onClicked: { Audio.muted = !Audio.muted }
}
RalewaySemiBold {
@ -254,27 +165,6 @@ Item {
}
}
states: [
State {
name: "muted state"
PropertyChanges {
target: muteText
text: "UNMUTE"
}
PropertyChanges {
target: muteIcon
visible: !Audio.muted
}
PropertyChanges {
target: tablet
micLevel: 0
}
}
]
function setCurrentItemState(state) {
var index = rowIndex + columnIndex;

View file

@ -334,5 +334,7 @@ Item {
readonly property string vol_x_3: "\ue016"
readonly property string vol_x_4: "\ue017"
readonly property string source: "\ue01c"
readonly property string playback_play: "\ue01d"
readonly property string stop_square: "\ue01e"
}
}

Binary file not shown.

View file

@ -117,6 +117,7 @@
#include <RenderDeferredTask.h>
#include <RenderForwardTask.h>
#include <RenderViewTask.h>
#include <SecondaryCamera.h>
#include <ResourceCache.h>
#include <ResourceRequest.h>
#include <SandboxUtils.h>
@ -1876,6 +1877,7 @@ void Application::initializeGL() {
render::CullFunctor cullFunctor = LODManager::shouldRender;
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD);
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraFrame", cullFunctor);
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, isDeferred);
_renderEngine->load();
_renderEngine->registerScene(_main3DScene);
@ -5571,6 +5573,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
connect(scriptEngine, &ScriptEngine::errorMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onErrorMessage);
connect(scriptEngine, &ScriptEngine::warningMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onWarningMessage);
connect(scriptEngine, &ScriptEngine::infoMessage, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onInfoMessage);
connect(scriptEngine, &ScriptEngine::clearDebugWindow, DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onClearDebugWindow);
}
bool Application::canAcceptURL(const QString& urlString) const {

View file

@ -0,0 +1,125 @@
//
// SecondaryCamera.cpp
// interface/src
//
// Created by Samuel Gateau, Howard Stearns, and Zach Fox on 2017-06-08.
// 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 "SecondaryCamera.h"
#include <TextureCache.h>
#include <gpu/Context.h>
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());
if (!isDeferred) {
task.addJob<RenderForwardTask>("Forward", items);
} else {
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
}
}
void SecondaryCameraRenderTaskConfig::resetSize(int width, int height) { // FIXME: Add an arg here for "destinationFramebuffer"
bool wasEnabled = isEnabled();
setEnabled(false);
auto textureCache = DependencyManager::get<TextureCache>();
textureCache->resetSpectatorCameraFramebuffer(width, height); // FIXME: Call the correct reset function based on the "destinationFramebuffer" arg
setEnabled(wasEnabled);
}
void SecondaryCameraRenderTaskConfig::resetSizeSpectatorCamera(int width, int height) { // Carefully adjust the framebuffer / texture.
resetSize(width, height);
}
class BeginSecondaryCameraFrame { // Changes renderContext for our framebuffer and and view.
glm::vec3 _position{};
glm::quat _orientation{};
float _vFoV{};
float _nearClipPlaneDistance{};
float _farClipPlaneDistance{};
public:
using Config = BeginSecondaryCameraFrameConfig;
using JobModel = render::Job::ModelO<BeginSecondaryCameraFrame, RenderArgsPointer, Config>;
BeginSecondaryCameraFrame() {
_cachedArgsPointer = std::make_shared<RenderArgs>(_cachedArgs);
}
void configure(const Config& config) {
if (config.enabled || config.alwaysEnabled) {
_position = config.position;
_orientation = config.orientation;
_vFoV = config.vFoV;
_nearClipPlaneDistance = config.nearClipPlaneDistance;
_farClipPlaneDistance = config.farClipPlaneDistance;
}
}
void run(const render::RenderContextPointer& renderContext, RenderArgsPointer& cachedArgs) {
auto args = renderContext->args;
auto textureCache = DependencyManager::get<TextureCache>();
gpu::FramebufferPointer destFramebuffer;
destFramebuffer = textureCache->getSpectatorCameraFramebuffer(); // FIXME: Change the destination based on some unimplemented config var
if (destFramebuffer) {
_cachedArgsPointer->_blitFramebuffer = args->_blitFramebuffer;
_cachedArgsPointer->_viewport = args->_viewport;
_cachedArgsPointer->_displayMode = args->_displayMode;
_cachedArgsPointer->_renderMode = args->_renderMode;
args->_blitFramebuffer = destFramebuffer;
args->_viewport = glm::ivec4(0, 0, destFramebuffer->getWidth(), destFramebuffer->getHeight());
args->_displayMode = RenderArgs::MONO;
args->_renderMode = RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.disableContextStereo();
});
auto srcViewFrustum = args->getViewFrustum();
srcViewFrustum.setPosition(_position);
srcViewFrustum.setOrientation(_orientation);
srcViewFrustum.setProjection(glm::perspective(glm::radians(_vFoV), ((float)args->_viewport.z / (float)args->_viewport.w), _nearClipPlaneDistance, _farClipPlaneDistance));
// Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,
// which is not what we want here.
srcViewFrustum.calculate();
args->pushViewFrustum(srcViewFrustum);
cachedArgs = _cachedArgsPointer;
}
}
protected:
RenderArgs _cachedArgs;
RenderArgsPointer _cachedArgsPointer;
};
class EndSecondaryCameraFrame { // Restores renderContext.
public:
using JobModel = render::Job::ModelI<EndSecondaryCameraFrame, RenderArgsPointer>;
void run(const render::RenderContextPointer& renderContext, const RenderArgsPointer& cachedArgs) {
auto args = renderContext->args;
args->_blitFramebuffer = cachedArgs->_blitFramebuffer;
args->_viewport = cachedArgs->_viewport;
args->popViewFrustum();
args->_displayMode = cachedArgs->_displayMode;
args->_renderMode = cachedArgs->_renderMode;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
batch.restoreContextStereo();
});
}
};
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) {
const auto cachedArg = task.addJob<BeginSecondaryCameraFrame>("BeginSecondaryCamera");
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
assert(items.canCast<RenderFetchCullSortTask::Output>());
task.addJob<RenderDeferredTask>("RenderDeferredTask", items);
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
}

View file

@ -0,0 +1,70 @@
//
// SecondaryCamera.h
// interface/src
//
// Created by Samuel Gateau, Howard Stearns, and Zach Fox on 2017-06-08.
// 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
//
#pragma once
#ifndef hifi_SecondaryCamera_h
#define hifi_SecondaryCamera_h
#include <RenderShadowTask.h>
#include <render/RenderFetchCullSortTask.h>
#include <RenderDeferredTask.h>
#include <RenderForwardTask.h>
class MainRenderTask {
public:
using JobModel = render::Task::Model<MainRenderTask>;
MainRenderTask() {}
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true);
};
class BeginSecondaryCameraFrameConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript.
Q_OBJECT
Q_PROPERTY(glm::vec3 position MEMBER position NOTIFY dirty) // of viewpoint to render from
Q_PROPERTY(glm::quat orientation MEMBER orientation NOTIFY dirty) // of viewpoint to render from
Q_PROPERTY(float vFoV MEMBER vFoV NOTIFY dirty) // Secondary camera's vertical field of view. In degrees.
Q_PROPERTY(float nearClipPlaneDistance MEMBER nearClipPlaneDistance NOTIFY dirty) // Secondary camera's near clip plane distance. In meters.
Q_PROPERTY(float farClipPlaneDistance MEMBER farClipPlaneDistance NOTIFY dirty) // Secondary camera's far clip plane distance. In meters.
public:
glm::vec3 position{};
glm::quat orientation{};
float vFoV{ 45.0f };
float nearClipPlaneDistance{ 0.1f };
float farClipPlaneDistance{ 100.0f };
BeginSecondaryCameraFrameConfig() : render::Task::Config(false) {}
signals:
void dirty();
};
class SecondaryCameraRenderTaskConfig : public render::Task::Config {
Q_OBJECT
public:
SecondaryCameraRenderTaskConfig() : render::Task::Config(false) {}
private:
void resetSize(int width, int height);
signals:
void dirty();
public slots:
void resetSizeSpectatorCamera(int width, int height);
};
class SecondaryCameraRenderTask {
public:
using Config = SecondaryCameraRenderTaskConfig;
using JobModel = render::Task::Model<SecondaryCameraRenderTask, Config>;
SecondaryCameraRenderTask() {}
void configure(const Config& config) {}
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor);
};
#endif

View file

@ -1881,15 +1881,14 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f;
bool MyAvatar::cameraInsideHead() const {
const glm::vec3 cameraPosition = qApp->getCamera().getPosition();
bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const {
return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
}
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
bool defaultMode = renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE;
bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON;
bool insideHead = cameraInsideHead();
bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition());
return !defaultMode || !firstPerson || !insideHead;
}

View file

@ -56,6 +56,7 @@ class MyAvatar : public Avatar {
*
* @namespace MyAvatar
* @augments Avatar
* @property qmlPosition {Vec3} Used as a stopgap for position access by QML, as glm::vec3 is unavailable outside of scripts
* @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface,
* and false if you would not like to see MyAvatar in your local interface.
* @property motorVelocity {Vec3} Can be used to move the avatar with this velocity.
@ -101,6 +102,10 @@ class MyAvatar : public Avatar {
* "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js".
*/
// FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
QVector3D getQmlPosition() { auto p = getPosition(); return QVector3D(p.x, p.y, p.z); }
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
@ -615,7 +620,7 @@ private:
float scale = 1.0f, bool isSoft = false,
bool allowDuplicates = false, bool useSaved = true) override;
bool cameraInsideHead() const;
bool cameraInsideHead(const glm::vec3& cameraPosition) const;
void updateEyeContactTarget(float deltaTime);

View file

@ -23,9 +23,33 @@ QString Audio::HMD { "VR" };
Setting::Handle<bool> enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
float Audio::loudnessToLevel(float loudness) {
const float LOG2 = log(2.0f);
const float METER_LOUDNESS_SCALE = 2.8f / 5.0f;
const float LOG2_LOUDNESS_FLOOR = 11.0f;
float level = 0.0f;
loudness += 1.0f;
float log2loudness = logf(loudness) / LOG2;
if (log2loudness <= LOG2_LOUDNESS_FLOOR) {
level = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE;
} else {
level = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.0f)) * METER_LOUDNESS_SCALE;
}
if (level > 1.0f) {
level = 1.0;
}
return level;
}
Audio::Audio() : _devices(_contextIsHMD) {
auto client = DependencyManager::get<AudioClient>();
connect(client.data(), &AudioClient::muteToggled, this, &Audio::onMutedChanged);
auto client = DependencyManager::get<AudioClient>().data();
connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged);
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
connect(&_devices._inputs, &AudioDeviceList::deviceChanged, this, &Audio::onInputChanged);
enableNoiseReduction(enableNoiseReductionSetting.get());
@ -88,6 +112,15 @@ void Audio::onInputChanged() {
}
}
void Audio::onInputLoudnessChanged(float loudness) {
float level = loudnessToLevel(loudness);
if (_inputLevel != level) {
_inputLevel = level;
emit inputLevelChanged(_inputLevel);
}
}
QString Audio::getContext() const {
return _contextIsHMD ? Audio::HMD : Audio::DESKTOP;
}

View file

@ -26,6 +26,7 @@ class Audio : public AudioScriptingInterface {
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
Q_PROPERTY(QString context READ getContext NOTIFY contextChanged)
Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop)
@ -34,11 +35,14 @@ public:
static QString HMD;
static QString DESKTOP;
static float loudnessToLevel(float loudness);
virtual ~Audio() {}
bool isMuted() const { return _isMuted; }
bool noiseReductionEnabled() const { return _enableNoiseReduction; }
float getInputVolume() const { return _inputVolume; }
float getInputLevel() const { return _inputLevel; }
QString getContext() const;
void setMuted(bool muted);
@ -54,12 +58,14 @@ signals:
void mutedChanged(bool isMuted);
void noiseReductionChanged(bool isEnabled);
void inputVolumeChanged(float volume);
void inputLevelChanged(float level);
void contextChanged(const QString& context);
public slots:
void onMutedChanged();
void onContextChanged();
void onInputChanged();
void onInputLoudnessChanged(float loudness);
protected:
// Audio must live on a separate thread from AudioClient to avoid deadlocks
@ -68,6 +74,7 @@ protected:
private:
float _inputVolume { 1.0f };
float _inputLevel { 0.0f };
bool _isMuted { false };
bool _enableNoiseReduction;
bool _contextIsHMD { false };

View file

@ -284,6 +284,11 @@ void WindowScriptingInterface::copyToClipboard(const QString& text) {
QApplication::clipboard()->setText(text);
}
bool WindowScriptingInterface::setDisplayTexture(const QString& name) {
return qApp->getActiveDisplayPlugin()->setDisplayTexture(name); // Plugins that don't know how, answer false.
}
void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) {
qApp->takeSnapshot(notify, includeAnimated, aspectRatio);
}

View file

@ -62,6 +62,7 @@ public slots:
void displayAnnouncement(const QString& message);
void shareSnapshot(const QString& path, const QUrl& href = QUrl(""));
bool isPhysicsEnabled();
bool setDisplayTexture(const QString& name);
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton);

View file

@ -45,15 +45,6 @@ AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) {
} \
}
#define AI_UPDATE_FLOAT(name, src, epsilon) \
{ \
float val = src; \
if (fabsf(_##name - val) >= epsilon) { \
_##name = val; \
emit name##Changed(); \
} \
}
float AvatarInputs::loudnessToAudioLevel(float loudness) {
const float AUDIO_METER_AVERAGING = 0.5;
const float LOG2 = log(2.0f);
@ -85,27 +76,6 @@ void AvatarInputs::update() {
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
AI_UPDATE(isHMD, qApp->isHMDMode());
auto audioIO = DependencyManager::get<AudioClient>();
const float audioLevel = loudnessToAudioLevel(DependencyManager::get<AudioClient>()->getLastInputLoudness());
AI_UPDATE_FLOAT(audioLevel, audioLevel, 0.01f);
AI_UPDATE(audioClipping, ((audioIO->getTimeSinceLastClip() > 0.0f) && (audioIO->getTimeSinceLastClip() < 1.0f)));
AI_UPDATE(audioMuted, audioIO->isMuted());
//// Make muted icon pulsate
//static const float PULSE_MIN = 0.4f;
//static const float PULSE_MAX = 1.0f;
//static const float PULSE_FREQUENCY = 1.0f; // in Hz
//qint64 now = usecTimestampNow();
//if (now - _iconPulseTimeReference > (qint64)USECS_PER_SECOND) {
// // Prevents t from getting too big, which would diminish glm::cos precision
// _iconPulseTimeReference = now - ((now - _iconPulseTimeReference) % USECS_PER_SECOND);
//}
//float t = (float)(now - _iconPulseTimeReference) / (float)USECS_PER_SECOND;
//float pulseFactor = (glm::cos(t * PULSE_FREQUENCY * 2.0f * PI) + 1.0f) / 2.0f;
//iconColor = PULSE_MIN + (PULSE_MAX - PULSE_MIN) * pulseFactor;
}
void AvatarInputs::setShowAudioTools(bool showAudioTools) {
@ -124,10 +94,6 @@ void AvatarInputs::toggleCameraMute() {
}
}
void AvatarInputs::toggleAudioMute() {
DependencyManager::get<AudioClient>()->toggleMute();
}
void AvatarInputs::resetSensors() {
qApp->resetSensors();
}

View file

@ -25,9 +25,6 @@ class AvatarInputs : public QQuickItem {
AI_PROPERTY(bool, cameraEnabled, false)
AI_PROPERTY(bool, cameraMuted, false)
AI_PROPERTY(bool, audioMuted, false)
AI_PROPERTY(bool, audioClipping, false)
AI_PROPERTY(float, audioLevel, 0)
AI_PROPERTY(bool, isHMD, false)
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
@ -45,16 +42,12 @@ public slots:
signals:
void cameraEnabledChanged();
void cameraMutedChanged();
void audioMutedChanged();
void audioClippingChanged();
void audioLevelChanged();
void isHMDChanged();
void showAudioToolsChanged(bool show);
protected:
Q_INVOKABLE void resetSensors();
Q_INVOKABLE void toggleCameraMute();
Q_INVOKABLE void toggleAudioMute();
private:
float _trailingAudioLoudness{ 0 };

View file

@ -52,6 +52,7 @@
#include "scripting/GlobalServicesScriptingInterface.h"
#include <plugins/InputConfiguration.h>
#include "ui/Snapshot.h"
#include "SoundCache.h"
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@ -202,6 +203,7 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getSurfaceContext()->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
_webSurface->getSurfaceContext()->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
_webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());

View file

@ -1023,6 +1023,8 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
emit inputReceived(audioBuffer);
}
emit inputLoudnessChanged(_lastInputLoudness);
// state machine to detect gate opening and closing
bool audioGateOpen = (_lastInputLoudness != 0.0f);
bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened

View file

@ -210,6 +210,7 @@ signals:
bool muteToggled();
void mutedByMixer();
void inputReceived(const QByteArray& inputSamples);
void inputLoudnessChanged(float loudness);
void outputBytesToNetwork(int numBytes);
void inputBytesFromNetwork(int numBytes);
void noiseGateOpened();

View file

@ -288,6 +288,13 @@ void Avatar::updateAvatarEntities() {
properties.setScript(noScript);
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
@ -1067,15 +1074,15 @@ void Avatar::setModelURLFinished(bool success) {
const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts
if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
_skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) {
qCWarning(avatars_renderer) << "Using default after failing to load Avatar model: " << _skeletonModelURL
qCWarning(avatars_renderer) << "Using default after failing to load Avatar model: " << _skeletonModelURL
<< "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts.";
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
} else {
qCWarning(avatars_renderer) << "Avatar model: " << _skeletonModelURL
<< "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts()
qCWarning(avatars_renderer) << "Avatar model: " << _skeletonModelURL
<< "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts()
<< "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS;
}
}

View file

@ -496,6 +496,17 @@ void OpenGLDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
_newFrameQueue.push(newFrame);
});
}
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor) {
batch.enableStereo(false);
batch.resetViewTransform();
batch.setFramebuffer(gpu::FramebufferPointer());
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
batch.setStateScissorRect(scissor);
batch.setViewportTransform(viewport);
batch.setResourceTexture(0, texture);
batch.setPipeline(_presentPipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
}
void OpenGLDisplayPlugin::updateFrameData() {
PROFILE_RANGE(render, __FUNCTION__)
@ -605,14 +616,11 @@ void OpenGLDisplayPlugin::compositeLayers() {
void OpenGLDisplayPlugin::internalPresent() {
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.resetViewTransform();
batch.setFramebuffer(gpu::FramebufferPointer());
batch.setViewportTransform(ivec4(uvec2(0), getSurfacePixels()));
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
batch.setPipeline(_presentPipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
// Note: _displayTexture must currently be the same size as the display.
uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels();
auto viewport = ivec4(uvec2(0), dims);
renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport);
});
swapBuffers();
_presentRate.increment();
}
@ -694,6 +702,22 @@ void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const {
_container->makeRenderingContextCurrent();
}
bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
// Note: it is the caller's responsibility to keep the network texture in cache.
if (name.isEmpty()) {
_displayTexture.reset();
onDisplayTextureReset();
return true;
}
auto textureCache = DependencyManager::get<TextureCache>();
auto displayNetworkTexture = textureCache->getTexture(name);
if (!displayNetworkTexture) {
return false;
}
_displayTexture = displayNetworkTexture->getGPUTexture();
return !!_displayTexture;
}
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
auto size = _compositeFramebuffer->getSize();
if (isHmd()) {

View file

@ -57,6 +57,8 @@ public:
return getSurfaceSize();
}
virtual bool setDisplayTexture(const QString& name) override;
virtual bool onDisplayTextureReset() { return false; };
QImage getScreenshot(float aspectRatio = 0.0f) const override;
float presentRate() const override;
@ -109,6 +111,7 @@ protected:
// Plugin specific functionality to send the composed scene to the output window or device
virtual void internalPresent();
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
virtual void updateFrameData();
void withMainThreadContext(std::function<void()> f) const;
@ -134,6 +137,7 @@ protected:
gpu::PipelinePointer _simplePipeline;
gpu::PipelinePointer _presentPipeline;
gpu::PipelinePointer _cursorPipeline;
gpu::TexturePointer _displayTexture{};
float _compositeOverlayAlpha { 1.0f };
struct CursorData {

View file

@ -27,6 +27,7 @@
#include <gpu/StandardShaderLib.h>
#include <gpu/gl/GLBackend.h>
#include <TextureCache.h>
#include <PathUtils.h>
#include "../Logging.h"
@ -211,7 +212,15 @@ void HmdDisplayPlugin::internalPresent() {
// Composite together the scene, overlay and mouse cursor
hmdPresent();
if (!_disablePreview) {
if (_displayTexture) {
// Note: _displayTexture must currently be the same size as the display.
uvec2 dims = uvec2(_displayTexture->getDimensions());
auto viewport = ivec4(uvec2(0), dims);
render([&](gpu::Batch& batch) {
renderFromTexture(batch, _displayTexture, viewport, viewport);
});
swapBuffers();
} else if (!_disablePreview) {
// screen preview mirroring
auto sourceSize = _renderTargetSize;
if (_monoPreview) {
@ -278,16 +287,7 @@ void HmdDisplayPlugin::internalPresent() {
viewport.z *= 2;
}
batch.enableStereo(false);
batch.resetViewTransform();
batch.setFramebuffer(gpu::FramebufferPointer());
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
batch.setStateScissorRect(scissor); // was viewport
batch.setViewportTransform(viewport);
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
batch.setPipeline(_presentPipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor);
});
swapBuffers();
} else if (_clearPreviewFlag) {
@ -316,15 +316,7 @@ void HmdDisplayPlugin::internalPresent() {
auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions()));
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.resetViewTransform();
batch.setFramebuffer(gpu::FramebufferPointer());
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
batch.setStateScissorRect(viewport);
batch.setViewportTransform(viewport);
batch.setResourceTexture(0, _previewTexture);
batch.setPipeline(_presentPipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
renderFromTexture(batch, _previewTexture, viewport, viewport);
});
_clearPreviewFlag = false;
swapBuffers();

View file

@ -46,6 +46,8 @@ public:
float stutterRate() const override;
virtual bool onDisplayTextureReset() override { _clearPreviewFlag = true; return true; };
protected:
virtual void hmdPresent() = 0;
virtual bool isHmdMounted() const = 0;

View file

@ -386,7 +386,13 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
return 0;
}
qint64 clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
int64_t clockSkew = 0;
uint64_t maxPingRoundTrip = 33333; // two frames periods at 60 fps
if (args.sourceNode) {
clockSkew = args.sourceNode->getClockSkewUsec();
const float MSECS_PER_USEC = 1000;
maxPingRoundTrip += args.sourceNode->getPingMs() * MSECS_PER_USEC;
}
BufferParser parser(data, bytesLeftToRead);
@ -653,7 +659,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
const QUuid& myNodeID = nodeList->getSessionUUID();
bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
// pack SimulationOwner and terse update properties near each other
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
// even when we would otherwise ignore the rest of the packet.
@ -678,7 +683,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
if (newSimOwner.getID().isNull() && !_simulationOwner.pendingRelease(lastEditedFromBufferAdjusted)) {
// entity-server is trying to clear our ownership (probably at our own request)
// but we actually want to own it, therefore we ignore this clear event
// and pretend that we own it (we assume we'll recover it soon)
// and pretend that we own it (e.g. we assume we'll receive ownership soon)
// However, for now, when the server uses a newer time than what we sent, listen to what we're told.
if (overwriteLocalData) {
@ -690,16 +695,19 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
// recompute weOwnSimulation for later
weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
}
} else if (newSimOwner.getID().isNull() && _simulationOwner.pendingTake(lastEditedFromBufferAdjusted)) {
// entity-server is trying to clear someone else's ownership
// but we want to own it, therefore we ignore this clear event
// and pretend that we own it (we assume we'll get it soon)
} else if (_simulationOwner.pendingTake(now - maxPingRoundTrip)) {
// we sent a bid before this packet could have been sent from the server
// so we ignore it and pretend we own the object's simulation
weOwnSimulation = true;
if (!_simulationOwner.isNull()) {
// someone else really did own it
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
_simulationOwner.clearCurrentOwner();
if (newSimOwner.getID().isNull()) {
// entity-server is trying to clear someone else's ownership
// but we want to own it, therefore we ignore this clear event
if (!_simulationOwner.isNull()) {
// someone else really did own it
markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID);
somethingChanged = true;
_simulationOwner.clearCurrentOwner();
}
}
} else if (newSimOwner.matchesValidID(myNodeID) && !_hasBidOnSimulation) {
// entity-server tells us that we have simulation ownership while we never requested this for this EntityItem,

View file

@ -115,6 +115,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
(&::gpu::gl::GLBackend::do_resetStages),
(&::gpu::gl::GLBackend::do_disableContextStereo),
(&::gpu::gl::GLBackend::do_restoreContextStereo),
(&::gpu::gl::GLBackend::do_runLambda),
(&::gpu::gl::GLBackend::do_startNamedCall),
@ -224,6 +227,14 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
_transform.preUpdate(_commandIndex, _stereo);
break;
case Batch::COMMAND_disableContextStereo:
_stereo._contextDisable = true;
break;
case Batch::COMMAND_restoreContextStereo:
_stereo._contextDisable = false;
break;
case Batch::COMMAND_setViewportTransform:
case Batch::COMMAND_setViewTransform:
case Batch::COMMAND_setProjectionTransform: {
@ -308,16 +319,16 @@ void GLBackend::render(const Batch& batch) {
}
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
if (_stereo._enable) {
if (_stereo.isStereo()) {
glEnable(GL_CLIP_DISTANCE0);
}
#endif
{
PROFILE_RANGE(render_gpu_gl_detail, _stereo._enable ? "Render Stereo" : "Render");
PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render");
renderPassDraw(batch);
}
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
if (_stereo._enable) {
if (_stereo.isStereo()) {
glDisable(GL_CLIP_DISTANCE0);
}
#endif
@ -358,6 +369,15 @@ void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) {
resetStages();
}
void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) {
}
void GLBackend::do_restoreContextStereo(const Batch& batch, size_t paramOffset) {
}
void GLBackend::do_runLambda(const Batch& batch, size_t paramOffset) {
std::function<void()> f = batch._lambdas.get(batch._params[paramOffset]._uint);
f();

View file

@ -143,6 +143,9 @@ public:
// Reset stages
virtual void do_resetStages(const Batch& batch, size_t paramOffset) final;
virtual void do_disableContextStereo(const Batch& batch, size_t paramOffset) final;
virtual void do_restoreContextStereo(const Batch& batch, size_t paramOffset) final;
virtual void do_runLambda(const Batch& batch, size_t paramOffset) final;
virtual void do_startNamedCall(const Batch& batch, size_t paramOffset) final;

View file

@ -48,7 +48,7 @@ void GLBackend::do_setFramebuffer(const Batch& batch, size_t paramOffset) {
}
void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
if (_stereo._enable && !_pipeline._stateCache.scissorEnable) {
if (_stereo.isStereo() && !_pipeline._stateCache.scissorEnable) {
qWarning("Clear without scissor in stereo mode");
}

View file

@ -322,7 +322,7 @@ void GLBackend::do_setStateScissorRect(const Batch& batch, size_t paramOffset) {
Vec4i rect;
memcpy(&rect, batch.readData(batch._params[paramOffset]._uint), sizeof(Vec4i));
if (_stereo._enable) {
if (_stereo.isStereo()) {
rect.z /= 2;
if (_stereo._pass) {
rect.x += rect.z;

View file

@ -37,7 +37,7 @@ void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset)
glViewport(vp.x, vp.y, vp.z, vp.w);
// Where we assign the GL viewport
if (_stereo._enable) {
if (_stereo.isStereo()) {
vp.z /= 2;
if (_stereo._pass) {
vp.x += vp.z;
@ -119,7 +119,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo
size_t offset = _cameraUboSize * _cameras.size();
_cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset));
if (stereo._enable) {
if (stereo.isStereo()) {
#ifdef GPU_STEREO_CAMERA_BUFFER
_cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view), _camera.getEyeCamera(1, stereo, _view)));
#else
@ -151,7 +151,7 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta
#ifdef GPU_STEREO_CAMERA_BUFFER
bindCurrentCamera(0);
#else
if (!stereo._enable) {
if (!stereo.isStereo()) {
bindCurrentCamera(0);
}
#endif

View file

@ -390,6 +390,15 @@ void Batch::resetStages() {
ADD_COMMAND(resetStages);
}
void Batch::disableContextStereo() {
ADD_COMMAND(disableContextStereo);
}
void Batch::restoreContextStereo() {
ADD_COMMAND(restoreContextStereo);
}
void Batch::runLambda(std::function<void()> f) {
ADD_COMMAND(runLambda);
_params.emplace_back(_lambdas.cache(f));

View file

@ -217,6 +217,9 @@ public:
// Reset the stage caches and states
void resetStages();
void disableContextStereo();
void restoreContextStereo();
// Debugging
void pushProfileRange(const char* name);
void popProfileRange();
@ -301,6 +304,9 @@ public:
COMMAND_resetStages,
COMMAND_disableContextStereo,
COMMAND_restoreContextStereo,
COMMAND_runLambda,
COMMAND_startNamedCall,
@ -467,7 +473,7 @@ public:
NamedBatchDataMap _namedData;
bool _enableStereo{ true };
bool _enableSkybox{ false };
bool _enableSkybox { false };
protected:
friend class Context;

View file

@ -145,7 +145,7 @@ void Context::enableStereo(bool enable) {
}
bool Context::isStereo() {
return _stereo._enable;
return _stereo.isStereo();
}
void Context::setStereoProjections(const mat4 eyeProjections[2]) {

View file

@ -118,7 +118,7 @@ public:
protected:
virtual bool isStereo() {
return _stereo._enable;
return _stereo.isStereo();
}
void getStereoProjections(mat4* eyeProjections) const {

View file

@ -93,7 +93,11 @@ namespace gpu {
using TextureViews = std::vector<TextureView>;
struct StereoState {
bool isStereo() const {
return _enable && !_contextDisable;
}
bool _enable{ false };
bool _contextDisable { false };
bool _skybox{ false };
// 0 for left eye, 1 for right eye
uint8 _pass{ 0 };

View file

@ -16,6 +16,8 @@
using namespace ktx;
int ktxDescriptorMetaTypeId = qRegisterMetaType<KTXDescriptor*>();
const Header::Identifier ktx::Header::IDENTIFIER {{
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
}};

View file

@ -387,4 +387,6 @@ namespace ktx {
}
Q_DECLARE_METATYPE(ktx::KTXDescriptor*);
#endif // hifi_ktx_KTX_h

View file

@ -13,6 +13,8 @@
#include <mutex>
#include <QtConcurrent/QtConcurrentRun>
#include <QCryptographicHash>
#include <QImageReader>
#include <QRunnable>
@ -50,6 +52,9 @@ Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.k
const std::string TextureCache::KTX_DIRNAME { "ktx_cache" };
const std::string TextureCache::KTX_EXT { "ktx" };
static const QString RESOURCE_SCHEME = "resource";
static const QUrl SPECTATOR_CAMERA_FRAME_URL("resource://spectatorCameraFrame");
static const float SKYBOX_LOAD_PRIORITY { 10.0f }; // Make sure skybox loads first
static const float HIGH_MIPS_LOAD_PRIORITY { 9.0f }; // Make sure high mips loads after skybox but before models
@ -180,6 +185,9 @@ ScriptableResource* TextureCache::prefetch(const QUrl& url, int type, int maxNum
}
NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) {
if (url.scheme() == RESOURCE_SCHEME) {
return getResourceTexture(url);
}
TextureExtra extra = { type, content, maxNumPixels };
return ResourceCache::getResource(url, QUrl(), &extra).staticCast<NetworkTexture>();
}
@ -265,6 +273,18 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
return QSharedPointer<Resource>(texture, &Resource::deleter);
}
NetworkTexture::NetworkTexture(const QUrl& url) :
Resource(url),
_type(),
_sourceIsKTX(false),
_maxNumPixels(100)
{
_textureSource = std::make_shared<gpu::TextureSource>();
_lowestRequestedMipLevel = 0;
_loaded = true;
}
NetworkTexture::NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels) :
Resource(url),
_type(type),
@ -303,14 +323,12 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth,
_width = texture->getWidth();
_height = texture->getHeight();
setSize(texture->getStoredSize());
finishedLoading(true);
} else {
// FIXME: If !gpuTexture, we failed to load!
_width = _height = 0;
qWarning() << "Texture did not load";
finishedLoading(false);
}
finishedLoading(true);
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
}
@ -382,8 +400,7 @@ void NetworkTexture::makeRequest() {
emit loading();
connect(_ktxHeaderRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxHeaderRequestProgress);
connect(_ktxHeaderRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxHeaderRequestFinished);
connect(_ktxHeaderRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxInitialDataRequestFinished);
_bytesReceived = _bytesTotal = _bytes = 0;
@ -407,18 +424,18 @@ void NetworkTexture::makeRequest() {
}
void NetworkTexture::startRequestForNextMipLevel() {
if (_lowestKnownPopulatedMip == 0) {
qWarning(networking) << "Requesting next mip level but all have been fulfilled: " << _lowestKnownPopulatedMip
<< " " << _textureSource->getGPUTexture()->minAvailableMipLevel() << " " << _url;
auto self = _self.lock();
if (!self) {
return;
}
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST) {
auto self = _self.lock();
if (!self) {
return;
}
auto texture = _textureSource->getGPUTexture();
if (!texture || _ktxResourceState != WAITING_FOR_MIP_REQUEST) {
return;
}
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
if (_lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
_ktxResourceState = PENDING_MIP_REQUEST;
init(false);
@ -453,6 +470,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
ByteRange range;
range.fromInclusive = -HIGH_MIP_MAX_SIZE;
_ktxMipRequest->setByteRange(range);
connect(_ktxMipRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxInitialDataRequestFinished);
} else {
ByteRange range;
range.fromInclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
@ -460,229 +479,315 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
range.toExclusive = ktx::KTX_HEADER_SIZE + _originalKtxDescriptor->header.bytesOfKeyValueData
+ _originalKtxDescriptor->images[high + 1]._imageOffset;
_ktxMipRequest->setByteRange(range);
}
connect(_ktxMipRequest, &ResourceRequest::progress, this, &NetworkTexture::ktxMipRequestProgress);
connect(_ktxMipRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxMipRequestFinished);
connect(_ktxMipRequest, &ResourceRequest::finished, this, &NetworkTexture::ktxMipRequestFinished);
}
_ktxMipRequest->send();
}
void NetworkTexture::ktxHeaderRequestFinished() {
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
if (!_ktxHeaderRequest) {
// This is called when the header or top mips have been loaded
void NetworkTexture::ktxInitialDataRequestFinished() {
if (!_ktxHeaderRequest || _ktxHeaderRequest->getState() != ResourceRequest::Finished ||
!_ktxMipRequest || _ktxMipRequest->getState() != ResourceRequest::Finished) {
// Wait for both request to be finished
return;
}
_ktxHeaderRequestFinished = true;
maybeHandleFinishedInitialLoad();
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
Q_ASSERT_X(_ktxHeaderRequest && _ktxMipRequest, __FUNCTION__, "Request should not be null while in ktxInitialDataRequestFinished");
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
{ "from_cache", _ktxHeaderRequest->loadedFromCache() },
{ "size_mb", _bytesTotal / 1000000.0 }
});
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
setSize(_bytesTotal);
TextureCache::requestCompleted(_self);
auto result = _ktxHeaderRequest->getResult();
if (result == ResourceRequest::Success) {
result = _ktxMipRequest->getResult();
}
if (result == ResourceRequest::Success) {
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
_ktxHeaderData = _ktxHeaderRequest->getData();
_ktxHighMipData = _ktxMipRequest->getData();
handleFinishedInitialLoad();
} else {
if (handleFailedRequest(result)) {
_ktxResourceState = PENDING_INITIAL_LOAD;
} else {
_ktxResourceState = FAILED_TO_LOAD;
}
}
_ktxHeaderRequest->disconnect(this);
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
_ktxMipRequest->disconnect(this);
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
}
void NetworkTexture::ktxMipRequestFinished() {
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA || _ktxResourceState == REQUESTING_MIP);
Q_ASSERT_X(_ktxMipRequest, __FUNCTION__, "Request should not be null while in ktxMipRequestFinished");
Q_ASSERT(_ktxResourceState == REQUESTING_MIP);
if (!_ktxMipRequest) {
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
{ "from_cache", _ktxMipRequest->loadedFromCache() },
{ "size_mb", _bytesTotal / 1000000.0 }
});
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
setSize(_bytesTotal);
if (!_ktxMipRequest || _ktxMipRequest != sender()) {
// This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted.
qWarning(networking) << "Received signal NetworkTexture::ktxMipRequestFinished from ResourceRequest that is not the current"
<< " request: " << sender() << ", " << _ktxMipRequest;
return;
}
if (_ktxResourceState == LOADING_INITIAL_DATA) {
_ktxHighMipRequestFinished = true;
maybeHandleFinishedInitialLoad();
} else if (_ktxResourceState == REQUESTING_MIP) {
Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
TextureCache::requestCompleted(_self);
TextureCache::requestCompleted(_self);
if (_ktxMipRequest->getResult() == ResourceRequest::Success) {
auto result = _ktxMipRequest->getResult();
if (result == ResourceRequest::Success) {
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
if (_ktxResourceState == REQUESTING_MIP) {
Q_ASSERT(_ktxMipLevelRangeInFlight.first != NULL_MIP_LEVEL);
Q_ASSERT(_ktxMipLevelRangeInFlight.second - _ktxMipLevelRangeInFlight.first == 0);
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
auto self = _self;
auto url = _url;
auto data = _ktxMipRequest->getData();
auto mipLevel = _ktxMipLevelRangeInFlight.first;
auto texture = _textureSource->getGPUTexture();
if (texture) {
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
QtConcurrent::run(QThreadPool::globalInstance(), [self, data, mipLevel, url, texture] {
PROFILE_RANGE_EX(resource_parse_image, "NetworkTexture - Processing Mip Data", 0xffff0000, 0, { { "url", url.toString() } });
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
if (texture->minAvailableMipLevel() <= _ktxMipLevelRangeInFlight.first) {
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
} else {
qWarning(networking) << "Failed to load mip: " << _url << ":" << _ktxMipLevelRangeInFlight.first;
_ktxResourceState = FAILED_TO_LOAD;
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;
}
} else {
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
qWarning(networking) << "Trying to update mips but texture is null";
}
finishedLoading(true);
QThread::currentThread()->setPriority(QThread::LowPriority);
Finally restorePriority([originalPriority] { QThread::currentThread()->setPriority(originalPriority); });
auto resource = self.lock();
if (!resource) {
// Resource no longer exists, bail
return;
}
Q_ASSERT_X(texture, "Async - NetworkTexture::ktxMipRequestFinished", "NetworkTexture should have been assigned a GPU texture by now.");
texture->assignStoredMip(mipLevel, data.size(), reinterpret_cast<const uint8_t*>(data.data()));
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel");
});
} else {
qWarning(networking) << "Mip request finished in an unexpected state: " << _ktxResourceState;
finishedLoading(false);
if (handleFailedRequest(_ktxMipRequest->getResult())) {
_ktxResourceState = PENDING_MIP_REQUEST;
} else {
qWarning(networking) << "Failed to load mip: " << _url;
_ktxResourceState = FAILED_TO_LOAD;
}
}
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
if (_ktxResourceState == WAITING_FOR_MIP_REQUEST && _lowestRequestedMipLevel < _lowestKnownPopulatedMip) {
startRequestForNextMipLevel();
}
} else {
qWarning() << "Mip request finished in an unexpected state: " << _ktxResourceState;
if (handleFailedRequest(result)) {
_ktxResourceState = PENDING_MIP_REQUEST;
} else {
_ktxResourceState = FAILED_TO_LOAD;
}
}
_ktxMipRequest->disconnect(this);
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
}
// This is called when the header or top mips have been loaded
void NetworkTexture::maybeHandleFinishedInitialLoad() {
// This is called when the header and top mips have been loaded
void NetworkTexture::handleFinishedInitialLoad() {
Q_ASSERT(_ktxResourceState == LOADING_INITIAL_DATA);
Q_ASSERT(!_ktxHeaderData.isEmpty() && !_ktxHighMipData.isEmpty());
if (_ktxHeaderRequestFinished && _ktxHighMipRequestFinished) {
// create ktx...
auto ktxHeaderData = _ktxHeaderData;
auto ktxHighMipData = _ktxHighMipData;
_ktxHeaderData.clear();
_ktxHighMipData.clear();
TextureCache::requestCompleted(_self);
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
if (_ktxHeaderRequest->getResult() != ResourceRequest::Success || _ktxMipRequest->getResult() != ResourceRequest::Success) {
if (handleFailedRequest(_ktxMipRequest->getResult())) {
_ktxResourceState = PENDING_INITIAL_LOAD;
}
else {
_ktxResourceState = FAILED_TO_LOAD;
}
auto self = _self;
auto url = _url;
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
QtConcurrent::run(QThreadPool::globalInstance(), [self, ktxHeaderData, ktxHighMipData, url] {
PROFILE_RANGE_EX(resource_parse_image, "NetworkTexture - Processing Initial Data", 0xffff0000, 0, { { "url", url.toString() } });
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
} else {
// create ktx...
auto ktxHeaderData = _ktxHeaderRequest->getData();
auto ktxHighMipData = _ktxMipRequest->getData();
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
if (!ktx::checkIdentifier(header->identifier)) {
qWarning() << "Cannot load " << _url << ", invalid header identifier";
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false);
return;
}
auto kvSize = header->bytesOfKeyValueData;
if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false);
return;
}
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
auto imageDescriptors = header->generateImageDescriptors();
if (imageDescriptors.size() == 0) {
qWarning(networking) << "Failed to process ktx file " << _url;
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false);
}
_originalKtxDescriptor.reset(new ktx::KTXDescriptor(*header, keyValues, imageDescriptors));
// Create bare ktx in memory
auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
});
std::string filename;
std::string hash;
if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
qWarning("Invalid source hash key found, bailing");
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false);
return;
} else {
// at this point the source hash is in binary 16-byte form
// and we need it in a hexadecimal string
auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
hash = filename = binaryHash.toHex().toStdString();
}
auto textureCache = DependencyManager::get<TextureCache>();
gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile);
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
_file = ktxFile;
}
}
}
if (!texture) {
auto memKtx = ktx::KTX::createBare(*header, keyValues);
if (!memKtx) {
qWarning() << " Ktx could not be created, bailing";
finishedLoading(false);
return;
}
// Move ktx to file
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
KTXFilePointer file;
auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
qCWarning(modelnetworking) << _url << " failed to write cache file";
_ktxResourceState = FAILED_TO_LOAD;
finishedLoading(false);
return;
} else {
_file = file;
}
auto newKtxDescriptor = memKtx->toDescriptor();
texture = gpu::Texture::build(newKtxDescriptor);
texture->setKtxBacking(file);
texture->setSource(filename);
auto& images = _originalKtxDescriptor->images;
size_t imageSizeRemaining = ktxHighMipData.size();
uint8_t* ktxData = reinterpret_cast<uint8_t*>(ktxHighMipData.data());
ktxData += ktxHighMipData.size();
// TODO Move image offset calculation to ktx ImageDescriptor
for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
auto& image = images[level];
if (image._imageSize > imageSizeRemaining) {
break;
}
ktxData -= image._imageSize;
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
ktxData -= ktx::IMAGE_SIZE_WIDTH;
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
}
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner
texture = textureCache->cacheTextureByHash(filename, texture);
}
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
setImage(texture, header->getPixelWidth(), header->getPixelHeight());
_ktxHeaderRequest->deleteLater();
_ktxHeaderRequest = nullptr;
_ktxMipRequest->deleteLater();
_ktxMipRequest = nullptr;
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;
}
startRequestForNextMipLevel();
}
QThread::currentThread()->setPriority(QThread::LowPriority);
Finally restorePriority([originalPriority] { QThread::currentThread()->setPriority(originalPriority); });
auto resource = self.lock();
if (!resource) {
// Resource no longer exists, bail
return;
}
auto header = reinterpret_cast<const ktx::Header*>(ktxHeaderData.data());
if (!ktx::checkIdentifier(header->identifier)) {
qWarning() << "Cannot load " << url << ", invalid header identifier";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto kvSize = header->bytesOfKeyValueData;
if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) {
qWarning() << "Cannot load " << url << ", did not receive all kv data with initial request";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto keyValues = ktx::KTX::parseKeyValues(header->bytesOfKeyValueData, reinterpret_cast<const ktx::Byte*>(ktxHeaderData.data()) + ktx::KTX_HEADER_SIZE);
auto imageDescriptors = header->generateImageDescriptors();
if (imageDescriptors.size() == 0) {
qWarning(networking) << "Failed to process ktx file " << url;
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto originalKtxDescriptor = new ktx::KTXDescriptor(*header, keyValues, imageDescriptors);
QMetaObject::invokeMethod(resource.data(), "setOriginalDescriptor",
Q_ARG(ktx::KTXDescriptor*, originalKtxDescriptor));
// Create bare ktx in memory
auto found = std::find_if(keyValues.begin(), keyValues.end(), [](const ktx::KeyValue& val) -> bool {
return val._key.compare(gpu::SOURCE_HASH_KEY) == 0;
});
std::string filename;
std::string hash;
if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) {
qWarning("Invalid source hash key found, bailing");
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
} else {
// at this point the source hash is in binary 16-byte form
// and we need it in a hexadecimal string
auto binaryHash = QByteArray(reinterpret_cast<char*>(found->_value.data()), gpu::SOURCE_HASH_BYTES);
hash = filename = binaryHash.toHex().toStdString();
}
auto textureCache = DependencyManager::get<TextureCache>();
gpu::TexturePointer texture = textureCache->getTextureByHash(hash);
if (!texture) {
KTXFilePointer ktxFile = textureCache->_ktxCache.getFile(hash);
if (ktxFile) {
texture = gpu::Texture::unserialize(ktxFile);
if (texture) {
texture = textureCache->cacheTextureByHash(hash, texture);
}
}
}
if (!texture) {
auto memKtx = ktx::KTX::createBare(*header, keyValues);
if (!memKtx) {
qWarning() << " Ktx could not be created, bailing";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
// Move ktx to file
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
KTXFilePointer file;
auto& ktxCache = textureCache->_ktxCache;
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
qCWarning(modelnetworking) << url << " failed to write cache file";
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, nullptr),
Q_ARG(int, 0),
Q_ARG(int, 0));
return;
}
auto newKtxDescriptor = memKtx->toDescriptor();
texture = gpu::Texture::build(newKtxDescriptor);
texture->setKtxBacking(file);
texture->setSource(filename);
auto& images = originalKtxDescriptor->images;
size_t imageSizeRemaining = ktxHighMipData.size();
const uint8_t* ktxData = reinterpret_cast<const uint8_t*>(ktxHighMipData.data());
ktxData += ktxHighMipData.size();
// TODO Move image offset calculation to ktx ImageDescriptor
for (int level = static_cast<int>(images.size()) - 1; level >= 0; --level) {
auto& image = images[level];
if (image._imageSize > imageSizeRemaining) {
break;
}
ktxData -= image._imageSize;
texture->assignStoredMip(static_cast<gpu::uint16>(level), image._imageSize, ktxData);
ktxData -= ktx::IMAGE_SIZE_WIDTH;
imageSizeRemaining -= (image._imageSize + ktx::IMAGE_SIZE_WIDTH);
}
// We replace the texture with the one stored in the cache. This deals with the possible race condition of two different
// images with the same hash being loaded concurrently. Only one of them will make it into the cache by hash first and will
// be the winner
texture = textureCache->cacheTextureByHash(filename, texture);
}
QMetaObject::invokeMethod(resource.data(), "setImage",
Q_ARG(gpu::TexturePointer, texture),
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel");
});
}
void NetworkTexture::downloadFinished(const QByteArray& data) {
@ -845,11 +950,11 @@ void ImageReader::read() {
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
size_t length = memKtx->_storage->size();
auto& ktxCache = textureCache->_ktxCache;
networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length)); //
if (!networkTexture->_file) {
auto file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length));
if (!file) {
qCWarning(modelnetworking) << _url << "file cache failed";
} else {
texture->setKtxBacking(networkTexture->_file);
texture->setKtxBacking(file);
}
} else {
qCWarning(modelnetworking) << "Unable to serialize texture to KTX " << _url;
@ -866,3 +971,32 @@ void ImageReader::read() {
Q_ARG(int, texture->getWidth()),
Q_ARG(int, texture->getHeight()));
}
NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) {
gpu::TexturePointer texture;
if (resourceTextureUrl == SPECTATOR_CAMERA_FRAME_URL) {
if (!_spectatorCameraNetworkTexture) {
_spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
}
texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
if (texture) {
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
return _spectatorCameraNetworkTexture;
}
}
return NetworkTexturePointer();
}
const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer() {
if (!_spectatorCameraFramebuffer) {
resetSpectatorCameraFramebuffer(2048, 1024);
}
return _spectatorCameraFramebuffer;
}
void TextureCache::resetSpectatorCameraFramebuffer(int width, int height) {
_spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height));
_spectatorCameraNetworkTexture.reset();
}

View file

@ -43,6 +43,7 @@ class NetworkTexture : public Resource, public Texture {
Q_OBJECT
public:
NetworkTexture(const QUrl& url);
NetworkTexture(const QUrl& url, image::TextureUsage::Type type, const QByteArray& content, int maxNumPixels);
~NetworkTexture() override;
@ -58,14 +59,13 @@ public:
void refresh() override;
Q_INVOKABLE void setOriginalDescriptor(ktx::KTXDescriptor* descriptor) { _originalKtxDescriptor.reset(descriptor); }
signals:
void networkTextureCreated(const QWeakPointer<NetworkTexture>& self);
public slots:
void ktxHeaderRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
void ktxHeaderRequestFinished();
void ktxMipRequestProgress(uint64_t bytesReceived, uint64_t bytesTotal) { }
void ktxInitialDataRequestFinished();
void ktxMipRequestFinished();
protected:
@ -74,14 +74,14 @@ protected:
virtual bool isCacheable() const override { return _loaded; }
virtual void downloadFinished(const QByteArray& data) override;
Q_INVOKABLE void loadContent(const QByteArray& content);
Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight);
void startRequestForNextMipLevel();
Q_INVOKABLE void startRequestForNextMipLevel();
void startMipRangeRequest(uint16_t low, uint16_t high);
void maybeHandleFinishedInitialLoad();
void handleFinishedInitialLoad();
private:
friend class KTXReader;
@ -102,16 +102,13 @@ private:
bool _sourceIsKTX { false };
KTXResourceState _ktxResourceState { PENDING_INITIAL_LOAD };
// TODO Can this be removed?
KTXFilePointer _file;
// The current mips that are currently being requested w/ _ktxMipRequest
std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
ResourceRequest* _ktxHeaderRequest { nullptr };
ResourceRequest* _ktxMipRequest { nullptr };
bool _ktxHeaderRequestFinished{ false };
bool _ktxHighMipRequestFinished{ false };
QByteArray _ktxHeaderData;
QByteArray _ktxHighMipData;
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
@ -128,6 +125,8 @@ private:
int _width { 0 };
int _height { 0 };
int _maxNumPixels { ABSOLUTE_MAX_TEXTURE_NUM_PIXELS };
friend class TextureCache;
};
using NetworkTexturePointer = QSharedPointer<NetworkTexture>;
@ -166,6 +165,12 @@ public:
gpu::TexturePointer getTextureByHash(const std::string& hash);
gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture);
/// SpectatorCamera rendering targets.
NetworkTexturePointer getResourceTexture(QUrl resourceTextureUrl);
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer();
void resetSpectatorCameraFramebuffer(int width, int height);
protected:
// Overload ResourceCache::prefetch to allow specifying texture type for loads
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
@ -183,6 +188,7 @@ private:
static const std::string KTX_DIRNAME;
static const std::string KTX_EXT;
KTXCache _ktxCache;
// Map from image hashes to texture weak pointers
std::unordered_map<std::string, std::weak_ptr<gpu::Texture>> _texturesByHashes;
@ -193,6 +199,9 @@ private:
gpu::TexturePointer _grayTexture;
gpu::TexturePointer _blueTexture;
gpu::TexturePointer _blackTexture;
NetworkTexturePointer _spectatorCameraNetworkTexture;
gpu::FramebufferPointer _spectatorCameraFramebuffer;
};
#endif // hifi_TextureCache_h

View file

@ -353,13 +353,20 @@ void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, S
connect(message.data(), &ReceivedMessage::progress, this, [this, weakNode, messageID, length](qint64 size) {
handleProgressCallback(weakNode, messageID, size, length);
});
connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID]() {
handleCompleteCallback(weakNode, messageID);
connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID, length]() {
handleCompleteCallback(weakNode, messageID, length);
});
if (message->isComplete()) {
disconnect(message.data(), nullptr, this, nullptr);
callbacks.completeCallback(true, error, message->readAll());
if (length != message->getBytesLeftToRead()) {
callbacks.completeCallback(false, error, QByteArray());
} else {
callbacks.completeCallback(true, error, message->readAll());
}
messageCallbackMap.erase(requestIt);
}
}
@ -391,7 +398,7 @@ void AssetClient::handleProgressCallback(const QWeakPointer<Node>& node, Message
callbacks.progressCallback(size, length);
}
void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID) {
void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, DataOffset length) {
auto senderNode = node.toStrongRef();
if (!senderNode) {
@ -424,8 +431,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer<Node>& node, Message
return;
}
if (message->failed()) {
if (message->failed() || length != message->getBytesLeftToRead()) {
callbacks.completeCallback(false, AssetServerError::NoError, QByteArray());
} else {
callbacks.completeCallback(true, AssetServerError::NoError, message->readAll());

View file

@ -93,7 +93,7 @@ private:
bool cancelUploadAssetRequest(MessageID id);
void handleProgressCallback(const QWeakPointer<Node>& node, MessageID messageID, qint64 size, DataOffset length);
void handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID);
void handleCompleteCallback(const QWeakPointer<Node>& node, MessageID messageID, DataOffset length);
void forceFailureOfPendingRequests(SharedNodePointer node);

View file

@ -104,12 +104,7 @@ void AssetRequest::start() {
break;
}
} else {
if (_byteRange.isSet()) {
// we had a byte range, the size of the data does not match what we expect, so we return an error
if (data.size() != _byteRange.size()) {
_error = SizeVerificationFailed;
}
} else if (hashData(data).toHex() != _hash) {
if (!_byteRange.isSet() && hashData(data).toHex() != _hash) {
// the hash of the received data does not match what we expect, so we return an error
_error = HashVerificationFailed;
}

View file

@ -23,14 +23,16 @@ const uint16_t ObjectActionTractor::tractorVersion = 1;
ObjectActionTractor::ObjectActionTractor(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectAction(DYNAMIC_TYPE_TRACTOR, id, ownerEntity),
_positionalTarget(glm::vec3(0.0f)),
_desiredPositionalTarget(glm::vec3(0.0f)),
_positionalTarget(0.0f),
_desiredPositionalTarget(0.0f),
_linearTimeScale(FLT_MAX),
_positionalTargetSet(true),
_rotationalTarget(glm::quat()),
_desiredRotationalTarget(glm::quat()),
_positionalTargetSet(false),
_rotationalTarget(),
_desiredRotationalTarget(),
_angularTimeScale(FLT_MAX),
_rotationalTargetSet(true) {
_rotationalTargetSet(true),
_linearVelocityTarget(0.0f)
{
#if WANT_DEBUG
qCDebug(physics) << "ObjectActionTractor::ObjectActionTractor";
#endif
@ -77,7 +79,6 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) {
glm::quat rotation;
glm::vec3 position;
glm::vec3 linearVelocity;
glm::vec3 angularVelocity;
bool linearValid = false;
@ -117,7 +118,6 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) {
linearValid = true;
linearTractorCount++;
position += positionForAction;
linearVelocity += linearVelocityForAction;
}
}
}
@ -126,9 +126,18 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) {
withWriteLock([&]{
if (linearValid && linearTractorCount > 0) {
position /= linearTractorCount;
linearVelocity /= linearTractorCount;
if (_positionalTargetSet) {
_lastPositionTarget = _positionalTarget;
} else {
_lastPositionTarget = position;
}
_positionalTarget = position;
_linearVelocityTarget = linearVelocity;
if (deltaTimeStep > EPSILON) {
// blend the new velocity with the old (low-pass filter)
glm::vec3 newVelocity = (1.0f / deltaTimeStep) * (position - _lastPositionTarget);
const float blend = 0.25f;
_linearVelocityTarget = (1.0f - blend) * _linearVelocityTarget + blend * newVelocity;
}
_positionalTargetSet = true;
_active = true;
}
@ -169,19 +178,19 @@ void ObjectActionTractor::updateActionWorker(btScalar deltaTimeStep) {
}
if (_linearTimeScale < MAX_TRACTOR_TIMESCALE) {
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
btVector3 offsetVelocity(0.0f, 0.0f, 0.0f);
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
float offsetLength = offset.length();
if (offsetLength > FLT_EPSILON) {
float speed = glm::min(offsetLength / _linearTimeScale, TRACTOR_MAX_SPEED);
targetVelocity = (-speed / offsetLength) * offset;
offsetVelocity = (-speed / offsetLength) * offset;
if (speed > rigidBody->getLinearSleepingThreshold()) {
forceBodyNonStatic();
rigidBody->activate();
}
}
// this action is aggresively critically damped and defeats the current velocity
rigidBody->setLinearVelocity(targetVelocity);
rigidBody->setLinearVelocity(glmToBullet(_linearVelocityTarget) + offsetVelocity);
}
if (_angularTimeScale < MAX_TRACTOR_TIMESCALE) {

View file

@ -36,6 +36,7 @@ protected:
glm::vec3 _positionalTarget;
glm::vec3 _desiredPositionalTarget;
glm::vec3 _lastPositionTarget;
float _linearTimeScale;
bool _positionalTargetSet;

View file

@ -184,6 +184,9 @@ public:
// will query the underlying hmd api to compute the most recent head pose
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
// Set the texture to display on the monitor and return true, if allowed. Empty string resets.
virtual bool setDisplayTexture(const QString& name) { return false; }
virtual float devicePixelRatio() { return 1.0f; }
// Rate at which we render frames
virtual float renderRate() const { return -1.0f; }

View file

@ -29,7 +29,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
// Still relying on the raw data from the model
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE);
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE);
if (useCauterizedMesh) {
ModelPointer model = _model.lock();
if (model) {

View file

@ -548,6 +548,7 @@ glm::ivec3 LightClusters::updateClusters() {
LightClusteringPass::LightClusteringPass() {
_lightClusters = std::make_shared<LightClusters>();
}
@ -566,12 +567,7 @@ void LightClusteringPass::run(const render::RenderContextPointer& renderContext,
auto deferredTransform = inputs.get0();
auto lightingModel = inputs.get1();
auto surfaceGeometryFramebuffer = inputs.get2();
if (!_lightClusters) {
_lightClusters = std::make_shared<LightClusters>();
}
// first update the Grid with the new frustum
if (!_freeze) {
_lightClusters->updateFrustum(args->getViewFrustum());

View file

@ -61,7 +61,7 @@ namespace render {
class Args {
public:
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE };
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE, SECONDARY_CAMERA_RENDER_MODE };
enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD };
enum DebugFlags {
RENDER_DEBUG_NONE = 0,

View file

@ -11,6 +11,8 @@
#include "AudioScriptingInterface.h"
#include <QVector3D>
#include "ScriptAudioInjector.h"
#include "ScriptEngineLogging.h"
@ -19,6 +21,13 @@ void registerAudioMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue);
}
ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound, const QVector3D& position) {
AudioInjectorOptions options;
options.position = glm::vec3(position.x(), position.y(), position.z());
options.localOnly = true;
return playSound(sound, options);
}
ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions) {
if (QThread::currentThread() != thread()) {
ScriptAudioInjector* injector = NULL;

View file

@ -30,8 +30,10 @@ public:
protected:
AudioScriptingInterface() {}
// this method is protected to stop C++ callers from calling, but invokable from script
// these methods are protected to stop C++ callers from calling, but invokable from script
Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions());
// FIXME: there is no way to play a positionless sound
Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position);
Q_INVOKABLE void setStereoInput(bool stereo);

View file

@ -0,0 +1,182 @@
//
// ConsoleScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by NeetBhagat on 6/1/17.
// Copyright 2014 High Fidelity, Inc.
//
// ConsoleScriptingInterface is responsible for following functionality
// Printing logs with various tags and grouping on debug Window and Logs/log file.
// Debugging functionalities like Timer start-end, assertion, trace.
// To use these functionalities, use "console" object in Javascript files.
// For examples please refer "scripts/developer/tests/consoleObjectTest.js"
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ConsoleScriptingInterface.h"
#include "ScriptEngine.h"
#define INDENTATION 4 // 1 Tab - 4 spaces
const QString LINE_SEPARATOR = "\n ";
const QString SPACE_SEPARATOR = " ";
const QString STACK_TRACE_FORMAT = "\n[Stacktrace]%1%2";
QList<QString> ConsoleScriptingInterface::_groupDetails = QList<QString>();
QScriptValue ConsoleScriptingInterface::info(QScriptContext* context, QScriptEngine* engine) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptInfoMessage(appendArguments(context));
}
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::log(QScriptContext* context, QScriptEngine* engine) {
QString message = appendArguments(context);
if (_groupDetails.count() == 0) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptPrintedMessage(message);
}
} else {
logGroupMessage(message, engine);
}
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::debug(QScriptContext* context, QScriptEngine* engine) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptPrintedMessage(appendArguments(context));
}
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::warn(QScriptContext* context, QScriptEngine* engine) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptWarningMessage(appendArguments(context));
}
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::error(QScriptContext* context, QScriptEngine* engine) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptErrorMessage(appendArguments(context));
}
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::exception(QScriptContext* context, QScriptEngine* engine) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptErrorMessage(appendArguments(context));
}
return QScriptValue::NullValue;
}
void ConsoleScriptingInterface::time(QString labelName) {
_timerDetails.insert(labelName, QDateTime::currentDateTime().toUTC());
QString message = QString("%1: Timer started").arg(labelName);
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->scriptPrintedMessage(message);
}
}
void ConsoleScriptingInterface::timeEnd(QString labelName) {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
if (!_timerDetails.contains(labelName)) {
scriptEngine->scriptErrorMessage("No such label found " + labelName);
return;
}
if (_timerDetails.value(labelName).isNull()) {
_timerDetails.remove(labelName);
scriptEngine->scriptErrorMessage("Invalid start time for " + labelName);
return;
}
QDateTime _startTime = _timerDetails.value(labelName);
QDateTime _endTime = QDateTime::currentDateTime().toUTC();
qint64 diffInMS = _startTime.msecsTo(_endTime);
QString message = QString("%1: %2ms").arg(labelName).arg(QString::number(diffInMS));
_timerDetails.remove(labelName);
scriptEngine->scriptPrintedMessage(message);
}
}
QScriptValue ConsoleScriptingInterface::assertion(QScriptContext* context, QScriptEngine* engine) {
QString message;
bool condition = false;
for (int i = 0; i < context->argumentCount(); i++) {
if (i == 0) {
condition = context->argument(i).toBool(); // accept first value as condition
} else {
message += SPACE_SEPARATOR + context->argument(i).toString(); // accept other parameters as message
}
}
QString assertionResult;
if (!condition) {
if (message.isEmpty()) {
assertionResult = "Assertion failed";
} else {
assertionResult = QString("Assertion failed : %1").arg(message);
}
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptErrorMessage(assertionResult);
}
}
return QScriptValue::NullValue;
}
void ConsoleScriptingInterface::trace() {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->scriptPrintedMessage
(QString(STACK_TRACE_FORMAT).arg(LINE_SEPARATOR,
scriptEngine->currentContext()->backtrace().join(LINE_SEPARATOR)));
}
}
void ConsoleScriptingInterface::clear() {
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine())) {
scriptEngine->clearDebugLogWindow();
}
}
QScriptValue ConsoleScriptingInterface::group(QScriptContext* context, QScriptEngine* engine) {
logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label
_groupDetails.push_back(context->argument(0).toString());
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::groupCollapsed(QScriptContext* context, QScriptEngine* engine) {
logGroupMessage(context->argument(0).toString(), engine); // accept first parameter as label
_groupDetails.push_back(context->argument(0).toString());
return QScriptValue::NullValue;
}
QScriptValue ConsoleScriptingInterface::groupEnd(QScriptContext* context, QScriptEngine* engine) {
ConsoleScriptingInterface::_groupDetails.removeLast();
return QScriptValue::NullValue;
}
QString ConsoleScriptingInterface::appendArguments(QScriptContext* context) {
QString message;
for (int i = 0; i < context->argumentCount(); i++) {
if (i > 0) {
message += SPACE_SEPARATOR;
}
message += context->argument(i).toString();
}
return message;
}
void ConsoleScriptingInterface::logGroupMessage(QString message, QScriptEngine* engine) {
int _addSpaces = _groupDetails.count() * INDENTATION;
QString logMessage;
for (int i = 0; i < _addSpaces; i++) {
logMessage.append(SPACE_SEPARATOR);
}
logMessage.append(message);
if (ScriptEngine* scriptEngine = qobject_cast<ScriptEngine*>(engine)) {
scriptEngine->scriptPrintedMessage(logMessage);
}
}

View file

@ -0,0 +1,56 @@
//
// ConsoleScriptingInterface.h
// libraries/script-engine/src
//
// Created by NeetBhagat on 6/1/17.
// Copyright 2014 High Fidelity, Inc.
//
// ConsoleScriptingInterface is responsible for following functionality
// Printing logs with various tags and grouping on debug Window and Logs/log file.
// Debugging functionalities like Timer start-end, assertion, trace.
// To use these functionalities, use "console" object in Javascript files.
// For examples please refer "scripts/developer/tests/consoleObjectTest.js"
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_ConsoleScriptingInterface_h
#define hifi_ConsoleScriptingInterface_h
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtScript/QScriptable>
#include <QList>
#include <QHash>
// Scriptable interface of "console" object. Used exclusively in the JavaScript API
class ConsoleScriptingInterface : public QObject, protected QScriptable {
Q_OBJECT
public:
static QScriptValue info(QScriptContext* context, QScriptEngine* engine);
static QScriptValue log(QScriptContext* context, QScriptEngine* engine);
static QScriptValue debug(QScriptContext* context, QScriptEngine* engine);
static QScriptValue warn(QScriptContext* context, QScriptEngine* engine);
static QScriptValue error(QScriptContext* context, QScriptEngine* engine);
static QScriptValue exception(QScriptContext* context, QScriptEngine* engine);
static QScriptValue assertion(QScriptContext* context, QScriptEngine* engine);
static QScriptValue group(QScriptContext* context, QScriptEngine* engine);
static QScriptValue groupCollapsed(QScriptContext* context, QScriptEngine* engine);
static QScriptValue groupEnd(QScriptContext* context, QScriptEngine* engine);
public slots:
void time(QString labelName);
void timeEnd(QString labelName);
void trace();
void clear();
private:
QHash<QString, QDateTime> _timerDetails;
static QList<QString> _groupDetails;
static void logGroupMessage(QString message, QScriptEngine* engine);
static QString appendArguments(QScriptContext* context);
};
#endif // hifi_ConsoleScriptingInterface_h

View file

@ -472,20 +472,24 @@ void ScriptEngine::scriptErrorMessage(const QString& message) {
}
void ScriptEngine::scriptWarningMessage(const QString& message) {
qCWarning(scriptengine) << message;
qCWarning(scriptengine) << qPrintable(message);
emit warningMessage(message, getFilename());
}
void ScriptEngine::scriptInfoMessage(const QString& message) {
qCInfo(scriptengine) << message;
qCInfo(scriptengine) << qPrintable(message);
emit infoMessage(message, getFilename());
}
void ScriptEngine::scriptPrintedMessage(const QString& message) {
qCDebug(scriptengine) << message;
qCDebug(scriptengine) << qPrintable(message);
emit printedMessage(message, getFilename());
}
void ScriptEngine::clearDebugLogWindow() {
emit clearDebugWindow();
}
// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of
// callAnimationStateHandler requires that the type be registered.
// These two are meaningful, if we ever do want to use them...
@ -668,8 +672,18 @@ void ScriptEngine::init() {
registerGlobalObject("Mat4", &_mat4Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
registerGlobalObject("File", new FileScriptingInterface(this));
registerGlobalObject("console", &_consoleScriptingInterface);
registerFunction("console", "info", ConsoleScriptingInterface::info, currentContext()->argumentCount());
registerFunction("console", "log", ConsoleScriptingInterface::log, currentContext()->argumentCount());
registerFunction("console", "debug", ConsoleScriptingInterface::debug, currentContext()->argumentCount());
registerFunction("console", "warn", ConsoleScriptingInterface::warn, currentContext()->argumentCount());
registerFunction("console", "error", ConsoleScriptingInterface::error, currentContext()->argumentCount());
registerFunction("console", "exception", ConsoleScriptingInterface::exception, currentContext()->argumentCount());
registerFunction("console", "assert", ConsoleScriptingInterface::assertion, currentContext()->argumentCount());
registerFunction("console", "group", ConsoleScriptingInterface::group, 1);
registerFunction("console", "groupCollapsed", ConsoleScriptingInterface::groupCollapsed, 1);
registerFunction("console", "groupEnd", ConsoleScriptingInterface::groupEnd, 0);
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);

View file

@ -41,6 +41,7 @@
#include "ScriptCache.h"
#include "ScriptUUID.h"
#include "Vec3.h"
#include "ConsoleScriptingInterface.h"
#include "SettingHandle.h"
class QScriptEngineDebugger;
@ -225,7 +226,7 @@ public:
void scriptWarningMessage(const QString& message);
void scriptInfoMessage(const QString& message);
void scriptPrintedMessage(const QString& message);
void clearDebugLogWindow();
int getNumRunningEntityScripts() const;
bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const;
@ -245,6 +246,7 @@ signals:
void warningMessage(const QString& message, const QString& scriptName);
void infoMessage(const QString& message, const QString& scriptName);
void runningStateChanged();
void clearDebugWindow();
void loadScript(const QString& scriptName, bool isUserLoaded);
void reloadScript(const QString& scriptName, bool isUserLoaded);
void doneRunning();
@ -305,6 +307,7 @@ protected:
Vec3 _vec3Library;
Mat4 _mat4Library;
ScriptUUID _uuidLibrary;
ConsoleScriptingInterface _consoleScriptingInterface;
std::atomic<bool> _isUserLoaded { false };
bool _isReloading { false };

View file

@ -54,6 +54,10 @@ void ScriptEngines::onInfoMessage(const QString& message, const QString& scriptN
emit infoMessage(message, scriptName);
}
void ScriptEngines::onClearDebugWindow() {
emit clearDebugWindow();
}
void ScriptEngines::onErrorLoadingScript(const QString& url) {
emit errorLoadingScript(url);
}

View file

@ -79,6 +79,7 @@ signals:
void warningMessage(const QString& message, const QString& engineName);
void infoMessage(const QString& message, const QString& engineName);
void errorLoadingScript(const QString& url);
void clearDebugWindow();
public slots:
void onPrintedMessage(const QString& message, const QString& scriptName);
@ -86,6 +87,7 @@ public slots:
void onWarningMessage(const QString& message, const QString& scriptName);
void onInfoMessage(const QString& message, const QString& scriptName);
void onErrorLoadingScript(const QString& url);
void onClearDebugWindow();
protected slots:
void onScriptFinished(const QString& fileNameString, ScriptEngine* engine);

View file

@ -614,15 +614,6 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
}
}
void TabletProxy::updateAudioBar(const double micLevel) {
auto tablet = getQmlTablet();
if (!tablet) {
//qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
} else {
QMetaObject::invokeMethod(tablet, "setMicLevel", Qt::AutoConnection, Q_ARG(QVariant, QVariant(micLevel)));
}
}
void TabletProxy::emitScriptEvent(QVariant msg) {
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));

View file

@ -152,13 +152,6 @@ public:
*/
Q_INVOKABLE void removeButton(QObject* tabletButtonProxy);
/**jsdoc
* Updates the audio bar in tablet to reflect latest mic level
* @function TabletProxy#updateAudioBar
* @param micLevel {double} mic level value between 0 and 1
*/
Q_INVOKABLE void updateAudioBar(const double micLevel);
/**jsdoc
* Used to send an event to the html/js embedded in the tablet
* @function TabletProxy#emitScriptEvent

View file

@ -153,6 +153,9 @@ void QmlWindowClass::sendToQml(const QVariant& message) {
QMetaObject::invokeMethod(asQuickItem(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
void QmlWindowClass::clearDebugWindow() {
QMetaObject::invokeMethod(asQuickItem(), "clearDebugWindow", Qt::QueuedConnection);
}
void QmlWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
if (QThread::currentThread() != thread()) {

View file

@ -53,6 +53,7 @@ public slots:
// Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message);
void clearDebugWindow();
// QmlWindow content may include WebView requiring EventBridge.
void emitScriptEvent(const QVariant& scriptMessage);

View file

@ -49,4 +49,8 @@ ScriptDiscoveryService.infoMessage.connect(function(message, scriptFileName) {
sendToLogWindow("INFO", message, scriptFileName);
});
}()); // END LOCAL_SCOPE
ScriptDiscoveryService.clearDebugWindow.connect(function() {
window.clearDebugWindow();
});
}());

View file

@ -40,6 +40,10 @@ Rectangle {
}
textArea.append(message);
}
function clearWindow() {
textArea.remove(0,textArea.length);
}
}

View file

@ -0,0 +1,114 @@
// Examples and understanding of console object. Include console methods like
// info, log, debug, warn, error, exception, trace, clear, asserts, group, groupCollapsed, groupEnd, time, timeEnd.
// Useful in debugging and exclusively made for JavaScript files.
// To view the logs click on Developer -> script logs [logs on debug window] and for text file logs go to Logs/log file.
main();
function main() {
var someObject = { str: "Some text", id: 5 };
var someValue = 5;
// console.info examples
console.info("[console.info] Hello World.");
console.info(5 + 6);
console.info(someObject.str);
console.info(a = 2 * 6);
console.info(someValue);
console.info('someObject id ' + someObject.id);
console.info('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6);
// console.log examples
console.log("[console.log] Hello World");
console.log(5 + 6);
console.log(someObject.str);
console.log(a = 2 * 6);
console.log(someValue);
console.log('someObject id ' + someObject.id);
console.log('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6);
// console.debug examples
console.debug("[console.debug] Hello World.");
console.debug(5 + 6);
console.debug(someObject.str);
console.debug(a = 2 * 6);
console.debug(someValue);
console.debug('someObject id ' + someObject.id);
console.debug('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6);
// console.warn examples
console.warn("[console.warn] This is warning message.");
console.warn(5 + 6);
console.warn(someObject.str);
console.warn(a = 2 * 6);
console.warn(someValue);
console.warn('someObject id ' + someObject.id);
console.warn('Hello World ', 'someObject.id = ', someObject.id, "a = 2 * 6 = ", a = 2 * 6);
// console.error examples
console.error('An error occurred!');
console.error('An error occurred! ', 'Value = ', someValue);
console.error('An error occurred! ' + 'Value = ' + someValue);
// console.exception examples
console.exception('An exception occurred!');
console.exception('An exception occurred! ', 'Value = ', someValue);
console.exception('An exception occurred! ' + 'Value = ' + someValue);
// console.trace examples
function fooA() {
function fooB() {
function fooC() {
console.trace();
}
fooC();
}
fooB();
}
fooA();
// console.assert() examples
var valA = 1, valB = "1";
console.assert(valA === valB, "Value A doesn't equal to B");
console.assert(valA === valB);
console.assert(5 === 5, "5 equals to 5");
console.assert(5 === 5);
console.assert(5 > 6, "5 is not greater than 6");
console.assert(5 > 6, "5 is not greater than 6", "This assertion will fail");
// console.group() examples.
console.group("Group 1");
console.log("Sentence 1");
console.log("Sentence 2");
console.group("Group 2");
console.log("Sentence 3");
console.log("Sentence 4");
console.groupCollapsed("Group 3");
console.log("Sentence 5");
console.log("Sentence 6");
console.groupEnd();
console.log("Sentence 7");
console.groupEnd();
console.log("Sentence 8");
console.groupEnd();
console.log("Sentence 9");
// console.time(),console.timeEnd() examples
console.time('MyTimer');
// Do some process
sleep(1000);
console.timeEnd('MyTimer');
// use console.clear() to clean Debug Window logs
// console.clear();
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}

View file

@ -13,6 +13,7 @@ import "configSlider"
import "../lib/plotperf"
Column {
property var mainViewTask: Render.getConfig("RenderMainView")
spacing: 8
Column {
id: surfaceGeometry
@ -32,7 +33,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: (modelData.split(":")[3] == 'true')
config: Render.getConfig("AmbientOcclusion")
config: mainViewTask.getConfig("AmbientOcclusion")
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: 0.0
@ -50,8 +51,8 @@ Column {
]
CheckBox {
text: qsTr(modelData.split(":")[0])
checked: Render.getConfig("AmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { Render.getConfig("AmbientOcclusion")[modelData.split(":")[1]] = checked }
checked: mainViewTask.getConfig("AmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { mainViewTask.getConfig("AmbientOcclusion")[modelData.split(":")[1]] = checked }
}
}
}
@ -62,8 +63,8 @@ Column {
]
CheckBox {
text: qsTr(modelData.split(":")[0])
checked: Render.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { Render.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]] = checked }
checked: mainViewTask.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]]
onCheckedChanged: { mainViewTask.getConfig("DebugAmbientOcclusion")[modelData.split(":")[1]] = checked }
}
}
}
@ -72,7 +73,7 @@ Column {
PlotPerf {
title: "Timing"
height: 50
object: Render.getConfig("AmbientOcclusion")
object: mainViewTask.getConfig("AmbientOcclusion")
valueUnit: "ms"
valueScale: 1
valueNumDigits: "3"

View file

@ -14,8 +14,9 @@ import "configSlider"
Column {
id: root
spacing: 8
property var sceneOctree: Render.getConfig("DrawSceneOctree");
property var itemSelection: Render.getConfig("DrawItemSelection");
property var mainViewTask: Render.getConfig("RenderMainView");
property var sceneOctree: mainViewTask.getConfig("DrawSceneOctree");
property var itemSelection: mainViewTask.getConfig("DrawItemSelection");
Component.onCompleted: {
sceneOctree.enabled = true;
@ -30,8 +31,8 @@ Column {
Component.onDestruction: {
sceneOctree.enabled = false;
itemSelection.enabled = false;
Render.getConfig("FetchSceneSelection").freezeFrustum = false;
Render.getConfig("CullSceneSelection").freezeFrustum = false;
mainViewTask.getConfig("FetchSceneSelection").freezeFrustum = false;
mainViewTask.getConfig("CullSceneSelection").freezeFrustum = false;
}
GroupBox {
@ -45,8 +46,8 @@ Column {
text: "Freeze Culling Frustum"
checked: false
onCheckedChanged: {
Render.getConfig("FetchSceneSelection").freezeFrustum = checked;
Render.getConfig("CullSceneSelection").freezeFrustum = checked;
mainViewTask.getConfig("FetchSceneSelection").freezeFrustum = checked;
mainViewTask.getConfig("CullSceneSelection").freezeFrustum = checked;
}
}
Label {
@ -103,7 +104,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: true
config: Render.getConfig(modelData.split(":")[1])
config: mainViewTask.getConfig(modelData.split(":")[1])
property: "maxDrawn"
max: config.numDrawn
min: -1

View file

@ -34,5 +34,5 @@ function setDebugCursor(x, y) {
nx = (x / Window.innerWidth);
ny = 1.0 - ((y) / (Window.innerHeight - 32));
Render.getConfig("DebugAmbientOcclusion").debugCursorTexcoord = { x: nx, y: ny };
Render.getConfig("RenderMainView").getConfig("DebugAmbientOcclusion").debugCursorTexcoord = { x: nx, y: ny };
}

View file

@ -33,5 +33,5 @@ function setDebugCursor(x, y) {
nx = (x / Window.innerWidth);
ny = 1.0 - ((y) / (Window.innerHeight - 32));
Render.getConfig("DebugScattering").debugCursorTexcoord = { x: nx, y: ny };
Render.getConfig("RenderMainView").getConfig("DebugScattering").debugCursorTexcoord = { x: nx, y: ny };
}

View file

@ -13,6 +13,7 @@ import "configSlider"
Column {
spacing: 8
property var mainViewTask: Render.getConfig("RenderMainView")
Row {
spacing: 8
@ -29,8 +30,8 @@ Column {
]
CheckBox {
text: modelData.split(":")[0]
checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
}
}
}
@ -49,8 +50,8 @@ Column {
]
CheckBox {
text: modelData.split(":")[0]
checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
}
}
}
@ -69,8 +70,8 @@ Column {
]
CheckBox {
text: modelData.split(":")[0]
checked: Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { Render.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]]
onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked }
}
}
}
@ -83,7 +84,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: Render.getConfig(modelData.split(":")[1])
config: mainViewTask.getConfig(modelData.split(":")[1])
property: modelData.split(":")[2]
max: modelData.split(":")[3]
min: modelData.split(":")[4]
@ -107,7 +108,7 @@ Column {
ListElement { text: "Filmic"; color: "White" }
}
width: 200
onCurrentIndexChanged: { Render.getConfig("ToneMapping")["curve"] = currentIndex }
onCurrentIndexChanged: { mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex }
}
}
}
@ -120,7 +121,7 @@ Column {
anchors.left: root.left
}
property var config: Render.getConfig("DebugDeferredBuffer")
property var config: mainViewTask.getConfig("DebugDeferredBuffer")
function setDebugMode(mode) {
framebuffer.config.enabled = (mode != 0);
@ -168,40 +169,40 @@ Column {
CheckBox {
text: "Opaques"
checked: Render.getConfig("DrawOpaqueBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawOpaqueBounds")["enabled"] = checked }
checked: mainViewTask.getConfig("DrawOpaqueBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] = checked }
}
CheckBox {
text: "Transparents"
checked: Render.getConfig("DrawTransparentBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawTransparentBounds")["enabled"] = checked }
checked: mainViewTask.getConfig("DrawTransparentBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked }
}
CheckBox {
text: "Overlay Opaques"
checked: Render.getConfig("DrawOverlayOpaqueBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawOverlayOpaqueBounds")["enabled"] = checked }
checked: mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"] = checked }
}
CheckBox {
text: "Overlay Transparents"
checked: Render.getConfig("DrawOverlayTransparentBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked }
checked: mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked }
}
}
Column {
CheckBox {
text: "Metas"
checked: Render.getConfig("DrawMetaBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawMetaBounds")["enabled"] = checked }
checked: mainViewTask.getConfig("DrawMetaBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawMetaBounds")["enabled"] = checked }
}
CheckBox {
text: "Lights"
checked: Render.getConfig("DrawLightBounds")["enabled"]
onCheckedChanged: { Render.getConfig("DrawLightBounds")["enabled"] = checked; }
checked: mainViewTask.getConfig("DrawLightBounds")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("DrawLightBounds")["enabled"] = checked; }
}
CheckBox {
text: "Zones"
checked: Render.getConfig("DrawZones")["enabled"]
onCheckedChanged: { Render.getConfig("ZoneRenderer")["enabled"] = checked; Render.getConfig("DrawZones")["enabled"] = checked; }
checked: mainViewTask.getConfig("DrawZones")["enabled"]
onCheckedChanged: { mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; mainViewTask.getConfig("DrawZones")["enabled"] = checked; }
}
}
}

View file

@ -17,18 +17,19 @@ Column {
Column {
id: lightClustering
spacing: 10
property var mainViewTask: Render.getConfig("RenderMainView");
Column{
PlotPerf {
title: "Light CLustering Timing"
height: 50
object: Render.getConfig("LightClustering")
object: mainViewTask.getConfig("LightClustering")
valueUnit: "ms"
valueScale: 1
valueNumDigits: "4"
plots: [
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "cpuRunTime",
label: "time",
scale: 1,
@ -40,19 +41,19 @@ Column {
PlotPerf {
title: "Lights"
height: 50
object: Render.getConfig("LightClustering")
object: mainViewTask.getConfig("LightClustering")
valueUnit: ""
valueScale: 1
valueNumDigits: "0"
plots: [
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "numClusteredLights",
label: "visible",
color: "#D959FE"
},
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "numInputLights",
label: "input",
color: "#FED959"
@ -63,25 +64,25 @@ Column {
PlotPerf {
title: "Scene Lights"
height: 80
object: Render.getConfig("LightClustering")
object: mainViewTask.getConfig("LightClustering")
valueUnit: ""
valueScale: 1
valueNumDigits: "0"
plots: [
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "numSceneLights",
label: "current",
color: "#00B4EF"
},
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "numFreeSceneLights",
label: "free",
color: "#1AC567"
},
{
object: Render.getConfig("LightClustering"),
object: mainViewTask.getConfig("LightClustering"),
prop: "numAllocatedSceneLights",
label: "allocated",
color: "#9495FF"
@ -92,7 +93,7 @@ Column {
ConfigSlider {
label: qsTr("Range Near [m]")
integral: false
config: Render.getConfig("LightClustering")
config: mainViewTask.getConfig("LightClustering")
property: "rangeNear"
max: 20.0
min: 0.1
@ -100,7 +101,7 @@ Column {
ConfigSlider {
label: qsTr("Range Far [m]")
integral: false
config: Render.getConfig("LightClustering")
config: mainViewTask.getConfig("LightClustering")
property: "rangeFar"
max: 500.0
min: 100.0
@ -108,7 +109,7 @@ Column {
ConfigSlider {
label: qsTr("Grid X")
integral: true
config: Render.getConfig("LightClustering")
config: mainViewTask.getConfig("LightClustering")
property: "dimX"
max: 32
min: 1
@ -116,7 +117,7 @@ Column {
ConfigSlider {
label: qsTr("Grid Y")
integral: true
config: Render.getConfig("LightClustering")
config: mainViewTask.getConfig("LightClustering")
property: "dimY"
max: 32
min: 1
@ -124,33 +125,33 @@ Column {
ConfigSlider {
label: qsTr("Grid Z")
integral: true
config: Render.getConfig("LightClustering")
config: mainViewTask.getConfig("LightClustering")
property: "dimZ"
max: 31
min: 1
}
CheckBox {
text: "Freeze"
checked: Render.getConfig("LightClustering")["freeze"]
onCheckedChanged: { Render.getConfig("LightClustering")["freeze"] = checked }
checked: mainViewTask.getConfig("LightClustering")["freeze"]
onCheckedChanged: { mainViewTask.getConfig("LightClustering")["freeze"] = checked }
}
CheckBox {
text: "Draw Grid"
checked: Render.getConfig("DebugLightClusters")["doDrawGrid"]
onCheckedChanged: { Render.getConfig("DebugLightClusters")["doDrawGrid"] = checked }
checked: mainViewTask.getConfig("DebugLightClusters")["doDrawGrid"]
onCheckedChanged: { mainViewTask.getConfig("DebugLightClusters")["doDrawGrid"] = checked }
}
CheckBox {
text: "Draw Cluster From Depth"
checked: Render.getConfig("DebugLightClusters")["doDrawClusterFromDepth"]
onCheckedChanged: { Render.getConfig("DebugLightClusters")["doDrawClusterFromDepth"] = checked }
checked: mainViewTask.getConfig("DebugLightClusters")["doDrawClusterFromDepth"]
onCheckedChanged: { mainViewTask.getConfig("DebugLightClusters")["doDrawClusterFromDepth"] = checked }
}
CheckBox {
text: "Draw Content"
checked: Render.getConfig("DebugLightClusters")["doDrawContent"]
onCheckedChanged: { Render.getConfig("DebugLightClusters")["doDrawContent"] = checked }
checked: mainViewTask.getConfig("DebugLightClusters")["doDrawContent"]
onCheckedChanged: { mainViewTask.getConfig("DebugLightClusters")["doDrawContent"] = checked }
}
Label {
text: "Num Cluster Items = " + Render.getConfig("LightClustering")["numClusteredLightReferences"].toFixed(0)
text: "Num Cluster Items = " + mainViewTask.getConfig("LightClustering")["numClusteredLightReferences"].toFixed(0)
}
}

View file

@ -21,7 +21,8 @@ Item {
spacing: 8
anchors.fill:parent
property var config: Render.getConfig("Stats")
property var mainViewTask: Render.getConfig("RenderMainView");
property var config: mainViewTask.getConfig("Stats")
function evalEvenHeight() {
// Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ?
@ -182,9 +183,9 @@ Item {
]
}
property var drawOpaqueConfig: Render.getConfig("DrawOpaqueDeferred")
property var drawTransparentConfig: Render.getConfig("DrawTransparentDeferred")
property var drawLightConfig: Render.getConfig("DrawLight")
property var drawOpaqueConfig: mainViewTask.getConfig("DrawOpaqueDeferred")
property var drawTransparentConfig: mainViewTask.getConfig("DrawTransparentDeferred")
property var drawLightConfig: mainViewTask.getConfig("DrawLight")
PlotPerf {
title: "Items"
@ -199,13 +200,13 @@ Item {
color: "#1AC567"
},
{
object: Render.getConfig("DrawTransparentDeferred"),
object: mainViewTask.getConfig("DrawTransparentDeferred"),
prop: "numDrawn",
label: "Translucents",
color: "#00B4EF"
},
{
object: Render.getConfig("DrawLight"),
object: mainViewTask.getConfig("DrawLight"),
prop: "numDrawn",
label: "Lights",
color: "#FED959"
@ -222,25 +223,25 @@ Item {
valueNumDigits: "2"
plots: [
{
object: Render.getConfig("DrawOpaqueDeferred"),
object: mainViewTask.getConfig("DrawOpaqueDeferred"),
prop: "cpuRunTime",
label: "Opaques",
color: "#1AC567"
},
{
object: Render.getConfig("DrawTransparentDeferred"),
object: mainViewTask.getConfig("DrawTransparentDeferred"),
prop: "cpuRunTime",
label: "Translucents",
color: "#00B4EF"
},
{
object: Render.getConfig("RenderDeferred"),
object: mainViewTask.getConfig("RenderDeferred"),
prop: "cpuRunTime",
label: "Lighting",
color: "#FED959"
},
{
object: Render.getConfig("RenderDeferredTask"),
object: mainViewTask.getConfig("RenderDeferredTask"),
prop: "cpuRunTime",
label: "RenderFrame",
color: "#E2334D"

View file

@ -21,7 +21,8 @@ Item {
spacing: 8
anchors.fill:parent
property var config: Render.getConfig("Stats")
property var mainViewTask: Render.getConfig("RenderMainView");
property var config: mainViewTask.getConfig("Stats")
function evalEvenHeight() {
// Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ?
@ -38,31 +39,31 @@ Item {
valueNumDigits: "4"
plots: [
{
object: Render.getConfig("OpaqueRangeTimer"),
object: mainViewTask.getConfig("OpaqueRangeTimer"),
prop: "gpuRunTime",
label: "Opaque",
color: "#FFFFFF"
},
{
object: Render.getConfig("LinearDepth"),
object: mainViewTask.getConfig("LinearDepth"),
prop: "gpuRunTime",
label: "LinearDepth",
color: "#00FF00"
},{
object: Render.getConfig("SurfaceGeometry"),
object: mainViewTask.getConfig("SurfaceGeometry"),
prop: "gpuRunTime",
label: "SurfaceGeometry",
color: "#00FFFF"
},
{
object: Render.getConfig("RenderDeferred"),
object: mainViewTask.getConfig("RenderDeferred"),
prop: "gpuRunTime",
label: "DeferredLighting",
color: "#FF00FF"
}
,
{
object: Render.getConfig("ToneAndPostRangeTimer"),
object: mainViewTask.getConfig("ToneAndPostRangeTimer"),
prop: "gpuRunTime",
label: "tone and post",
color: "#FF0000"
@ -78,31 +79,31 @@ Item {
valueNumDigits: "3"
plots: [
{
object: Render.getConfig("OpaqueRangeTimer"),
object: mainViewTask.getConfig("OpaqueRangeTimer"),
prop: "batchRunTime",
label: "Opaque",
color: "#FFFFFF"
},
{
object: Render.getConfig("LinearDepth"),
object: mainViewTask.getConfig("LinearDepth"),
prop: "batchRunTime",
label: "LinearDepth",
color: "#00FF00"
},{
object: Render.getConfig("SurfaceGeometry"),
object: mainViewTask.getConfig("SurfaceGeometry"),
prop: "batchRunTime",
label: "SurfaceGeometry",
color: "#00FFFF"
},
{
object: Render.getConfig("RenderDeferred"),
object: mainViewTask.getConfig("RenderDeferred"),
prop: "batchRunTime",
label: "DeferredLighting",
color: "#FF00FF"
}
,
{
object: Render.getConfig("ToneAndPostRangeTimer"),
object: mainViewTask.getConfig("ToneAndPostRangeTimer"),
prop: "batchRunTime",
label: "tone and post",
color: "#FF0000"

View file

@ -16,28 +16,29 @@ Column {
Column {
id: scattering
spacing: 10
property var mainViewTask: Render.getConfig("RenderMainView");
Column{
CheckBox {
text: "Scattering"
checked: Render.getConfig("Scattering").enableScattering
onCheckedChanged: { Render.getConfig("Scattering").enableScattering = checked }
checked: mainViewTask.getConfig("Scattering").enableScattering
onCheckedChanged: { mainViewTask.getConfig("Scattering").enableScattering = checked }
}
CheckBox {
text: "Show Scattering BRDF"
checked: Render.getConfig("Scattering").showScatteringBRDF
onCheckedChanged: { Render.getConfig("Scattering").showScatteringBRDF = checked }
checked: mainViewTask.getConfig("Scattering").showScatteringBRDF
onCheckedChanged: { mainViewTask.getConfig("Scattering").showScatteringBRDF = checked }
}
CheckBox {
text: "Show Curvature"
checked: Render.getConfig("Scattering").showCurvature
onCheckedChanged: { Render.getConfig("Scattering").showCurvature = checked }
checked: mainViewTask.getConfig("Scattering").showCurvature
onCheckedChanged: { mainViewTask.getConfig("Scattering").showCurvature = checked }
}
CheckBox {
text: "Show Diffused Normal"
checked: Render.getConfig("Scattering").showDiffusedNormal
onCheckedChanged: { Render.getConfig("Scattering").showDiffusedNormal = checked }
checked: mainViewTask.getConfig("Scattering").showDiffusedNormal
onCheckedChanged: { mainViewTask.getConfig("Scattering").showDiffusedNormal = checked }
}
Repeater {
model: [ "Scattering Bent Red:Scattering:bentRed:2.0",
@ -50,7 +51,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: Render.getConfig(modelData.split(":")[1])
config: mainViewTask.getConfig(modelData.split(":")[1])
property: modelData.split(":")[2]
max: modelData.split(":")[3]
min: 0.0
@ -58,23 +59,23 @@ Column {
}
CheckBox {
text: "Scattering Profile"
checked: Render.getConfig("DebugScattering").showProfile
onCheckedChanged: { Render.getConfig("DebugScattering").showProfile = checked }
checked: mainViewTask.getConfig("DebugScattering").showProfile
onCheckedChanged: { mainViewTask.getConfig("DebugScattering").showProfile = checked }
}
CheckBox {
text: "Scattering Table"
checked: Render.getConfig("DebugScattering").showLUT
onCheckedChanged: { Render.getConfig("DebugScattering").showLUT = checked }
checked: mainViewTask.getConfig("DebugScattering").showLUT
onCheckedChanged: { mainViewTask.getConfig("DebugScattering").showLUT = checked }
}
CheckBox {
text: "Cursor Pixel"
checked: Render.getConfig("DebugScattering").showCursorPixel
onCheckedChanged: { Render.getConfig("DebugScattering").showCursorPixel = checked }
checked: mainViewTask.getConfig("DebugScattering").showCursorPixel
onCheckedChanged: { mainViewTask.getConfig("DebugScattering").showCursorPixel = checked }
}
CheckBox {
text: "Skin Specular Beckmann"
checked: Render.getConfig("DebugScattering").showSpecularTable
onCheckedChanged: { Render.getConfig("DebugScattering").showSpecularTable = checked }
checked: mainViewTask.getConfig("DebugScattering").showSpecularTable
onCheckedChanged: { mainViewTask.getConfig("DebugScattering").showSpecularTable = checked }
}
}
}

View file

@ -16,12 +16,13 @@ Column {
Column {
id: surfaceGeometry
spacing: 10
property var mainViewTask: Render.getConfig("RenderMainView");
Column{
ConfigSlider {
label: qsTr("Depth Threshold [cm]")
integral: false
config: Render.getConfig("SurfaceGeometry")
config: mainViewTask.getConfig("SurfaceGeometry")
property: "depthThreshold"
max: 5.0
min: 0.0
@ -34,7 +35,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: (modelData.split(":")[3] == 'true')
config: Render.getConfig("SurfaceGeometry")
config: mainViewTask.getConfig("SurfaceGeometry")
property: modelData.split(":")[1]
max: modelData.split(":")[2]
min: 0.0
@ -42,8 +43,8 @@ Column {
}
CheckBox {
text: "Half Resolution"
checked: Render.getConfig("SurfaceGeometry")["resolutionLevel"]
onCheckedChanged: { Render.getConfig("SurfaceGeometry")["resolutionLevel"] = checked }
checked: mainViewTask.getConfig("SurfaceGeometry")["resolutionLevel"]
onCheckedChanged: { mainViewTask.getConfig("SurfaceGeometry")["resolutionLevel"] = checked }
}
Repeater {
@ -53,7 +54,7 @@ Column {
ConfigSlider {
label: qsTr(modelData.split(":")[0])
integral: false
config: Render.getConfig(modelData.split(":")[1])
config: mainViewTask.getConfig(modelData.split(":")[1])
property: modelData.split(":")[2]
max: modelData.split(":")[3]
min: 0.0

View file

@ -22,7 +22,8 @@ Item {
spacing: 8
anchors.fill:parent
property var config: Render.getConfig("Stats")
property var mainViewTask: Render.getConfig("RenderMainView");
property var config: mainViewTask.getConfig("Stats")
function evalEvenHeight() {
// Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ?

View file

@ -622,7 +622,7 @@ var toolBar = (function () {
}));
isActive = active;
activeButton.editProperties({isActive: isActive});
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!isActive) {
@ -1519,6 +1519,8 @@ function importSVO(importURL) {
// entities after they're imported so that they're all the correct distance in front of and with geometric mean
// centered on the avatar/camera direction.
var deltaPosition = Vec3.ZERO;
var entityPositions = [];
var entityParentIDs = [];
var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]);
var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
@ -1534,10 +1536,9 @@ function importSVO(importURL) {
var targetPosition = getPositionToCreateEntity();
var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
var entityPositions = [];
for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
var properties = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
"registrationPoint", "rotation"]);
"registrationPoint", "rotation", "parentID"]);
var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
properties.registrationPoint, properties.dimensions, properties.rotation);
var delta = Vec3.subtract(adjustedPosition, properties.position);
@ -1546,6 +1547,7 @@ function importSVO(importURL) {
deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
deltaPerpendicular);
entityPositions[i] = properties.position;
entityParentIDs[i] = properties.parentID;
}
deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
@ -1562,9 +1564,11 @@ function importSVO(importURL) {
if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
Entities.editEntity(pastedEntityIDs[i], {
position: Vec3.sum(deltaPosition, entityPositions[i])
});
if (Uuid.isNull(entityParentIDs[i])) {
Entities.editEntity(pastedEntityIDs[i], {
position: Vec3.sum(deltaPosition, entityPositions[i])
});
}
}
}
}

View file

@ -183,11 +183,6 @@
return;
}
//TODO: move to tablet qml?
if (tabletShown) {
gTablet.updateAudioBar(getMicLevel());
}
if (now - validCheckTime > MSECS_PER_SEC) {
validCheckTime = now;
updateTabletWidthFromSettings();
@ -268,12 +263,6 @@
Script.setInterval(updateShowTablet, 100);
// Calculate microphone level with the same scaling equation (log scale, exponentially averaged) in AvatarInputs and pal.js
function getMicLevel() {
//reuse already existing C++ code
return AvatarInputs.loudnessToAudioLevel(MyAvatar.audioLoudness)
}
Script.scriptEnding.connect(function () {
// if we reload scripts in tablet mode make sure we close the currently open window, by calling gotoHomeScreen