Merge remote-tracking branch 'upstream/master'

This commit is contained in:
ericrius1 2016-03-25 10:15:40 -07:00
commit c3d9f0c1e7
90 changed files with 1479 additions and 663 deletions

View file

@ -8,14 +8,14 @@ like to get paid for your work, make sure you report the bug via a job on
[Worklist.net](https://worklist.net). [Worklist.net](https://worklist.net).
We're hiring! We're looking for skilled developers; We're hiring! We're looking for skilled developers;
send your resume to hiring@highfidelity.io send your resume to hiring@highfidelity.com
##### Chat with us ##### Chat with us
Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi! Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi!
Documentation Documentation
========= =========
Documentation is available at [docs.highfidelity.io](http://docs.highfidelity.io), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project). Documentation is available at [docs.highfidelity.com](http://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
Build Instructions Build Instructions
========= =========

View file

@ -369,14 +369,6 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
reverbTime = _zoneReverbSettings[i].reverbTime; reverbTime = _zoneReverbSettings[i].reverbTime;
wetLevel = _zoneReverbSettings[i].wetLevel; wetLevel = _zoneReverbSettings[i].wetLevel;
// Modulate wet level with distance to wall
float MIN_ATTENUATION_DISTANCE = 2.0f;
float MAX_ATTENUATION = -12; // dB
glm::vec3 distanceToWalls = (box.getDimensions() / 2.0f) - glm::abs(streamPosition - box.calcCenter());
float distanceToClosestWall = glm::min(distanceToWalls.x, distanceToWalls.z);
if (distanceToClosestWall < MIN_ATTENUATION_DISTANCE) {
wetLevel += MAX_ATTENUATION * (1.0f - distanceToClosestWall / MIN_ATTENUATION_DISTANCE);
}
break; break;
} }
} }

View file

@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
ExternalProject_Add( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL https://github.com/ValveSoftware/openvr/archive/v0.9.15.zip URL https://github.com/ValveSoftware/openvr/archive/v0.9.19.zip
URL_MD5 0ff8560b49b6da1150fcc47360e8ceca URL_MD5 843f9dde488584d8af1f3ecf2252b4e0
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""

View file

@ -308,7 +308,7 @@
"name": "reverb", "name": "reverb",
"type": "table", "type": "table",
"label": "Reverb Settings", "label": "Reverb Settings",
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet level of -10 db. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet level of -5 db.", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
"numbered": true, "numbered": true,
"columns": [ "columns": [
{ {
@ -325,9 +325,9 @@
}, },
{ {
"name": "wet_level", "name": "wet_level",
"label": "Wet Level", "label": "Wet/Dry Mix",
"can_set": true, "can_set": true,
"placeholder": "(in db)" "placeholder": "(in percent)"
} }
] ]
} }

301
examples/airship/airship.js Normal file
View file

@ -0,0 +1,301 @@
//
// airship.js
//
// Animates a pirate airship that chases people and shoots cannonballs at them
//
// Created by Philip Rosedale on March 7, 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
var entityID,
minimumDelay = 100, // milliseconds
distanceScale = 2.0, // distance at which 100% chance of hopping
timeScale = 300.0, // crash
hopStrength = 0.4, // meters / second
spotlight = null,
wantDebug = false,
timeoutID = undefined,
bullet = null,
particles = null,
nearbyAvatars = 0,
nearbyAvatarsInRange = 0,
averageAvatarLocation = { x: 0, y: 0, z: 0 },
properties,
lightTimer = 0,
lightTimeoutID = undefined,
audioInjector = null;
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var cannonSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/cannonShot.wav");
var explosionSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "philip/explosion.wav");
var NO_SHOOT_COLOR = { red: 100, green: 100, blue: 100 };
var MAX_TARGET_RANGE = 200;
function printDebug(message) {
if (wantDebug) {
print(message);
}
}
var LIGHT_UPDATE_INTERVAL = 50;
var LIGHT_LIFETIME = 700;
function randomVector(size) {
return { x: (Math.random() - 0.5) * size,
y: (Math.random() - 0.5) * size,
z: (Math.random() - 0.5) * size };
}
function makeLight(parent, position, colorDivisor) {
// Create a flickering light somewhere for a while
if (spotlight !== null) {
// light still exists, do nothing
printDebug("light still there");
return;
}
printDebug("making light");
var colorIndex = 180 + Math.random() * 50;
spotlight = Entities.addEntity({
type: "Light",
name: "Test Light",
intensity: 50.0,
falloffRadius: 20.0,
dimensions: {
x: 150,
y: 150,
z: 150
},
position: position,
parentID: parent,
color: {
red: colorIndex,
green: colorIndex / colorDivisor,
blue: 0
},
lifetime: LIGHT_LIFETIME * 2
});
lightTimer = 0;
lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL);
};
function updateLight() {
lightTimer += LIGHT_UPDATE_INTERVAL;
if ((spotlight !== null) && (lightTimer > LIGHT_LIFETIME)) {
printDebug("deleting light!");
Entities.deleteEntity(spotlight);
spotlight = null;
} else {
Entities.editEntity(spotlight, {
intensity: 5 + Math.random() * 50,
falloffRadius: 5 + Math.random() * 10
});
lightTimeoutID = Script.setTimeout(updateLight, LIGHT_UPDATE_INTERVAL);
}
}
function move() {
var HOVER_DISTANCE = 30.0;
var RUN_TOWARD_STRENGTH = 0.002;
var VELOCITY_FOLLOW_RATE = 0.01;
var range = Vec3.distance(properties.position, averageAvatarLocation);
var impulse = { x: 0, y: 0, z: 0 };
// move in the XZ plane
var away = Vec3.subtract(properties.position, averageAvatarLocation);
away.y = 0.0;
if (range > HOVER_DISTANCE) {
impulse = Vec3.multiply(-RUN_TOWARD_STRENGTH, Vec3.normalize(away));
}
var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, properties.velocity);
Entities.editEntity(entityID, {velocity: Vec3.sum(properties.velocity, impulse), rotation: Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE)});
}
function countAvatars() {
var workQueue = AvatarList.getAvatarIdentifiers();
var averageLocation = {x: 0, y: 0, z: 0};
var summed = 0;
var inRange = 0;
for (var i = 0; i < workQueue.length; i++) {
var avatar = AvatarList.getAvatar(workQueue[i]), avatarPosition = avatar && avatar.position;
if (avatarPosition) {
averageLocation = Vec3.sum(averageLocation, avatarPosition);
summed++;
if (Vec3.distance(avatarPosition, properties.position) < MAX_TARGET_RANGE) {
inRange++;
}
}
}
if (summed > 0) {
averageLocation = Vec3.multiply(1 / summed, averageLocation);
}
nearbyAvatars = summed;
nearbyAvatarsInRange = inRange;
averageAvatarLocation = averageLocation;
//printDebug(" Avatars: " + summed + "in range: " + nearbyAvatarsInRange);
//Vec3.print(" location: ", averageAvatarLocation);
return;
}
function shoot() {
if (bullet !== null) {
return;
}
if (Vec3.distance(MyAvatar.position, properties.position) > MAX_TARGET_RANGE) {
return;
}
var SPEED = 7.5;
var GRAVITY = -2.0;
var DIAMETER = 0.5;
var direction = Vec3.subtract(MyAvatar.position, properties.position);
var range = Vec3.distance(MyAvatar.position, properties.position);
var timeOfFlight = range / SPEED;
var fall = 0.5 * -GRAVITY * (timeOfFlight * timeOfFlight);
var velocity = Vec3.multiply(SPEED, Vec3.normalize(direction));
velocity.y += 0.5 * fall / timeOfFlight * 2.0;
var DISTANCE_TO_DECK = 3;
var bulletStart = properties.position;
bulletStart.y -= DISTANCE_TO_DECK;
makeLight(entityID, Vec3.sum(properties.position, randomVector(10.0)), 2);
bullet = Entities.addEntity({
type: "Sphere",
name: "cannonball",
position: properties.position,
dimensions: { x: DIAMETER, y: DIAMETER, z: DIAMETER },
color: { red: 10, green: 10, blue: 10 },
velocity: velocity,
damping: 0.01,
dynamic: true,
ignoreForCollisions: true,
gravity: { x: 0, y: GRAVITY, z: 0 },
lifetime: timeOfFlight * 2
});
Audio.playSound(cannonSound, {
position: Vec3.sum(MyAvatar.position, velocity),
volume: 1.0
});
makeParticles(properties.position, bullet, timeOfFlight * 2);
Script.setTimeout(explode, timeOfFlight * 1000);
}
function explode() {
var properties = Entities.getEntityProperties(bullet);
var direction = Vec3.normalize(Vec3.subtract(MyAvatar.position, properties.position));
makeLight(null, properties.position, 10);
Audio.playSound(explosionSound, { position: Vec3.sum(MyAvatar.position, direction), volume: 1.0 });
bullet = null;
}
function maybe() { // every user checks their distance and tries to claim if close enough.
var PROBABILITY_OF_SHOOT = 0.015;
properties = Entities.getEntityProperties(entityID);
countAvatars();
if (nearbyAvatars && (Math.random() < 1 / nearbyAvatars)) {
move();
}
if (nearbyAvatarsInRange && (Math.random() < PROBABILITY_OF_SHOOT / nearbyAvatarsInRange)) {
shoot();
}
var TIME_TO_NEXT_CHECK = 33;
timeoutID = Script.setTimeout(maybe, TIME_TO_NEXT_CHECK);
}
this.preload = function (givenEntityID) {
printDebug("preload airship v1...");
entityID = givenEntityID;
properties = Entities.getEntityProperties(entityID);
timeoutID = Script.setTimeout(maybe, minimumDelay);
};
this.unload = function () {
printDebug("unload airship...");
if (timeoutID !== undefined) {
Script.clearTimeout(timeoutID);
}
if (lightTimeoutID !== undefined) {
Script.clearTimeout(lightTimeoutID);
}
if (spotlight !== null) {
Entities.deleteEntity(spotlight);
}
};
function makeParticles(position, parent, lifespan) {
particles = Entities.addEntity({
type: 'ParticleEffect',
position: position,
parentID: parent,
color: {
red: 70,
green: 70,
blue: 70
},
isEmitting: 1,
maxParticles: 1000,
lifetime: lifespan,
lifespan: lifespan / 3,
emitRate: 80,
emitSpeed: 0,
speedSpread: 1.0,
emitRadiusStart: 1,
polarStart: -Math.PI/8,
polarFinish: Math.PI/8,
azimuthStart: -Math.PI/4,
azimuthFinish: Math.PI/4,
emitAcceleration: { x: 0, y: 0, z: 0 },
particleRadius: 0.25,
radiusSpread: 0.1,
radiusStart: 0.3,
radiusFinish: 0.15,
colorSpread: {
red: 100,
green: 100,
blue: 0
},
colorStart: {
red: 125,
green: 125,
blue: 125
},
colorFinish: {
red: 10,
green: 10,
blue: 10
},
alpha: 0.5,
alphaSpread: 0,
alphaStart: 1,
alphaFinish: 0,
emitterShouldTrail: true,
textures: 'https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png'
});
}
})

View file

@ -0,0 +1,59 @@
// makeAirship.js
//
// // Created by Philip Rosedale on March 7, 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var SIZE = 0.2;
var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere"
var MODEL_URL = "https://s3.amazonaws.com/hifi-public/philip/airship_compact.fbx"
var MODEL_DIMENSION = { x: 19.257, y: 24.094, z: 40.3122 };
var ENTITY_URL = "https://s3.amazonaws.com/hifi-public/scripts/airship/airship.js";
var LIFETIME = 3600 * 48;
var GRAVITY = { x: 0, y: 0, z: 0 };
var DAMPING = 0.05;
var ANGULAR_DAMPING = 0.01;
var collidable = true;
var gravity = true;
var HOW_FAR_IN_FRONT_OF_ME = 30;
var HOW_FAR_ABOVE_ME = 15;
var leaveBehind = true;
var shipLocation = Vec3.sum(MyAvatar.position, Vec3.multiply(HOW_FAR_IN_FRONT_OF_ME, Quat.getFront(Camera.orientation)));
shipLocation.y += HOW_FAR_ABOVE_ME;
var airship = Entities.addEntity({
type: TYPE,
modelURL: MODEL_URL,
name: "airship",
position: shipLocation,
dimensions: (TYPE == "Model") ? MODEL_DIMENSION : { x: SIZE, y: SIZE, z: SIZE },
damping: DAMPING,
angularDamping: ANGULAR_DAMPING,
gravity: (gravity ? GRAVITY : { x: 0, y: 0, z: 0}),
dynamic: collidable,
lifetime: LIFETIME,
animation: {url: MODEL_URL, running: true, currentFrame: 0, loop: true},
script: ENTITY_URL
});
function scriptEnding() {
if (!leaveBehind) {
Entities.deleteEntity(airship);
}
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -451,6 +451,37 @@
var elPreviewCameraButton = document.getElementById("preview-camera-button"); var elPreviewCameraButton = document.getElementById("preview-camera-button");
var urlUpdaters = document.getElementsByClassName("update-url-version");
var PARAM_REGEXP = /(?:\?)(\S+)/; // Check if this has any parameters.
var TIMESTAMP_REGEXP = /(&?HFTime=\d+)/;
var refreshEvent = function(event){
var urlElement = event.target.parentElement.getElementsByClassName("url")[0];
var content = urlElement.value;
var date = new Date();
var timeStamp = date.getTime();
if(content.length > 0){
if(PARAM_REGEXP.test(content)){
// Has params, so lets remove existing definition and append again.
content = content.replace(TIMESTAMP_REGEXP,"") + "&";
}else{
content += "?";
}
content = content.replace("?&","?");
urlElement.value = content + "HFTime=" + timeStamp;
}
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true );
urlElement.dispatchEvent(evt);
};
for(var index = 0; index < urlUpdaters.length; index++){
var urlUpdater = urlUpdaters[index];
urlUpdater.addEventListener("click", refreshEvent);
}
if (window.EventBridge !== undefined) { if (window.EventBridge !== undefined) {
var properties; var properties;
EventBridge.scriptEventReceived.connect(function(data) { EventBridge.scriptEventReceived.connect(function(data) {
@ -1185,6 +1216,7 @@
<div class="label">Ambient URL</div> <div class="label">Ambient URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-zone-key-ambient-url" class="url"> <input type="text" id="property-zone-key-ambient-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1262,6 +1294,7 @@
<div class="label">Skybox URL</div> <div class="label">Skybox URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-zone-skybox-url" class="url"> <input type="text" id="property-zone-skybox-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1273,6 +1306,7 @@
<div class="label">Source URL</div> <div class="label">Source URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-web-source-url" class="url"> <input type="text" id="property-web-source-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1286,12 +1320,14 @@
<div class="label">Href - Hifi://address</div> <div class="label">Href - Hifi://address</div>
<div class="value"> <div class="value">
<input id="property-hyperlink-href" class="url"> <input id="property-hyperlink-href" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
<div class="hyperlink-section property"> <div class="hyperlink-section property">
<div class="label">Description</div> <div class="label">Description</div>
<div class="value"> <div class="value">
<input id="property-hyperlink-description" class="url"> <input id="property-hyperlink-description" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1375,16 +1411,19 @@
<div class="label">X-axis Texture URL</div> <div class="label">X-axis Texture URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-x-texture-url" class="url"> <input type="text" id="property-x-texture-url" class="url">
<div class="update-url-version"></div>
</div> </div>
<div class="label">Y-axis Texture URL</div> <div class="label">Y-axis Texture URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-y-texture-url" class="url"> <input type="text" id="property-y-texture-url" class="url">
<div class="update-url-version"></div>
</div> </div>
<div class="label">Z-axis Texture URL</div> <div class="label">Z-axis Texture URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-z-texture-url" class="url"> <input type="text" id="property-z-texture-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1566,6 +1605,7 @@
<div class="label">Collision Sound URL</div> <div class="label">Collision Sound URL</div>
<div class="value"> <div class="value">
<input id="property-collision-sound-url" class="url"> <input id="property-collision-sound-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1583,6 +1623,7 @@
</div> </div>
<div class="value"> <div class="value">
<input id="property-script-url" class="url"> <input id="property-script-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1595,6 +1636,7 @@
<div class="label">Model URL</div> <div class="label">Model URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-model-url" class="url"> <input type="text" id="property-model-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
@ -1613,12 +1655,14 @@
<div class="label">Compound Shape URL</div> <div class="label">Compound Shape URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-compound-shape-url" class="url"> <input type="text" id="property-compound-shape-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
<div class="model-section property"> <div class="model-section property">
<div class="label">Animation URL</div> <div class="label">Animation URL</div>
<div class="value"> <div class="value">
<input type="text" id="property-model-animation-url" class="url"> <input type="text" id="property-model-animation-url" class="url">
<div class="update-url-version"></div>
</div> </div>
</div> </div>
<div class="model-section property"> <div class="model-section property">

View file

@ -10,50 +10,11 @@
var EventBridge; var EventBridge;
EventBridgeConnectionProxy = function(parent) { openEventBridge = function(callback) {
this.parent = parent; new QWebChannel(qt.webChannelTransport, function(channel) {
this.realSignal = this.parent.realBridge.scriptEventReceived console.log("uid " + EventBridgeUid);
this.webWindowId = this.parent.webWindow.windowId; EventBridge = channel.objects[EventBridgeUid];
} callback(EventBridge);
EventBridgeConnectionProxy.prototype.connect = function(callback) {
var that = this;
this.realSignal.connect(function(id, message) {
if (id === that.webWindowId) { callback(message); }
}); });
} }
EventBridgeProxy = function(webWindow) {
this.webWindow = webWindow;
this.realBridge = this.webWindow.eventBridge;
this.scriptEventReceived = new EventBridgeConnectionProxy(this);
}
EventBridgeProxy.prototype.emitWebEvent = function(data) {
this.realBridge.emitWebEvent(data);
}
openEventBridge = function(callback) {
EVENT_BRIDGE_URI = "ws://localhost:51016";
socket = new WebSocket(this.EVENT_BRIDGE_URI);
socket.onclose = function() {
console.error("web channel closed");
};
socket.onerror = function(error) {
console.error("web channel error: " + error);
};
socket.onopen = function() {
channel = new QWebChannel(socket, function(channel) {
console.log("Document url is " + document.URL);
var webWindow = channel.objects[document.URL.toLowerCase()];
console.log("WebWindow is " + webWindow)
eventBridgeProxy = new EventBridgeProxy(webWindow);
EventBridge = eventBridgeProxy;
if (callback) { callback(eventBridgeProxy); }
});
}
}

View file

@ -4,21 +4,17 @@
<script type="text/javascript" src="jquery-2.1.4.min.js"></script> <script type="text/javascript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="eventBridgeLoader.js"></script> <script type="text/javascript" src="eventBridgeLoader.js"></script>
<script> <script>
var myBridge;
window.onload = function() { window.onload = function() {
openEventBridge(function(eventBridge) { openEventBridge(function() {
myBridge = eventBridge; EventBridge.scriptEventReceived.connect(function(message) {
myBridge.scriptEventReceived.connect(function(message) {
console.log("HTML side received message: " + message); console.log("HTML side received message: " + message);
}); });
}); });
} }
testClick = function() { testClick = function() {
myBridge.emitWebEvent("HTML side sending message - button click"); EventBridge.emitWebEvent(["Foo", "Bar", { "baz": 1} ]);
} }
</script> </script>
</head> </head>

View file

@ -134,8 +134,18 @@ textarea {
resize: vertical; resize: vertical;
} }
.update-url-version{
width:17px;
height:17px;
float:right;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAQAAACRZI9xAAABAUlEQVQoz33RvyvEARjH8SdlUX6UbhFJfgwmE3aTxWEiitxqN/gH7IxCKTcymLiJFIOJsihiUKcrCYnuXoZv+F45n2d4lvfz6fPpCSFs6BO1JllvLmXk5F37dC0vJ5NGmlBxoFp7Rn6RDpRRtG5KtynrijhLoBBGQcllKkFWAXsyCbKEFxeGqmJmFZFLkE639vX+12hCSVft0nUR8RbLcRN/aSHGIqL2tVYPconL32qLtaiL88Rl0qtXc+pTDv1OPGMlidtgS8Wp2RR05FEFM98/GlRQ9mTHvB7jVt2rKGPAT9xh2z6qfnToHV1SjZpN23Tl051di1oco9W/pdttaBRfEhFXOZV7vEsAAAAASUVORK5CYII=);
padding:0 !important;
margin:0 2px 0 0 !important;
}
input.url { input.url {
width: 100%; width:85%;
padding-right: 20px;
} }
input.coord { input.coord {

View file

@ -198,6 +198,16 @@ function createColorPicker(key) {
settings[key] = colorArray; settings[key] = colorArray;
var controller = gui.addColor(settings, key); var controller = gui.addColor(settings, key);
controller.onChange(function(value) { controller.onChange(function(value) {
// Handle hex colors
if(_.isString(value) && value[0] === '#') {
const BASE_HEX = 16;
var colorRegExResult = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value);
value = [
parseInt(colorRegExResult[1], BASE_HEX),
parseInt(colorRegExResult[2], BASE_HEX),
parseInt(colorRegExResult[3], BASE_HEX)
];
}
var obj = {}; var obj = {};
obj[key] = convertColorArrayToObject(value); obj[key] = convertColorArrayToObject(value);
writeVec3ToInterface(obj); writeVec3ToInterface(obj);

View file

@ -8,26 +8,14 @@ webWindow.eventBridge.webEventReceived.connect(function(data) {
print("JS Side event received: " + data); print("JS Side event received: " + data);
}); });
var titles = ["A", "B", "C"];
var titleIndex = 0;
Script.setInterval(function() { Script.setInterval(function() {
webWindow.eventBridge.emitScriptEvent("JS Event sent"); var message = [ Math.random(), Math.random() ];
var size = webWindow.size; print("JS Side sending: " + message);
var position = webWindow.position; webWindow.emitScriptEvent(message);
print("Window url: " + webWindow.url) }, 5 * 1000);
print("Window visible: " + webWindow.visible)
print("Window size: " + size.x + "x" + size.y)
print("Window pos: " + position.x + "x" + position.y)
webWindow.setVisible(!webWindow.visible);
webWindow.setTitle(titles[titleIndex]);
webWindow.setSize(320 + Math.random() * 100, 240 + Math.random() * 100);
titleIndex += 1;
titleIndex %= titles.length;
}, 2 * 1000);
Script.setTimeout(function() { Script.scriptEnding.connect(function(){
print("Closing script");
webWindow.close(); webWindow.close();
Script.stop(); webWindow.deleteLater();
}, 15 * 1000) });

View file

@ -1,6 +1,7 @@
import QtQuick 2.3 import QtQuick 2.3
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtWebEngine 1.1 import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows" as Windows import "windows" as Windows
import "controls" as Controls import "controls" as Controls
@ -15,11 +16,22 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false destroyOnCloseButton: false
property alias source: webview.url property alias source: webview.url
property alias webChannel: webview.webChannel
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
// This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a
// missing signal
signal sendToScript(var message);
Controls.WebView { Controls.WebView {
id: webview id: webview
url: "about:blank" url: "about:blank"
anchors.fill: parent anchors.fill: parent
focus: true focus: true
onUrlChanged: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
Component.onCompleted: webview.runJavaScript("EventBridgeUid = \"" + uid + "\";");
} }
} // dialog } // dialog

View file

@ -37,14 +37,33 @@ Windows.Window {
Repeater { Repeater {
model: 4 model: 4
Tab { Tab {
// Force loading of the content even if the tab is not visible
// (required for letting the C++ code access the webview)
active: true active: true
enabled: false; enabled: false
// we need to store the original url here for future identification
property string originalUrl: ""; property string originalUrl: "";
onEnabledChanged: toolWindow.updateVisiblity();
Controls.WebView { Controls.WebView {
id: webView; id: webView;
// we need to store the original url here for future identification
// A unique identifier to let the HTML JS find the event bridge
// object (our C++ wrapper)
property string uid;
anchors.fill: parent anchors.fill: parent
enabled: false
// This is for JS/QML communication, which is unused in a WebWindow,
// but not having this here results in spurious warnings about a
// missing signal
signal sendToScript(var message);
onUrlChanged: webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
onEnabledChanged: toolWindow.updateVisiblity();
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
webView.runJavaScript("EventBridgeUid = \"" + uid + "\";");
}
}
} }
} }
} }
@ -113,20 +132,23 @@ Windows.Window {
var tab = tabView.getTab(index); var tab = tabView.getTab(index);
tab.title = ""; tab.title = "";
tab.originalUrl = "";
tab.enabled = false; tab.enabled = false;
tab.originalUrl = "";
tab.item.url = "about:blank";
tab.item.enabled = false;
} }
function addWebTab(properties) { function addWebTab(properties) {
if (!properties.source) { if (!properties.source) {
console.warn("Attempted to open Web Tool Pane without URL") console.warn("Attempted to open Web Tool Pane without URL");
return; return;
} }
var existingTabIndex = findIndexForUrl(properties.source); var existingTabIndex = findIndexForUrl(properties.source);
if (existingTabIndex >= 0) { if (existingTabIndex >= 0) {
console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source) console.log("Existing tab " + existingTabIndex + " found with URL " + properties.source);
return tabView.getTab(existingTabIndex); var tab = tabView.getTab(existingTabIndex);
return tab.item;
} }
var freeTabIndex = findFreeTab(); var freeTabIndex = findFreeTab();
@ -135,25 +157,22 @@ Windows.Window {
return; return;
} }
var newTab = tabView.getTab(freeTabIndex);
newTab.title = properties.title || "Unknown";
newTab.originalUrl = properties.source;
newTab.item.url = properties.source;
newTab.active = true;
if (properties.width) { if (properties.width) {
tabView.width = Math.min(Math.max(tabView.width, properties.width), tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
toolWindow.maxSize.x);
} }
if (properties.height) { if (properties.height) {
tabView.height = Math.min(Math.max(tabView.height, properties.height), tabView.height = Math.min(Math.max(tabView.height, properties.height), toolWindow.maxSize.y);
toolWindow.maxSize.y);
} }
console.log("Updating visibility based on child tab added"); var tab = tabView.getTab(freeTabIndex);
newTab.enabledChanged.connect(updateVisiblity) tab.title = properties.title || "Unknown";
updateVisiblity(); tab.enabled = true;
return newTab tab.originalUrl = properties.source;
var result = tab.item;
result.enabled = true;
result.url = properties.source;
return result;
} }
} }

24
interface/resources/qml/controls-uit/ComboBox.qml Normal file → Executable file
View file

@ -162,6 +162,30 @@ FocusScope {
height: 480 height: 480
width: root.width + 4 width: root.width + 4
style: ScrollViewStyle {
decrementControl: Item {
visible: false
}
incrementControl: Item {
visible: false
}
scrollBarBackground: Rectangle{
implicitWidth: 14
color: hifi.colors.baseGray
}
handle:
Rectangle {
implicitWidth: 8
anchors.left: parent.left
anchors.leftMargin: 3
anchors.top: parent.top
anchors.bottom: parent.bottom
radius: 3
color: hifi.colors.lightGrayText
}
}
ListView { ListView {
id: listView id: listView
height: textField.height * count * 1.4 height: textField.height * count * 1.4

24
interface/resources/qml/controls-uit/SpinBox.qml Normal file → Executable file
View file

@ -35,7 +35,6 @@ SpinBox {
style: SpinBoxStyle { style: SpinBoxStyle {
id: spinStyle id: spinStyle
background: Rectangle { background: Rectangle {
id: backgrondRec
color: isLightColorScheme color: isLightColorScheme
? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray) ? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray)
: (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow) : (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow)
@ -91,4 +90,27 @@ SpinBox {
color: spinBox.colorLabelInside color: spinBox.colorLabelInside
visible: spinBox.labelInside != "" visible: spinBox.labelInside != ""
} }
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onWheel: {
if(spinBox.focus)
wheel.accepted = false
else
wheel.accepted = true
}
onPressed: {
mouse.accepted = false
}
onReleased: {
mouse.accepted = false
}
onClicked: {
mouse.accepted = false
}
onDoubleClicked: {
mouse.accepted = false
}
}
} }

View file

@ -59,6 +59,7 @@ WebEngineView {
request.openIn(newWindow.webView) request.openIn(newWindow.webView)
} }
// This breaks the webchannel used for passing messages. Fixed in Qt 5.6
profile: desktop.browserProfile // See https://bugreports.qt.io/browse/QTBUG-49521
//profile: desktop.browserProfile
} }

View file

@ -4,18 +4,14 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
//import "../../windows"
import "../../styles-uit" import "../../styles-uit"
import "../../controls-uit" as HifiControls import "../../controls-uit" as HifiControls
import "../../windows-uit" import "../../windows-uit"
import "attachments" import "attachments"
Window { Window {
id: root id: root
title: "Attachments Dialog" title: "Attachments"
objectName: "AttachmentsDialog" objectName: "AttachmentsDialog"
width: 600 width: 600
height: 600 height: 600
@ -43,6 +39,7 @@ Window {
listView.model.append({}); listView.model.append({});
} }
} }
Column { Column {
width: pane.contentWidth width: pane.contentWidth
@ -55,14 +52,22 @@ Window {
Rectangle { Rectangle {
id: attachmentsBackground id: attachmentsBackground
anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 } anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 }
color: hifi.colors.lightGrayText color: hifi.colors.baseGrayShadow
radius: 4 radius: 4
ScrollView{ ScrollView {
id: scrollView id: scrollView
anchors.fill: parent anchors.fill: parent
anchors.margins: 4 anchors.margins: 4
style: ScrollViewStyle { style: ScrollViewStyle {
padding {
top: 0
right: 0
bottom: 0
}
decrementControl: Item { decrementControl: Item {
visible: false visible: false
} }
@ -72,16 +77,29 @@ Window {
scrollBarBackground: Rectangle{ scrollBarBackground: Rectangle{
implicitWidth: 14 implicitWidth: 14
color: hifi.colors.baseGray color: hifi.colors.baseGray
} radius: 4
Rectangle {
// Make top left corner of scrollbar appear square
width: 8
height: 4
color: hifi.colors.baseGray
anchors.top: parent.top
anchors.horizontalCenter: parent.left
}
}
handle: handle:
Rectangle { Rectangle {
implicitWidth: 8 implicitWidth: 8
anchors.left: parent.left anchors {
anchors.leftMargin: 3 left: parent.left
anchors.top: parent.top leftMargin: 3
anchors.bottom: parent.bottom top: parent.top
radius: 3 topMargin: 3
bottom: parent.bottom
bottomMargin: 4
}
radius: 4
color: hifi.colors.lightGrayText color: hifi.colors.lightGrayText
} }
} }
@ -90,8 +108,9 @@ Window {
id: listView id: listView
model: ListModel {} model: ListModel {}
delegate: Item { delegate: Item {
id: attachmentDelegate
implicitHeight: attachmentView.height + 8; implicitHeight: attachmentView.height + 8;
implicitWidth: attachmentView.width; implicitWidth: attachmentView.width
Attachment { Attachment {
id: attachmentView id: attachmentView
width: scrollView.width width: scrollView.width
@ -113,7 +132,7 @@ Window {
anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 } anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 }
text: "New Attachment" text: "New Attachment"
color: hifi.buttons.black color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark
onClicked: { onClicked: {
var template = { var template = {
modelUrl: "", modelUrl: "",
@ -133,15 +152,15 @@ Window {
id: buttonRow id: buttonRow
spacing: 8 spacing: 8
anchors { right: parent.right; bottom: parent.bottom; margins: 8 } anchors { right: parent.right; bottom: parent.bottom; margins: 8 }
HifiControls.Button {
action: cancelAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
}
HifiControls.Button { HifiControls.Button {
action: okAction action: okAction
color: hifi.buttons.black color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark
}
HifiControls.Button {
action: cancelAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
} }
} }

View file

@ -4,11 +4,8 @@ import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
//import "../../../windows"
//import "../../../controls" as VrControls
import "." import "."
import ".." import ".."
import "../../../styles-uit" import "../../../styles-uit"
import "../../../controls-uit" as HifiControls import "../../../controls-uit" as HifiControls
import "../../../windows-uit" import "../../../windows-uit"
@ -33,13 +30,13 @@ Item {
Column { Column {
y: 8 y: 8
id: column id: column
anchors { left: parent.left; right: parent.right; margins: 8 } anchors { left: parent.left; right: parent.right; margins: 20 }
spacing: 8 spacing: 8
Item { Item {
height: modelChooserButton.height + urlLabel.height height: modelChooserButton.height + urlLabel.height + 4
anchors { left: parent.left; right: parent.right;} anchors { left: parent.left; right: parent.right;}
Text { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL:"; width: 80; anchors.top: parent.top;} HifiControls.Label { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL"; anchors.top: parent.top;}
HifiControls.TextField { HifiControls.TextField {
id: modelUrl; id: modelUrl;
height: jointChooser.height; height: jointChooser.height;
@ -60,12 +57,12 @@ Item {
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark
anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter } anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter }
Component { Component {
id: modelBrowserBuiler; id: modelBrowserBuilder;
ModelBrowserDialog {} ModelBrowserDialog {}
} }
onClicked: { onClicked: {
var browser = modelBrowserBuiler.createObject(desktop); var browser = modelBrowserBuilder.createObject(desktop);
browser.selected.connect(function(newModelUrl){ browser.selected.connect(function(newModelUrl){
modelUrl.text = newModelUrl; modelUrl.text = newModelUrl;
}) })
@ -74,12 +71,11 @@ Item {
} }
Item { Item {
height: jointChooser.height + jointLabel.height height: jointChooser.height + jointLabel.height + 4
anchors { left: parent.left; right: parent.right; } anchors { left: parent.left; right: parent.right; }
Text { HifiControls.Label {
id: jointLabel; id: jointLabel;
width: 80; text: "Joint";
text: "Joint:";
color: hifi.colors.lightGrayText; color: hifi.colors.lightGrayText;
anchors.top: parent.top anchors.top: parent.top
} }
@ -99,9 +95,9 @@ Item {
} }
Item { Item {
height: translation.height + translationLabel.height height: translation.height + translationLabel.height + 4
anchors { left: parent.left; right: parent.right; } anchors { left: parent.left; right: parent.right; }
Text { id: translationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Translation:"; anchors.top: parent.top; } HifiControls.Label { id: translationLabel; color: hifi.colors.lightGrayText; text: "Translation"; anchors.top: parent.top; }
Translation { Translation {
id: translation; id: translation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom} anchors { left: parent.left; right: parent.right; bottom: parent.bottom}
@ -116,9 +112,9 @@ Item {
} }
Item { Item {
height: rotation.height + rotationLabel.height height: rotation.height + rotationLabel.height + 4
anchors { left: parent.left; right: parent.right; } anchors { left: parent.left; right: parent.right; }
Text { id: rotationLabel; width: 80; color: hifi.colors.lightGrayText; text: "Rotation:"; anchors.top: parent.top; } HifiControls.Label { id: rotationLabel; color: hifi.colors.lightGrayText; text: "Rotation"; anchors.top: parent.top; }
Rotation { Rotation {
id: rotation; id: rotation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
@ -133,45 +129,58 @@ Item {
} }
Item { Item {
height: scaleSpinner.height + scaleLabel.height height: scaleItem.height
anchors { left: parent.left; right: parent.right; } anchors { left: parent.left; right: parent.right; }
Text { id: scaleLabel; width: 80; color: hifi.colors.lightGrayText; text: "Scale:"; anchors.top: parent.top; }
HifiControls.SpinBox { Item {
id: scaleSpinner; id: scaleItem
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; } height: scaleSpinner.height + scaleLabel.height + 4
decimals: 1; width: parent.width / 3 - 8
minimumValue: 0.1 anchors { right: parent.right; }
maximumValue: 10 HifiControls.Label { id: scaleLabel; color: hifi.colors.lightGrayText; text: "Scale"; anchors.top: parent.top; }
stepSize: 0.1; HifiControls.SpinBox {
value: attachment ? attachment.scale : 1.0 id: scaleSpinner;
colorScheme: hifi.colorSchemes.dark anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
onValueChanged: { decimals: 1;
if (completed && attachment && attachment.scale !== value) { minimumValue: 0.1
attachment.scale = value; maximumValue: 10
updateAttachment(); stepSize: 0.1;
value: attachment ? attachment.scale : 1.0
colorScheme: hifi.colorSchemes.dark
onValueChanged: {
if (completed && attachment && attachment.scale !== value) {
attachment.scale = value;
updateAttachment();
}
}
}
}
Item {
id: isSoftItem
height: scaleSpinner.height
anchors {
left: parent.left
bottom: parent.bottom
}
HifiControls.CheckBox {
id: soft
text: "Is soft"
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
checked: attachment ? attachment.soft : false
colorScheme: hifi.colorSchemes.dark
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
} }
} }
} }
} }
Item {
height: soft.height
anchors { left: parent.left; right: parent.right; }
Text { id: softLabel; width: 80; color: hifi.colors.lightGrayText; text: "Is soft"; anchors.left: soft.right; anchors.leftMargin: 8; }
HifiControls.CheckBox {
id: soft;
anchors { left: parent.left; bottom: parent.bottom;}
checked: attachment ? attachment.soft : false
colorScheme: hifi.colorSchemes.dark
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
}
}
}
HifiControls.Button { HifiControls.Button {
color: hifi.buttons.black color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark

View file

@ -1,7 +1,7 @@
import "." import "."
Vector3 { Vector3 {
decimals: 2; decimals: 3;
stepSize: 0.01; stepSize: 0.01;
maximumValue: 10 maximumValue: 10
minimumValue: -10 minimumValue: -10

View file

@ -195,6 +195,7 @@ static const QString FBX_EXTENSION = ".fbx";
static const QString OBJ_EXTENSION = ".obj"; static const QString OBJ_EXTENSION = ".obj";
static const QString AVA_JSON_EXTENSION = ".ava.json"; static const QString AVA_JSON_EXTENSION = ".ava.json";
static const int MSECS_PER_SEC = 1000;
static const int MIRROR_VIEW_TOP_PADDING = 5; static const int MIRROR_VIEW_TOP_PADDING = 5;
static const int MIRROR_VIEW_LEFT_PADDING = 10; static const int MIRROR_VIEW_LEFT_PADDING = 10;
static const int MIRROR_VIEW_WIDTH = 265; static const int MIRROR_VIEW_WIDTH = 265;
@ -239,7 +240,7 @@ class DeadlockWatchdogThread : public QThread {
public: public:
static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1;
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 10 * USECS_PER_SECOND; static const unsigned long MAX_HEARTBEAT_AGE_USECS = 15 * USECS_PER_SECOND;
// Set the heartbeat on launch // Set the heartbeat on launch
DeadlockWatchdogThread() { DeadlockWatchdogThread() {
@ -365,7 +366,7 @@ bool setupEssentials(int& argc, char** argv) {
Setting::preInit(); Setting::preInit();
CrashHandler::checkForAndHandleCrash(); bool previousSessionCrashed = CrashHandler::checkForAndHandleCrash();
CrashHandler::writeRunningMarkerFiler(); CrashHandler::writeRunningMarkerFiler();
qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); qAddPostRoutine(CrashHandler::deleteRunningMarkerFile);
@ -427,7 +428,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<InterfaceParentFinder>(); DependencyManager::set<InterfaceParentFinder>();
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp); DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
DependencyManager::set<CompositorHelper>(); DependencyManager::set<CompositorHelper>();
return true; return previousSessionCrashed;
} }
// FIXME move to header, or better yet, design some kind of UI manager // FIXME move to header, or better yet, design some kind of UI manager
@ -448,10 +449,13 @@ PluginContainer* _pluginContainer;
OffscreenGLCanvas* _chromiumShareContext { nullptr }; OffscreenGLCanvas* _chromiumShareContext { nullptr };
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
QApplication(argc, argv), QApplication(argc, argv),
_window(new MainWindow(desktop())), _window(new MainWindow(desktop())),
_dependencyManagerIsSetup(setupEssentials(argc, argv)), _sessionRunTimer(startupTimer),
_previousSessionCrashed(setupEssentials(argc, argv)),
_undoStackScriptingInterface(&_undoStack), _undoStackScriptingInterface(&_undoStack),
_frameCount(0), _frameCount(0),
_fps(60.0f), _fps(60.0f),
@ -601,7 +605,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SEC;
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>(); auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
@ -623,7 +627,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// connect to appropriate slots on AccountManager // connect to appropriate slots on AccountManager
AccountManager& accountManager = AccountManager::getInstance(); AccountManager& accountManager = AccountManager::getInstance();
const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * MSECS_PER_SEC;
connect(&balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); connect(&balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance);
balanceUpdateTimer.start(BALANCE_UPDATE_INTERVAL_MSECS); balanceUpdateTimer.start(BALANCE_UPDATE_INTERVAL_MSECS);
@ -638,7 +642,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
accountManager.setIsAgent(true); accountManager.setIsAgent(true);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
UserActivityLogger::getInstance().launch(applicationVersion()); // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get());
// once the event loop has started, check and signal for an access token // once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
@ -1409,7 +1415,7 @@ void Application::paintGL() {
_lastFramesPerSecondUpdate = now; _lastFramesPerSecondUpdate = now;
} }
PROFILE_RANGE(__FUNCTION__); PROFILE_RANGE_EX(__FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
PerformanceTimer perfTimer("paintGL"); PerformanceTimer perfTimer("paintGL");
if (nullptr == _displayPlugin) { if (nullptr == _displayPlugin) {
@ -2548,11 +2554,12 @@ void Application::idle(uint64_t now) {
return; return;
} }
PROFILE_RANGE(__FUNCTION__);
// We're going to execute idle processing, so restart the last idle timer // We're going to execute idle processing, so restart the last idle timer
_lastTimeUpdated.start(); _lastTimeUpdated.start();
{ {
PROFILE_RANGE(__FUNCTION__);
static uint64_t lastIdleStart{ now }; static uint64_t lastIdleStart{ now };
uint64_t idleStartToStartDuration = now - lastIdleStart; uint64_t idleStartToStartDuration = now - lastIdleStart;
if (idleStartToStartDuration != 0) { if (idleStartToStartDuration != 0) {
@ -2804,6 +2811,7 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
void Application::loadSettings() { void Application::loadSettings() {
sessionRunTime.set(0); // Just clean living. We're about to saveSettings, which will update value.
DependencyManager::get<AudioClient>()->loadSettings(); DependencyManager::get<AudioClient>()->loadSettings();
DependencyManager::get<LODManager>()->loadSettings(); DependencyManager::get<LODManager>()->loadSettings();
@ -2817,6 +2825,7 @@ void Application::loadSettings() {
} }
void Application::saveSettings() { void Application::saveSettings() {
sessionRunTime.set(_sessionRunTimer.elapsed() / MSECS_PER_SEC);
DependencyManager::get<AudioClient>()->saveSettings(); DependencyManager::get<AudioClient>()->saveSettings();
DependencyManager::get<LODManager>()->saveSettings(); DependencyManager::get<LODManager>()->saveSettings();
@ -3138,6 +3147,9 @@ void Application::updateDialogs(float deltaTime) {
} }
void Application::update(float deltaTime) { void Application::update(float deltaTime) {
PROFILE_RANGE_EX(__FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1);
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::update()"); PerformanceWarning warn(showWarnings, "Application::update()");
@ -3228,7 +3240,9 @@ void Application::update(float deltaTime) {
controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND); controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND); controller::Pose rightHandPose = userInputMapper->getPoseState(controller::Action::RIGHT_HAND);
auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()); auto myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
myAvatar->setHandControllerPosesInWorldFrame(leftHandPose.transform(myAvatarMatrix), rightHandPose.transform(myAvatarMatrix)); auto worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix());
auto avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
myAvatar->setHandControllerPosesInSensorFrame(leftHandPose.transform(avatarToSensorMatrix), rightHandPose.transform(avatarToSensorMatrix));
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
updateDialogs(deltaTime); // update various stats dialogs if present updateDialogs(deltaTime); // update various stats dialogs if present
@ -3236,9 +3250,13 @@ void Application::update(float deltaTime) {
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>(); QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
if (_physicsEnabled) { if (_physicsEnabled) {
PROFILE_RANGE_EX("Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("physics"); PerformanceTimer perfTimer("physics");
{ {
PROFILE_RANGE_EX("UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("updateStates)"); PerformanceTimer perfTimer("updateStates)");
static VectorOfMotionStates motionStates; static VectorOfMotionStates motionStates;
_entitySimulation.getObjectsToRemoveFromPhysics(motionStates); _entitySimulation.getObjectsToRemoveFromPhysics(motionStates);
@ -3271,12 +3289,14 @@ void Application::update(float deltaTime) {
}); });
} }
{ {
PROFILE_RANGE_EX("StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("stepSimulation"); PerformanceTimer perfTimer("stepSimulation");
getEntities()->getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation(); _physicsEngine->stepSimulation();
}); });
} }
{ {
PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("havestChanges"); PerformanceTimer perfTimer("havestChanges");
if (_physicsEngine->hasOutgoingChanges()) { if (_physicsEngine->hasOutgoingChanges()) {
getEntities()->getTree()->withWriteLock([&] { getEntities()->getTree()->withWriteLock([&] {
@ -3311,14 +3331,24 @@ void Application::update(float deltaTime) {
qApp->setAvatarSimrateSample(1.0f / deltaTime); qApp->setAvatarSimrateSample(1.0f / deltaTime);
avatarManager->updateOtherAvatars(deltaTime); {
PROFILE_RANGE_EX("OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
avatarManager->updateOtherAvatars(deltaTime);
}
qApp->updateMyAvatarLookAtPosition(); qApp->updateMyAvatarLookAtPosition();
avatarManager->updateMyAvatar(deltaTime); // update sensorToWorldMatrix for camera and hand controllers
myAvatar->updateSensorToWorldMatrix();
{
PROFILE_RANGE_EX("MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
avatarManager->updateMyAvatar(deltaTime);
}
} }
{ {
PROFILE_RANGE_EX("Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("overlays"); PerformanceTimer perfTimer("overlays");
_overlays.update(deltaTime); _overlays.update(deltaTime);
} }
@ -3338,6 +3368,7 @@ void Application::update(float deltaTime) {
// Update my voxel servers with my current voxel query... // Update my voxel servers with my current voxel query...
{ {
PROFILE_RANGE_EX("QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("queryOctree"); PerformanceTimer perfTimer("queryOctree");
quint64 sinceLastQuery = now - _lastQueriedTime; quint64 sinceLastQuery = now - _lastQueriedTime;
const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND;
@ -3374,9 +3405,6 @@ void Application::update(float deltaTime) {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection);
} }
} }
// update sensorToWorldMatrix for rendering camera.
myAvatar->updateSensorToWorldMatrix();
} }
@ -4667,13 +4695,18 @@ qreal Application::getDevicePixelRatio() {
} }
DisplayPlugin* Application::getActiveDisplayPlugin() { DisplayPlugin* Application::getActiveDisplayPlugin() {
std::unique_lock<std::recursive_mutex> lock(_displayPluginLock); DisplayPlugin* result = nullptr;
if (nullptr == _displayPlugin && QThread::currentThread() == thread()) { if (QThread::currentThread() == thread()) {
updateDisplayMode(); if (nullptr == _displayPlugin) {
Q_ASSERT(_displayPlugin); updateDisplayMode();
Q_ASSERT(_displayPlugin);
}
result = _displayPlugin.get();
} else {
std::unique_lock<std::mutex> lock(_displayPluginLock);
result = _displayPlugin.get();
} }
return result;
return _displayPlugin.get();
} }
const DisplayPlugin* Application::getActiveDisplayPlugin() const { const DisplayPlugin* Application::getActiveDisplayPlugin() const {
@ -4791,20 +4824,26 @@ void Application::updateDisplayMode() {
return; return;
} }
if (_displayPlugin) {
_displayPlugin->deactivate();
}
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
// FIXME probably excessive and useless context switching // Make the switch atomic from the perspective of other threads
_offscreenContext->makeCurrent(); {
newDisplayPlugin->activate(); std::unique_lock<std::mutex> lock(_displayPluginLock);
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); if (_displayPlugin) {
_offscreenContext->makeCurrent(); _displayPlugin->deactivate();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); }
_displayPlugin = newDisplayPlugin;
// FIXME probably excessive and useless context switching
_offscreenContext->makeCurrent();
newDisplayPlugin->activate();
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
}
emit activeDisplayPluginChanged(); emit activeDisplayPluginChanged();

View file

@ -377,12 +377,13 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event); void maybeToggleMenuVisible(QMouseEvent* event);
MainWindow* _window; MainWindow* _window;
QElapsedTimer& _sessionRunTimer;
bool _dependencyManagerIsSetup; bool _previousSessionCrashed;
OffscreenGLCanvas* _offscreenContext { nullptr }; OffscreenGLCanvas* _offscreenContext { nullptr };
DisplayPluginPointer _displayPlugin; DisplayPluginPointer _displayPlugin;
std::recursive_mutex _displayPluginLock; std::mutex _displayPluginLock;
InputPluginList _activeInputPlugins; InputPluginList _activeInputPlugins;
bool _activatingDisplayPlugin { false }; bool _activatingDisplayPlugin { false };

View file

@ -27,7 +27,7 @@
static const QString RUNNING_MARKER_FILENAME = "Interface.running"; static const QString RUNNING_MARKER_FILENAME = "Interface.running";
void CrashHandler::checkForAndHandleCrash() { bool CrashHandler::checkForAndHandleCrash() {
QFile runningMarkerFile(runningMarkerFilePath()); QFile runningMarkerFile(runningMarkerFilePath());
if (runningMarkerFile.exists()) { if (runningMarkerFile.exists()) {
QSettings::setDefaultFormat(QSettings::IniFormat); QSettings::setDefaultFormat(QSettings::IniFormat);
@ -42,7 +42,9 @@ void CrashHandler::checkForAndHandleCrash() {
handleCrash(action); handleCrash(action);
} }
} }
return true;
} }
return false;
} }
CrashHandler::Action CrashHandler::promptUserForAction() { CrashHandler::Action CrashHandler::promptUserForAction() {

View file

@ -17,7 +17,7 @@
class CrashHandler { class CrashHandler {
public: public:
static void checkForAndHandleCrash(); static bool checkForAndHandleCrash();
static void writeRunningMarkerFiler(); static void writeRunningMarkerFiler();
static void deleteRunningMarkerFile(); static void deleteRunningMarkerFile();

View file

@ -107,7 +107,7 @@ Menu::Menu() {
auto scriptEngines = DependencyManager::get<ScriptEngines>(); auto scriptEngines = DependencyManager::get<ScriptEngines>();
// Edit > Stop All Scripts... [advanced] // Edit > Stop All Scripts... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0,
scriptEngines.data(), SLOT(stopAllScripts()), scriptEngines.data(), SLOT(stopAllScripts()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
@ -140,7 +140,7 @@ Menu::Menu() {
// Edit > Reload All Content [advanced] // Edit > Reload All Content [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()),
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
// Edit > Package Model... [advanced] // Edit > Package Model... [advanced]
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
@ -153,7 +153,7 @@ Menu::Menu() {
auto audioIO = DependencyManager::get<AudioClient>(); auto audioIO = DependencyManager::get<AudioClient>();
// Audio > Mute // Audio > Mute
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false,
audioIO.data(), SLOT(toggleMute())); audioIO.data(), SLOT(toggleMute()));
// Audio > Show Level Meter // Audio > Show Level Meter
@ -458,7 +458,7 @@ Menu::Menu() {
avatar, SLOT(setEnableMeshVisible(bool))); avatar, SLOT(setEnableMeshVisible(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false, addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, true,
avatar, SLOT(setUseAnimPreAndPostRotations(bool))); avatar, SLOT(setUseAnimPreAndPostRotations(bool)));
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true,
avatar, SLOT(setEnableInverseKinematics(bool))); avatar, SLOT(setEnableInverseKinematics(bool)));
@ -534,7 +534,7 @@ Menu::Menu() {
// Developer > Audio >>> // Developer > Audio >>>
MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio");
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true,
audioIO.data(), SLOT(toggleAudioNoiseReduction())); audioIO.data(), SLOT(toggleAudioNoiseReduction()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false,
audioIO.data(), SLOT(toggleServerEcho())); audioIO.data(), SLOT(toggleServerEcho()));
@ -617,7 +617,7 @@ Menu::Menu() {
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true,
NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); NULL, NULL, UNSPECIFIED_POSITION, "Advanced");
#endif #endif
} }
@ -651,7 +651,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) {
} else if (properties.isCheckable) { } else if (properties.isCheckable) {
menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName, menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName,
properties.shortcutKeySequence, properties.isChecked, properties.shortcutKeySequence, properties.isChecked,
MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()),
requestedPosition, properties.grouping); requestedPosition, properties.grouping);
} else { } else {
menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence, menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence,

View file

@ -187,7 +187,7 @@ void Avatar::simulate(float deltaTime) {
// simple frustum check // simple frustum check
float boundingRadius = getBoundingRadius(); float boundingRadius = getBoundingRadius();
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius); bool inView = qApp->getDisplayViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
if (_shouldAnimate && !_shouldSkipRender && inView) { if (_shouldAnimate && !_shouldSkipRender && inView) {
{ {

View file

@ -418,7 +418,7 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation); _hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation);
} }
// best called at end of main loop, just before rendering. // best called at end of main loop, after physics.
// update sensor to world matrix from current body position and hmd sensor. // update sensor to world matrix from current body position and hmd sensor.
// This is so the correct camera can be used for rendering. // This is so the correct camera can be used for rendering.
void MyAvatar::updateSensorToWorldMatrix() { void MyAvatar::updateSensorToWorldMatrix() {
@ -1087,24 +1087,32 @@ static controller::Pose applyLowVelocityFilter(const controller::Pose& oldPose,
return finalPose; return finalPose;
} }
void MyAvatar::setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right) { void MyAvatar::setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right) {
if (controller::InputDevice::getLowVelocityFilter()) { if (controller::InputDevice::getLowVelocityFilter()) {
auto oldLeftPose = getLeftHandControllerPoseInWorldFrame(); auto oldLeftPose = getLeftHandControllerPoseInSensorFrame();
auto oldRightPose = getRightHandControllerPoseInWorldFrame(); auto oldRightPose = getRightHandControllerPoseInSensorFrame();
_leftHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldLeftPose, left)); _leftHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldLeftPose, left));
_rightHandControllerPoseInWorldFrameCache.set(applyLowVelocityFilter(oldRightPose, right)); _rightHandControllerPoseInSensorFrameCache.set(applyLowVelocityFilter(oldRightPose, right));
} else { } else {
_leftHandControllerPoseInWorldFrameCache.set(left); _leftHandControllerPoseInSensorFrameCache.set(left);
_rightHandControllerPoseInWorldFrameCache.set(right); _rightHandControllerPoseInSensorFrameCache.set(right);
} }
} }
controller::Pose MyAvatar::getLeftHandControllerPoseInSensorFrame() const {
return _leftHandControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getRightHandControllerPoseInSensorFrame() const {
return _rightHandControllerPoseInSensorFrameCache.get();
}
controller::Pose MyAvatar::getLeftHandControllerPoseInWorldFrame() const { controller::Pose MyAvatar::getLeftHandControllerPoseInWorldFrame() const {
return _leftHandControllerPoseInWorldFrameCache.get(); return _leftHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
} }
controller::Pose MyAvatar::getRightHandControllerPoseInWorldFrame() const { controller::Pose MyAvatar::getRightHandControllerPoseInWorldFrame() const {
return _rightHandControllerPoseInWorldFrameCache.get(); return _rightHandControllerPoseInSensorFrameCache.get().transform(getSensorToWorldMatrix());
} }
controller::Pose MyAvatar::getLeftHandControllerPoseInAvatarFrame() const { controller::Pose MyAvatar::getLeftHandControllerPoseInAvatarFrame() const {

View file

@ -247,7 +247,9 @@ public:
virtual void rebuildCollisionShape() override; virtual void rebuildCollisionShape() override;
void setHandControllerPosesInWorldFrame(const controller::Pose& left, const controller::Pose& right); void setHandControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
controller::Pose getLeftHandControllerPoseInSensorFrame() const;
controller::Pose getRightHandControllerPoseInSensorFrame() const;
controller::Pose getLeftHandControllerPoseInWorldFrame() const; controller::Pose getLeftHandControllerPoseInWorldFrame() const;
controller::Pose getRightHandControllerPoseInWorldFrame() const; controller::Pose getRightHandControllerPoseInWorldFrame() const;
controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
@ -451,9 +453,9 @@ private:
bool _hoverReferenceCameraFacingIsCaptured { false }; bool _hoverReferenceCameraFacingIsCaptured { false };
glm::vec3 _hoverReferenceCameraFacing { 0.0f, 0.0f, -1.0f }; // hmd sensor space glm::vec3 _hoverReferenceCameraFacing { 0.0f, 0.0f, -1.0f }; // hmd sensor space
// These are stored in WORLD frame // These are stored in SENSOR frame
ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInWorldFrameCache { controller::Pose() }; ThreadSafeValueCache<controller::Pose> _leftHandControllerPoseInSensorFrameCache { controller::Pose() };
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInWorldFrameCache { controller::Pose() }; ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
float AUDIO_ENERGY_CONSTANT { 0.000001f }; float AUDIO_ENERGY_CONSTANT { 0.000001f };

View file

@ -13,7 +13,7 @@
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimUtil.h" #include "AnimUtil.h"
bool AnimClip::usePreAndPostPoseFromAnim = false; bool AnimClip::usePreAndPostPoseFromAnim = true;
AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) :
AnimNode(AnimNode::Type::Clip, id), AnimNode(AnimNode::Type::Clip, id),

View file

@ -20,6 +20,7 @@
#include <GeometryUtil.h> #include <GeometryUtil.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <DebugDraw.h> #include <DebugDraw.h>
#include <shared/NsightHelpers.h>
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimClip.h" #include "AnimClip.h"
@ -852,6 +853,8 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
PROFILE_RANGE_EX(__FUNCTION__, 0xffff00ff, 0);
setModelOffset(rootTransform); setModelOffset(rootTransform);
if (_animNode) { if (_animNode) {

View file

@ -565,10 +565,10 @@ void AudioClient::updateReverbOptions() {
_zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime());
reverbChanged = true; reverbChanged = true;
} }
//if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { if (_zoneReverbOptions.getWetDryMix() != _receivedAudioStream.getWetLevel()) {
// _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); _zoneReverbOptions.setWetDryMix(_receivedAudioStream.getWetLevel());
// reverbChanged = true; reverbChanged = true;
//} }
if (_reverbOptions != &_zoneReverbOptions) { if (_reverbOptions != &_zoneReverbOptions) {
_reverbOptions = &_zoneReverbOptions; _reverbOptions = &_zoneReverbOptions;

View file

@ -21,7 +21,7 @@
#include <gl/GLWidget.h> #include <gl/GLWidget.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <shared/NsightHelpers.h>
#include <plugins/PluginContainer.h> #include <plugins/PluginContainer.h>
#include <gl/Config.h> #include <gl/Config.h>
#include <gl/GLEscrow.h> #include <gl/GLEscrow.h>
@ -404,7 +404,11 @@ void OpenGLDisplayPlugin::submitOverlayTexture(const gpu::TexturePointer& overla
void OpenGLDisplayPlugin::updateTextures() { void OpenGLDisplayPlugin::updateTextures() {
// FIXME intrduce a GPU wait instead of a CPU/GPU sync point? // FIXME intrduce a GPU wait instead of a CPU/GPU sync point?
#if THREADED_PRESENT
if (_sceneTextureEscrow.fetchSignaledAndRelease(_currentSceneTexture)) { if (_sceneTextureEscrow.fetchSignaledAndRelease(_currentSceneTexture)) {
#else
if (_sceneTextureEscrow.fetchAndReleaseWithGpuWait(_currentSceneTexture)) {
#endif
updateFrameData(); updateFrameData();
} }
@ -527,6 +531,9 @@ void OpenGLDisplayPlugin::internalPresent() {
void OpenGLDisplayPlugin::present() { void OpenGLDisplayPlugin::present() {
incrementPresentCount(); incrementPresentCount();
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
updateTextures(); updateTextures();
if (_currentSceneTexture) { if (_currentSceneTexture) {
// Write all layers to a local framebuffer // Write all layers to a local framebuffer

View file

@ -17,9 +17,9 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <SimpleMovingAverage.h> #include <SimpleMovingAverage.h>
#include <gl/OglplusHelpers.h> #include <gl/OglplusHelpers.h>
#include <gl/GLEscrow.h>
#define THREADED_PRESENT 1 #define THREADED_PRESENT 1
#include <gl/GLEscrow.h>
class OpenGLDisplayPlugin : public DisplayPlugin { class OpenGLDisplayPlugin : public DisplayPlugin {
protected: protected:

View file

@ -19,6 +19,7 @@
#include <gpu/GLBackend.h> #include <gpu/GLBackend.h>
#include <CursorManager.h> #include <CursorManager.h>
#include <gl/GLWidget.h> #include <gl/GLWidget.h>
#include <shared/NsightHelpers.h>
#include "../Logging.h" #include "../Logging.h"
#include "../CompositorHelper.h" #include "../CompositorHelper.h"
@ -106,6 +107,9 @@ void HmdDisplayPlugin::compositePointer() {
} }
void HmdDisplayPlugin::internalPresent() { void HmdDisplayPlugin::internalPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
// Composite together the scene, overlay and mouse cursor // Composite together the scene, overlay and mouse cursor
hmdPresent(); hmdPresent();
@ -149,6 +153,8 @@ void HmdDisplayPlugin::internalPresent() {
}); });
swapBuffers(); swapBuffers();
} }
postPreview();
} }
void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {

View file

@ -31,6 +31,7 @@ public:
protected: protected:
virtual void hmdPresent() = 0; virtual void hmdPresent() = 0;
virtual bool isHmdMounted() const = 0; virtual bool isHmdMounted() const = 0;
virtual void postPreview() {};
void internalActivate() override; void internalActivate() override;
void compositeOverlay() override; void compositeOverlay() override;

View file

@ -113,14 +113,18 @@ QVariantMap RenderableModelEntityItem::parseTexturesToMap(QString textures) {
return _originalTexturesMap; return _originalTexturesMap;
} }
QString jsonTextures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; // Legacy: a ,\n-delimited list of filename:"texturepath"
if (*textures.cbegin() != '{') {
textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}";
}
QJsonParseError error; QJsonParseError error;
QJsonDocument texturesAsJson = QJsonDocument::fromJson(jsonTextures.toUtf8(), &error); QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures; qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << _textures;
return _originalTexturesMap;
} }
QJsonObject texturesAsJsonObject = texturesAsJson.object(); return texturesJson.object().toVariantMap();
return texturesAsJsonObject.toVariantMap();
} }
void RenderableModelEntityItem::remapTextures() { void RenderableModelEntityItem::remapTextures() {

View file

@ -38,12 +38,6 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData, EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override; bool& somethingChanged) override;
virtual void somethingChangedNotification() override {
// FIX ME: this is overly aggressive. We only really need to simulate() if something about
// the world space transform has changed and/or if some animation is occurring.
_needsInitialSimulation = true;
}
virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr);
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override; virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override; virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;

View file

@ -516,7 +516,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
EntityTreePointer tree = getTree(); EntityTreePointer tree = getTree();
if (tree && tree->isDeletedEntity(_id)) { if (tree && tree->isDeletedEntity(_id)) {
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "Recieved packet for previously deleted entity [" << _id << "] ignoring. " qDebug() << "Received packet for previously deleted entity [" << _id << "] ignoring. "
"(inside " << __FUNCTION__ << ")"; "(inside " << __FUNCTION__ << ")";
#endif #endif
ignoreServerPacket = true; ignoreServerPacket = true;

View file

@ -956,8 +956,10 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItem->recordCreationTime(); entityItem->recordCreationTime();
} }
} else { } else {
qDebug() << "Recieved packet for previously deleted entity [" << #ifdef WANT_DEBUG
qDebug() << "Received packet for previously deleted entity [" <<
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")"; entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
#endif
} }
} }
} }

View file

@ -1102,8 +1102,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("tex_color_map")) { } else if (type.contains("tex_color_map")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("transparentcolor")) { // it should be TransparentColor... } else if (type.contains("transparentcolor")) { // Maya way of passing TransparentMap
// THis is how Maya assign a texture that affect diffuse color AND transparency ? transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("transparencyfactor")) { // Blender way of passing TransparentMap
transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); transparentTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("bump")) { } else if (type.contains("bump")) {
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));

View file

@ -102,16 +102,20 @@ void FBXReader::consolidateFBXMaterials() {
detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity());
} }
FBXTexture transparentTexture; FBXTexture transparentTexture;
QString transparentTextureID = transparentTextures.value(material.materialID); QString transparentTextureID = transparentTextures.value(material.materialID);
// If PBS Material, systematically bind the albedo texture as transparency texture and check for the alpha channel
if (material.isPBSMaterial) {
transparentTextureID = diffuseTextureID;
}
if (!transparentTextureID.isNull()) { if (!transparentTextureID.isNull()) {
transparentTexture = getTexture(transparentTextureID); transparentTexture = getTexture(transparentTextureID);
material.opacityTexture = transparentTexture; material.opacityTexture = transparentTexture;
detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity());
} }
FBXTexture normalTexture; FBXTexture normalTexture;
QString bumpTextureID = bumpTextures.value(material.materialID); QString bumpTextureID = bumpTextures.value(material.materialID);
QString normalTextureID = normalTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID);

View file

@ -23,6 +23,7 @@
#include <PerfStat.h> #include <PerfStat.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <Finally.h>
#include "OffscreenGLCanvas.h" #include "OffscreenGLCanvas.h"
#include "GLEscrow.h" #include "GLEscrow.h"
@ -84,6 +85,7 @@ protected:
Queue _queue; Queue _queue;
QMutex _mutex; QMutex _mutex;
QWaitCondition _waitCondition; QWaitCondition _waitCondition;
std::atomic<bool> _rendering { false };
private: private:
// Event-driven methods // Event-driven methods
@ -271,15 +273,25 @@ void OffscreenQmlRenderThread::resize() {
} }
void OffscreenQmlRenderThread::render() { void OffscreenQmlRenderThread::render() {
if (_surface->_paused) { // Ensure we always release the main thread
Finally releaseMainThread([this] {
_waitCondition.wakeOne(); _waitCondition.wakeOne();
});
if (_surface->_paused) {
return; return;
} }
QMutexLocker locker(&_mutex); _rendering = true;
_renderControl->sync(); Finally unmarkRenderingFlag([this] {
_waitCondition.wakeOne(); _rendering = false;
locker.unlock(); });
{
QMutexLocker locker(&_mutex);
_renderControl->sync();
releaseMainThread.trigger();
}
using namespace oglplus; using namespace oglplus;
@ -292,6 +304,7 @@ void OffscreenQmlRenderThread::render() {
_fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0); _fbo->AttachTexture(Framebuffer::Target::Draw, FramebufferAttachment::Color, *texture, 0);
_fbo->Complete(Framebuffer::Target::Draw); _fbo->Complete(Framebuffer::Target::Draw);
{ {
PROFILE_RANGE("qml_render->rendercontrol")
_renderControl->render(); _renderControl->render();
// FIXME The web browsers seem to be leaving GL in an error state. // FIXME The web browsers seem to be leaving GL in an error state.
// Need a debug context with sync logging to figure out why. // Need a debug context with sync logging to figure out why.
@ -380,8 +393,6 @@ void OffscreenQmlSurface::resize(const QSize& newSize_) {
std::max(static_cast<int>(scale * newSize.height()), 10)); std::max(static_cast<int>(scale * newSize.height()), 10));
} }
QSize currentSize = _renderer->_quickWindow->geometry().size(); QSize currentSize = _renderer->_quickWindow->geometry().size();
if (newSize == currentSize) { if (newSize == currentSize) {
return; return;
@ -492,7 +503,12 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
} }
void OffscreenQmlSurface::updateQuick() { void OffscreenQmlSurface::updateQuick() {
if (!_renderer || !_renderer->allowNewFrame(_maxFps)) { // If we're
// a) not set up
// b) already rendering a frame
// c) rendering too fast
// then skip this
if (!_renderer || _renderer->_rendering || !_renderer->allowNewFrame(_maxFps)) {
return; return;
} }
@ -502,11 +518,11 @@ void OffscreenQmlSurface::updateQuick() {
} }
if (_render) { if (_render) {
PROFILE_RANGE(__FUNCTION__);
// Lock the GUI size while syncing // Lock the GUI size while syncing
QMutexLocker locker(&(_renderer->_mutex)); QMutexLocker locker(&(_renderer->_mutex));
_renderer->_queue.add(RENDER); _renderer->_queue.add(RENDER);
_renderer->_waitCondition.wait(&(_renderer->_mutex)); _renderer->_waitCondition.wait(&(_renderer->_mutex));
_render = false; _render = false;
} }

View file

@ -139,7 +139,7 @@ bool NetworkGeometry::isLoadedWithTextures() const {
} }
if (!_isLoadedWithTextures) { if (!_isLoadedWithTextures) {
_hasTransparentTextures = true; _hasTransparentTextures = false;
for (auto&& material : _materials) { for (auto&& material : _materials) {
if ((material->albedoTexture && !material->albedoTexture->isLoaded()) || if ((material->albedoTexture && !material->albedoTexture->isLoaded()) ||
@ -152,12 +152,11 @@ bool NetworkGeometry::isLoadedWithTextures() const {
return false; return false;
} }
if (material->albedoTexture && material->albedoTexture->getGPUTexture()) { if (material->albedoTexture && material->albedoTexture->getGPUTexture()) {
// Reset the materialKey transparentTexture key only, as it is albedoTexture-dependent // Reassign the texture to make sure that itsalbedo alpha channel material key is detected correctly
material->_material->setTextureMap(model::MaterialKey::ALBEDO_MAP, material->_material->getTextureMap(model::MaterialKey::ALBEDO_MAP));
const auto& usage = material->albedoTexture->getGPUTexture()->getUsage(); const auto& usage = material->albedoTexture->getGPUTexture()->getUsage();
bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask(); bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask();
material->_material->setTransparentTexture(isTransparentTexture); _hasTransparentTextures |= isTransparentTexture;
// FIXME: Materials with *some* transparent textures seem to give all *other* textures alphas of 0.
_hasTransparentTextures = isTransparentTexture && _hasTransparentTextures;
} }
} }
@ -177,9 +176,9 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u
auto albedoMap = model::TextureMapPointer(new model::TextureMap()); auto albedoMap = model::TextureMapPointer(new model::TextureMap());
albedoMap->setTextureSource(material->albedoTexture->_textureSource); albedoMap->setTextureSource(material->albedoTexture->_textureSource);
albedoMap->setTextureTransform( albedoMap->setTextureTransform(oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform());
oldTextureMaps[model::MaterialKey::ALBEDO_MAP]->getTextureTransform()); // when reassigning the albedo texture we also check for the alpha channel used as opacity
albedoMap->setUseAlphaChannel(true);
networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); networkMaterial->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
} else if (material->normalTextureName == name) { } else if (material->normalTextureName == name) {
material->normalTexture = textureCache->getTexture(url); material->normalTexture = textureCache->getTexture(url);
@ -212,10 +211,10 @@ void NetworkGeometry::setTextureWithNameToURL(const QString& name, const QUrl& u
networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap); networkMaterial->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap);
} else if (material->lightmapTextureName == name) { } else if (material->lightmapTextureName == name) {
material->emissiveTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE); material->lightmapTexture = textureCache->getTexture(url, LIGHTMAP_TEXTURE);
auto lightmapMap = model::TextureMapPointer(new model::TextureMap()); auto lightmapMap = model::TextureMapPointer(new model::TextureMap());
lightmapMap->setTextureSource(material->emissiveTexture->_textureSource); lightmapMap->setTextureSource(material->lightmapTexture->_textureSource);
lightmapMap->setTextureTransform( lightmapMap->setTextureTransform(
oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform()); oldTextureMaps[model::MaterialKey::LIGHTMAP_MAP]->getTextureTransform());
glm::vec2 oldOffsetScale = glm::vec2 oldOffsetScale =
@ -380,9 +379,20 @@ static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FB
auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE,
networkMaterial->albedoTexture, networkMaterial->albedoTextureName); networkMaterial->albedoTexture, networkMaterial->albedoTextureName);
albedoMap->setTextureTransform(material.albedoTexture.transform); albedoMap->setTextureTransform(material.albedoTexture.transform);
if (!material.opacityTexture.filename.isEmpty()) {
if (material.albedoTexture.filename == material.opacityTexture.filename) {
// Best case scenario, just indicating that the albedo map contains transparency
albedoMap->setUseAlphaChannel(true);
} else {
// Opacity Map is different from the Abledo map, not supported
}
}
material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap);
} }
if (!material.normalTexture.filename.isEmpty()) { if (!material.normalTexture.filename.isEmpty()) {
auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture, auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture,
(material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE),

View file

@ -199,7 +199,6 @@ public:
QSharedPointer<NetworkTexture> occlusionTexture; QSharedPointer<NetworkTexture> occlusionTexture;
QString lightmapTextureName; QString lightmapTextureName;
QSharedPointer<NetworkTexture> lightmapTexture; QSharedPointer<NetworkTexture> lightmapTexture;
}; };

View file

@ -320,13 +320,12 @@ void ImageReader::run() {
} }
QMetaObject::invokeMethod(texture.data(), "setImage", QMetaObject::invokeMethod(texture.data(), "setImage",
Q_ARG(const QImage&, image),
Q_ARG(void*, theTexture), Q_ARG(void*, theTexture),
Q_ARG(int, originalWidth), Q_ARG(int, originalHeight)); Q_ARG(int, originalWidth), Q_ARG(int, originalHeight));
QThread::currentThread()->setPriority(originalPriority); QThread::currentThread()->setPriority(originalPriority);
} }
void NetworkTexture::setImage(const QImage& image, void* voidTexture, int originalWidth, void NetworkTexture::setImage(void* voidTexture, int originalWidth,
int originalHeight) { int originalHeight) {
_originalWidth = originalWidth; _originalWidth = originalWidth;
_originalHeight = originalHeight; _originalHeight = originalHeight;

View file

@ -136,7 +136,7 @@ protected:
Q_INVOKABLE void loadContent(const QByteArray& content); Q_INVOKABLE void loadContent(const QByteArray& content);
// FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on...
Q_INVOKABLE void setImage(const QImage& image, void* texture, int originalWidth, int originalHeight); Q_INVOKABLE void setImage(void* texture, int originalWidth, int originalHeight);
private: private:

View file

@ -51,7 +51,7 @@ void Material::setEmissive(const Color& emissive, bool isSRGB) {
} }
void Material::setOpacity(float opacity) { void Material::setOpacity(float opacity) {
_key.setTransparent((opacity < 1.0f)); _key.setTranslucentFactor((opacity < 1.0f));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong(); _schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
_schemaBuffer.edit<Schema>()._opacity = opacity; _schemaBuffer.edit<Schema>()._opacity = opacity;
} }
@ -80,19 +80,52 @@ void Material::setMetallic(float metallic) {
_schemaBuffer.edit<Schema>()._metallic = metallic; _schemaBuffer.edit<Schema>()._metallic = metallic;
} }
void Material::setTransparentTexture(bool isTransparent) {
_key.setTransparentTexture(isTransparent);
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
if (textureMap) { if (textureMap) {
_key.setMapChannel(channel, (true)); _key.setMapChannel(channel, (true));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
if (channel == MaterialKey::ALBEDO_MAP) {
// clear the previous flags whatever they were:
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(false);
if (textureMap->useAlphaChannel() && textureMap->isDefined() && textureMap->getTextureView().isValid()) {
auto usage = textureMap->getTextureView()._texture->getUsage();
if (usage.isAlpha()) {
// Texture has alpha, is not just a mask or a true transparent channel
if (usage.isAlphaMask()) {
_key.setOpacityMaskMap(true);
_key.setTranslucentMap(false);
} else {
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(true);
}
}
}
}
_textureMaps[channel] = textureMap; _textureMaps[channel] = textureMap;
} else { } else {
_key.setMapChannel(channel, (false)); _key.setMapChannel(channel, (false));
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
if (channel == MaterialKey::ALBEDO_MAP) {
_key.setOpacityMaskMap(false);
_key.setTranslucentMap(false);
}
_textureMaps.erase(channel); _textureMaps.erase(channel);
} }
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
}
const TextureMapPointer Material::getTextureMap(MapChannel channel) const {
auto result = _textureMaps.find(channel);
if (result != _textureMaps.end()) {
return (result->second);
} else {
return TextureMapPointer();
}
} }

View file

@ -31,14 +31,15 @@ public:
ALBEDO_VAL_BIT, ALBEDO_VAL_BIT,
METALLIC_VAL_BIT, METALLIC_VAL_BIT,
GLOSSY_VAL_BIT, GLOSSY_VAL_BIT,
TRANSPARENT_VAL_BIT, OPACITY_VAL_BIT,
TRANSPARENT_TEX_VAL_BIT, OPACITY_MASK_MAP_BIT, // OPacity Map and Opacity MASK map are mutually exclusive
OPACITY_TRANSLUCENT_MAP_BIT,
// THe map bits must be in the smae sequence as the enum names for the map channels
EMISSIVE_MAP_BIT, EMISSIVE_MAP_BIT,
ALBEDO_MAP_BIT, ALBEDO_MAP_BIT,
METALLIC_MAP_BIT, METALLIC_MAP_BIT,
ROUGHNESS_MAP_BIT, ROUGHNESS_MAP_BIT,
TRANSPARENT_MAP_BIT,
NORMAL_MAP_BIT, NORMAL_MAP_BIT,
OCCLUSION_MAP_BIT, OCCLUSION_MAP_BIT,
LIGHTMAP_MAP_BIT, LIGHTMAP_MAP_BIT,
@ -52,7 +53,6 @@ public:
ALBEDO_MAP, ALBEDO_MAP,
METALLIC_MAP, METALLIC_MAP,
ROUGHNESS_MAP, ROUGHNESS_MAP,
TRANSPARENT_MAP,
NORMAL_MAP, NORMAL_MAP,
OCCLUSION_MAP, OCCLUSION_MAP,
LIGHTMAP_MAP, LIGHTMAP_MAP,
@ -77,13 +77,15 @@ public:
Builder& withAlbedo() { _flags.set(ALBEDO_VAL_BIT); return (*this); } Builder& withAlbedo() { _flags.set(ALBEDO_VAL_BIT); return (*this); }
Builder& withMetallic() { _flags.set(METALLIC_VAL_BIT); return (*this); } Builder& withMetallic() { _flags.set(METALLIC_VAL_BIT); return (*this); }
Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); } Builder& withGlossy() { _flags.set(GLOSSY_VAL_BIT); return (*this); }
Builder& withTransparent() { _flags.set(TRANSPARENT_VAL_BIT); return (*this); } Builder& withTranslucentFactor() { _flags.set(OPACITY_VAL_BIT); return (*this); }
Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); } Builder& withEmissiveMap() { _flags.set(EMISSIVE_MAP_BIT); return (*this); }
Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); } Builder& withAlbedoMap() { _flags.set(ALBEDO_MAP_BIT); return (*this); }
Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); } Builder& withMetallicMap() { _flags.set(METALLIC_MAP_BIT); return (*this); }
Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); } Builder& withRoughnessMap() { _flags.set(ROUGHNESS_MAP_BIT); return (*this); }
Builder& withTransparentMap() { _flags.set(TRANSPARENT_MAP_BIT); return (*this); }
Builder& withTranslucentMap() { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withMaskMap() { _flags.set(OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); } Builder& withNormalMap() { _flags.set(NORMAL_MAP_BIT); return (*this); }
Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); } Builder& withOcclusionMap() { _flags.set(OCCLUSION_MAP_BIT); return (*this); }
@ -102,9 +104,6 @@ public:
void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); } void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); }
bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; } bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; }
void setTransparentTexture(bool value) { _flags.set(TRANSPARENT_TEX_VAL_BIT, value); }
bool isTransparentTexture() const { return _flags[TRANSPARENT_TEX_VAL_BIT]; }
void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); } void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); }
bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; } bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; }
@ -121,13 +120,15 @@ public:
void setRoughnessMap(bool value) { _flags.set(ROUGHNESS_MAP_BIT, value); } void setRoughnessMap(bool value) { _flags.set(ROUGHNESS_MAP_BIT, value); }
bool isRoughnessMap() const { return _flags[ROUGHNESS_MAP_BIT]; } bool isRoughnessMap() const { return _flags[ROUGHNESS_MAP_BIT]; }
void setTransparent(bool value) { _flags.set(TRANSPARENT_VAL_BIT, value); } void setTranslucentFactor(bool value) { _flags.set(OPACITY_VAL_BIT, value); }
bool isTransparent() const { return _flags[TRANSPARENT_VAL_BIT]; } bool isTranslucentFactor() const { return _flags[OPACITY_VAL_BIT]; }
bool isOpaque() const { return !_flags[TRANSPARENT_VAL_BIT]; }
void setTransparentMap(bool value) { _flags.set(TRANSPARENT_MAP_BIT, value); } void setTranslucentMap(bool value) { _flags.set(OPACITY_TRANSLUCENT_MAP_BIT, value); }
bool isTransparentMap() const { return _flags[TRANSPARENT_MAP_BIT]; } bool isTranslucentMap() const { return _flags[OPACITY_TRANSLUCENT_MAP_BIT]; }
void setOpacityMaskMap(bool value) { _flags.set(OPACITY_MASK_MAP_BIT, value); }
bool isOpacityMaskMap() const { return _flags[OPACITY_MASK_MAP_BIT]; }
void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); } void setNormalMap(bool value) { _flags.set(NORMAL_MAP_BIT, value); }
bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; } bool isNormalMap() const { return _flags[NORMAL_MAP_BIT]; }
@ -140,6 +141,12 @@ public:
void setMapChannel(MapChannel channel, bool value) { _flags.set(EMISSIVE_MAP_BIT + channel, value); } void setMapChannel(MapChannel channel, bool value) { _flags.set(EMISSIVE_MAP_BIT + channel, value); }
bool isMapChannel(MapChannel channel) const { return _flags[EMISSIVE_MAP_BIT + channel]; } bool isMapChannel(MapChannel channel) const { return _flags[EMISSIVE_MAP_BIT + channel]; }
// Translucency and Opacity Heuristics are combining several flags:
bool isTranslucent() const { return isTranslucentFactor() || isTranslucentMap(); }
bool isOpaque() const { return !isTranslucent(); }
bool isSurfaceOpaque() const { return isOpaque() && !isOpacityMaskMap(); }
bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); }
}; };
@ -168,9 +175,6 @@ public:
Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); } Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
Builder& withoutTransparentTexture() { _value.reset(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
Builder& withTransparentTexture() { _value.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); } Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
@ -186,11 +190,14 @@ public:
Builder& withoutRoughnessMap() { _value.reset(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } Builder& withoutRoughnessMap() { _value.reset(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); }
Builder& withRoughnessMap() { _value.set(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); } Builder& withRoughnessMap() { _value.set(MaterialKey::ROUGHNESS_MAP_BIT); _mask.set(MaterialKey::ROUGHNESS_MAP_BIT); return (*this); }
Builder& withoutTransparent() { _value.reset(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } Builder& withoutTranslucentFactor() { _value.reset(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); }
Builder& withTransparent() { _value.set(MaterialKey::TRANSPARENT_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_VAL_BIT); return (*this); } Builder& withTranslucentFactor() { _value.set(MaterialKey::OPACITY_VAL_BIT); _mask.set(MaterialKey::OPACITY_VAL_BIT); return (*this); }
Builder& withoutTransparentMap() { _value.reset(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); } Builder& withoutTranslucentMap() { _value.reset(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withTransparentMap() { _value.set(MaterialKey::TRANSPARENT_MAP_BIT); _mask.set(MaterialKey::TRANSPARENT_MAP_BIT); return (*this); } Builder& withTranslucentMap() { _value.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); _mask.set(MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT); return (*this); }
Builder& withoutMaskMap() { _value.reset(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withMaskMap() { _value.set(MaterialKey::OPACITY_MASK_MAP_BIT); _mask.set(MaterialKey::OPACITY_MASK_MAP_BIT); return (*this); }
Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } Builder& withoutNormalMap() { _value.reset(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); } Builder& withNormalMap() { _value.set(MaterialKey::NORMAL_MAP_BIT); _mask.set(MaterialKey::NORMAL_MAP_BIT); return (*this); }
@ -202,7 +209,7 @@ public:
Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); } Builder& withLightmapMap() { _value.set(MaterialKey::LIGHTMAP_MAP_BIT); _mask.set(MaterialKey::LIGHTMAP_MAP_BIT); return (*this); }
// Convenient standard keys that we will keep on using all over the place // Convenient standard keys that we will keep on using all over the place
static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTransparent().build(); } static MaterialFilter opaqueAlbedo() { return Builder().withAlbedo().withoutTranslucentFactor().build(); }
}; };
// Item Filter operator testing if a key pass the filter // Item Filter operator testing if a key pass the filter
@ -255,7 +262,6 @@ public:
void setRoughness(float roughness); void setRoughness(float roughness);
float getRoughness() const { return _schemaBuffer.get<Schema>()._roughness; } float getRoughness() const { return _schemaBuffer.get<Schema>()._roughness; }
void setTransparentTexture(bool isTransparent);
// Schema to access the attribute values of the material // Schema to access the attribute values of the material
class Schema { class Schema {
@ -283,6 +289,7 @@ public:
// The texture map to channel association // The texture map to channel association
void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap);
const TextureMaps& getTextureMaps() const { return _textureMaps; } const TextureMaps& getTextureMaps() const { return _textureMaps; }
const TextureMapPointer getTextureMap(MapChannel channel) const;
// conversion from legacy material properties to PBR equivalent // conversion from legacy material properties to PBR equivalent
static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; }

View file

@ -39,21 +39,21 @@ float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); }
int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); } int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); }
const int EMISSIVE_VAL_BIT = 0x00000001; const int EMISSIVE_VAL_BIT = 0x00000001;
const int ALBEDO_VAL_BIT = 0x00000002; const int ALBEDO_VAL_BIT = 0x00000002;
const int METALLIC_VAL_BIT = 0x00000004; const int METALLIC_VAL_BIT = 0x00000004;
const int GLOSSY_VAL_BIT = 0x00000008; const int GLOSSY_VAL_BIT = 0x00000008;
const int TRANSPARENT_VAL_BIT = 0x00000010; const int OPACITY_VAL_BIT = 0x00000010;
const int TRANSPARENT_TEX_VAL_BIT = 0x00000020; const int OPACITY_MASK_MAP_BIT = 0x00000020;
const int OPACITY_TRANSLUCENT_MAP_BIT = 0x00000040;
const int EMISSIVE_MAP_BIT = 0x00000040; const int EMISSIVE_MAP_BIT = 0x00000080;
const int ALBEDO_MAP_BIT = 0x00000080; const int ALBEDO_MAP_BIT = 0x00000100;
const int METALLIC_MAP_BIT = 0x00000100; const int METALLIC_MAP_BIT = 0x00000200;
const int ROUGHNESS_MAP_BIT = 0x00000200; const int ROUGHNESS_MAP_BIT = 0x00000400;
const int TRANSPARENT_MAP_BIT = 0x00000400; const int NORMAL_MAP_BIT = 0x00000800;
const int NORMAL_MAP_BIT = 0x00000800; const int OCCLUSION_MAP_BIT = 0x00001000;
const int OCCLUSION_MAP_BIT = 0x00001000; const int LIGHTMAP_MAP_BIT = 0x00002000;
const int LIGHTMAP_MAP_BIT = 0x00002000;
<@endif@> <@endif@>

View file

@ -56,6 +56,9 @@ public:
void setTextureTransform(const Transform& texcoordTransform); void setTextureTransform(const Transform& texcoordTransform);
const Transform& getTextureTransform() const { return _texcoordTransform; } const Transform& getTextureTransform() const { return _texcoordTransform; }
void setUseAlphaChannel(bool useAlpha) { _useAlphaChannel = useAlpha; }
bool useAlphaChannel() const { return _useAlphaChannel; }
void setLightmapOffsetScale(float offset, float scale); void setLightmapOffsetScale(float offset, float scale);
const glm::vec2& getLightmapOffsetScale() const { return _lightmapOffsetScale; } const glm::vec2& getLightmapOffsetScale() const { return _lightmapOffsetScale; }
@ -64,6 +67,8 @@ protected:
Transform _texcoordTransform; Transform _texcoordTransform;
glm::vec2 _lightmapOffsetScale{ 0.0f, 1.0f }; glm::vec2 _lightmapOffsetScale{ 0.0f, 1.0f };
bool _useAlphaChannel{ false };
}; };
typedef std::shared_ptr< TextureMap > TextureMapPointer; typedef std::shared_ptr< TextureMap > TextureMapPointer;

View file

@ -216,17 +216,13 @@ Resource* ResourceCacheSharedItems::getHighestPendingRequest() {
bool ResourceCache::attemptRequest(Resource* resource) { bool ResourceCache::attemptRequest(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
// Disable request limiting for ATP if (_requestsActive >= _requestLimit) {
if (resource->getURL().scheme() != URL_SCHEME_ATP) { // wait until a slot becomes available
if (_requestsActive >= _requestLimit) { sharedItems->appendPendingRequest(resource);
// wait until a slot becomes available return false;
sharedItems->appendPendingRequest(resource);
return false;
}
++_requestsActive;
} }
++_requestsActive;
sharedItems->appendActiveRequest(resource); sharedItems->appendActiveRequest(resource);
resource->makeRequest(); resource->makeRequest();
return true; return true;
@ -235,9 +231,7 @@ bool ResourceCache::attemptRequest(Resource* resource) {
void ResourceCache::requestCompleted(Resource* resource) { void ResourceCache::requestCompleted(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->removeRequest(resource); sharedItems->removeRequest(resource);
if (resource->getURL().scheme() != URL_SCHEME_ATP) { --_requestsActive;
--_requestsActive;
}
attemptHighestPriorityRequest(); attemptHighestPriorityRequest();
} }
@ -380,6 +374,7 @@ void Resource::finishedLoading(bool success) {
_failedToLoad = true; _failedToLoad = true;
} }
_loadPriorities.clear(); _loadPriorities.clear();
emit finished(success);
} }
void Resource::reinsert() { void Resource::reinsert() {

View file

@ -201,6 +201,9 @@ signals:
/// This can be used instead of downloadFinished to access data before it is processed. /// This can be used instead of downloadFinished to access data before it is processed.
void loaded(const QByteArray& request); void loaded(const QByteArray& request);
/// Fired when the resource has finished loading.
void finished(bool success);
/// Fired when the resource failed to load. /// Fired when the resource failed to load.
void failed(QNetworkReply::NetworkError error); void failed(QNetworkReply::NetworkError error);
@ -224,9 +227,6 @@ protected:
/// This should be called by subclasses that override downloadFinished to mark the end of processing. /// This should be called by subclasses that override downloadFinished to mark the end of processing.
Q_INVOKABLE void finishedLoading(bool success); Q_INVOKABLE void finishedLoading(bool success);
/// Reinserts this resource into the cache.
virtual void reinsert();
QUrl _url; QUrl _url;
QUrl _activeUrl; QUrl _activeUrl;
bool _startedLoading = false; bool _startedLoading = false;
@ -246,6 +246,7 @@ private:
void makeRequest(); void makeRequest();
void retry(); void retry();
void reinsert();
friend class ResourceCache; friend class ResourceCache;

View file

@ -78,11 +78,15 @@ void UserActivityLogger::requestError(QNetworkReply& errorReply) {
qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString();
} }
void UserActivityLogger::launch(QString applicationVersion) { void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) {
const QString ACTION_NAME = "launch"; const QString ACTION_NAME = "launch";
QJsonObject actionDetails; QJsonObject actionDetails;
QString VERSION_KEY = "version"; QString VERSION_KEY = "version";
QString CRASH_KEY = "previousSessionCrashed";
QString RUNTIME_KEY = "previousSessionRuntime";
actionDetails.insert(VERSION_KEY, applicationVersion); actionDetails.insert(VERSION_KEY, applicationVersion);
actionDetails.insert(CRASH_KEY, previousSessionCrashed);
actionDetails.insert(RUNTIME_KEY, previousSessionRuntime);
logAction(ACTION_NAME, actionDetails); logAction(ACTION_NAME, actionDetails);
} }

View file

@ -29,7 +29,7 @@ public slots:
void disable(bool disable); void disable(bool disable);
void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
void launch(QString applicationVersion); void launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime);
void changedDisplayName(QString displayName); void changedDisplayName(QString displayName);
void changedModel(QString typeOfModel, QString modelURL); void changedModel(QString typeOfModel, QString modelURL);

View file

@ -201,7 +201,7 @@ void DefaultCC::onTimeout() {
void DefaultCC::stopSlowStart() { void DefaultCC::stopSlowStart() {
_slowStart = false; _slowStart = false;
if (_receiveRate > 0) { if (_receiveRate > 0) {
// Set the sending rate to the receiving rate. // Set the sending rate to the receiving rate.
setPacketSendPeriod(USECS_PER_SECOND / _receiveRate); setPacketSendPeriod(USECS_PER_SECOND / _receiveRate);

View file

@ -103,6 +103,7 @@ SendQueue& Connection::getSendQueue() {
QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission);
QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive);
QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout); QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout);
QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss);
// set defaults on the send queue from our congestion control object and estimatedTimeout() // set defaults on the send queue from our congestion control object and estimatedTimeout()
_sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod);
@ -140,6 +141,12 @@ void Connection::queueTimeout() {
}); });
} }
void Connection::queueShortCircuitLoss(quint32 sequenceNumber) {
updateCongestionControlAndSendQueue([this, sequenceNumber]{
_congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber });
});
}
void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) { void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) {
Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably.");
getSendQueue().queuePacket(std::move(packet)); getSendQueue().queuePacket(std::move(packet));

View file

@ -87,6 +87,7 @@ private slots:
void recordRetransmission(); void recordRetransmission();
void queueInactive(); void queueInactive();
void queueTimeout(); void queueTimeout();
void queueShortCircuitLoss(quint32 sequenceNumber);
private: private:
void sendACK(bool wasCausedBySyncTimeout = true); void sendACK(bool wasCausedBySyncTimeout = true);

View file

@ -128,13 +128,13 @@ void SendQueue::stop() {
_emptyCondition.notify_one(); _emptyCondition.notify_one();
} }
void SendQueue::sendPacket(const Packet& packet) { int SendQueue::sendPacket(const Packet& packet) {
_socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination); return _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination);
} }
void SendQueue::ack(SequenceNumber ack) { void SendQueue::ack(SequenceNumber ack) {
// this is a response from the client, re-set our timeout expiry and our last response time // this is a response from the client, re-set our timeout expiry and our last response time
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
if (_lastACKSequenceNumber == (uint32_t) ack) { if (_lastACKSequenceNumber == (uint32_t) ack) {
return; return;
@ -164,7 +164,7 @@ void SendQueue::ack(SequenceNumber ack) {
void SendQueue::nak(SequenceNumber start, SequenceNumber end) { void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
// this is a response from the client, re-set our timeout expiry // this is a response from the client, re-set our timeout expiry
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
{ {
std::lock_guard<std::mutex> nakLocker(_naksLock); std::lock_guard<std::mutex> nakLocker(_naksLock);
@ -177,8 +177,8 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) {
void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) {
// this is a response from the client, re-set our timeout expiry // this is a response from the client, re-set our timeout expiry
_lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch();
{ {
std::lock_guard<std::mutex> nakLocker(_naksLock); std::lock_guard<std::mutex> nakLocker(_naksLock);
_naks.clear(); _naks.clear();
@ -232,15 +232,16 @@ SequenceNumber SendQueue::getNextSequenceNumber() {
return _currentSequenceNumber; return _currentSequenceNumber;
} }
void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) { bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber) {
// write the sequence number and send the packet // write the sequence number and send the packet
newPacket->writeSequenceNumber(sequenceNumber); newPacket->writeSequenceNumber(sequenceNumber);
sendPacket(*newPacket);
// Save packet/payload size before we move it // Save packet/payload size before we move it
auto packetSize = newPacket->getDataSize(); auto packetSize = newPacket->getDataSize();
auto payloadSize = newPacket->getPayloadSize(); auto payloadSize = newPacket->getPayloadSize();
auto bytesWritten = sendPacket(*newPacket);
{ {
// Insert the packet we have just sent in the sent list // Insert the packet we have just sent in the sent list
QWriteLocker locker(&_sentLock); QWriteLocker locker(&_sentLock);
@ -249,8 +250,24 @@ void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket,
entry.second.swap(newPacket); entry.second.swap(newPacket);
} }
Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list"); Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list");
emit packetSent(packetSize, payloadSize); emit packetSent(packetSize, payloadSize);
if (bytesWritten < 0) {
// this is a short-circuit loss - we failed to put this packet on the wire
// so immediately add it to the loss list
{
std::lock_guard<std::mutex> nakLocker(_naksLock);
_naks.append(sequenceNumber);
}
emit shortCircuitLoss(quint32(sequenceNumber));
return false;
} else {
return true;
}
} }
void SendQueue::run() { void SendQueue::run() {
@ -285,12 +302,14 @@ void SendQueue::run() {
auto nextPacketTimestamp = p_high_resolution_clock::now(); auto nextPacketTimestamp = p_high_resolution_clock::now();
while (_state == State::Running) { while (_state == State::Running) {
bool sentAPacket = maybeResendPacket(); bool attemptedToSendPacket = maybeResendPacket();
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire // if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
// (this is according to the current flow window size) then we send out a new packet // (this is according to the current flow window size) then we send out a new packet
if (!sentAPacket) { auto newPacketCount = 0;
sentAPacket = maybeSendNewPacket(); if (!attemptedToSendPacket) {
newPacketCount = maybeSendNewPacket();
attemptedToSendPacket = (newPacketCount > 0);
} }
// since we're a while loop, give the thread a chance to process events // since we're a while loop, give the thread a chance to process events
@ -300,12 +319,13 @@ void SendQueue::run() {
// If the send queue has been innactive, skip the sleep for // If the send queue has been innactive, skip the sleep for
// Either _isRunning will have been set to false and we'll break // Either _isRunning will have been set to false and we'll break
// Or something happened and we'll keep going // Or something happened and we'll keep going
if (_state != State::Running || isInactive(sentAPacket)) { if (_state != State::Running || isInactive(attemptedToSendPacket)) {
return; return;
} }
// push the next packet timestamp forwards by the current packet send period // push the next packet timestamp forwards by the current packet send period
nextPacketTimestamp += std::chrono::microseconds(_packetSendPeriod); auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod;
nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta);
// sleep as long as we need until next packet send, if we can // sleep as long as we need until next packet send, if we can
const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now()); const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now());
@ -314,7 +334,7 @@ void SendQueue::run() {
} }
} }
bool SendQueue::maybeSendNewPacket() { int SendQueue::maybeSendNewPacket() {
if (!isFlowWindowFull()) { if (!isFlowWindowFull()) {
// we didn't re-send a packet, so time to send a new one // we didn't re-send a packet, so time to send a new one
@ -324,38 +344,43 @@ bool SendQueue::maybeSendNewPacket() {
// grab the first packet we will send // grab the first packet we will send
std::unique_ptr<Packet> firstPacket = _packets.takePacket(); std::unique_ptr<Packet> firstPacket = _packets.takePacket();
Q_ASSERT(firstPacket); Q_ASSERT(firstPacket);
std::unique_ptr<Packet> secondPacket;
bool shouldSendPairTail = false; // attempt to send the first packet
if (sendNewPacketAndAddToSentList(move(firstPacket), nextNumber)) {
if (((uint32_t) nextNumber & 0xF) == 0) { std::unique_ptr<Packet> secondPacket;
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets bool shouldSendPairTail = false;
// pull off a second packet if we can before we unlock
shouldSendPairTail = true; if (((uint32_t) nextNumber & 0xF) == 0) {
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
secondPacket = _packets.takePacket(); // pull off a second packet if we can before we unlock
shouldSendPairTail = true;
secondPacket = _packets.takePacket();
}
// do we have a second in a pair to send as well?
if (secondPacket) {
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
} else if (shouldSendPairTail) {
// we didn't get a second packet to send in the probe pair
// send a control packet of type ProbePairTail so the receiver can still do
// proper bandwidth estimation
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
_socket->writeBasePacket(*pairTailPacket, _destination);
}
// we attempted to send two packets, return 2
return 2;
} else {
// we attempted to send a single packet, return 1
return 1;
} }
// definitely send the first packet
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
// do we have a second in a pair to send as well?
if (secondPacket) {
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
} else if (shouldSendPairTail) {
// we didn't get a second packet to send in the probe pair
// send a control packet of type ProbePairTail so the receiver can still do
// proper bandwidth estimation
static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail);
_socket->writeBasePacket(*pairTailPacket, _destination);
}
// We sent our packet(s), return here
return true;
} }
} }
// No packets were sent // No packets were sent
return false; return 0;
} }
bool SendQueue::maybeResendPacket() { bool SendQueue::maybeResendPacket() {
@ -375,8 +400,9 @@ bool SendQueue::maybeResendPacket() {
// see if we can find the packet to re-send // see if we can find the packet to re-send
auto it = _sentPackets.find(resendNumber); auto it = _sentPackets.find(resendNumber);
if (it != _sentPackets.end()) { if (it != _sentPackets.end()) {
auto& entry = it->second; auto& entry = it->second;
// we found the packet - grab it // we found the packet - grab it
auto& resendPacket = *(entry.second); auto& resendPacket = *(entry.second);
@ -437,7 +463,7 @@ bool SendQueue::maybeResendPacket() {
return false; return false;
} }
bool SendQueue::isInactive(bool sentAPacket) { bool SendQueue::isInactive(bool attemptedToSendPacket) {
// check for connection timeout first // check for connection timeout first
// that will be the case if we have had 16 timeouts since hearing back from the client, and it has been // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been
@ -447,7 +473,8 @@ bool SendQueue::isInactive(bool sentAPacket) {
auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse); auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse);
if (sinceLastResponse >= quint64(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && if (sinceLastResponse > 0 &&
sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) &&
_lastReceiverResponse > 0 && _lastReceiverResponse > 0 &&
sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { sinceLastResponse > MIN_MS_BEFORE_INACTIVE) {
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER, // If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
@ -462,7 +489,7 @@ bool SendQueue::isInactive(bool sentAPacket) {
return true; return true;
} }
if (!sentAPacket) { if (!attemptedToSendPacket) {
// During our processing above we didn't send any packets // During our processing above we didn't send any packets
// If that is still the case we should use a condition_variable_any to sleep until we have data to handle. // If that is still the case we should use a condition_variable_any to sleep until we have data to handle.

View file

@ -79,6 +79,7 @@ signals:
void queueInactive(); void queueInactive();
void shortCircuitLoss(quint32 sequenceNumber);
void timeout(); void timeout();
private slots: private slots:
@ -91,13 +92,13 @@ private:
void sendHandshake(); void sendHandshake();
void sendPacket(const Packet& packet); int sendPacket(const Packet& packet);
void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber); bool sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
bool maybeSendNewPacket(); // Figures out what packet to send next int maybeSendNewPacket(); // Figures out what packet to send next
bool maybeResendPacket(); // Determines whether to resend a packet and which one bool maybeResendPacket(); // Determines whether to resend a packet and which one
bool isInactive(bool sentAPacket); bool isInactive(bool attemptedToSendPacket);
void deactivate(); // makes the queue inactive and cleans it up void deactivate(); // makes the queue inactive and cleans it up
bool isFlowWindowFull() const; bool isFlowWindowFull() const;
@ -122,7 +123,7 @@ private:
std::atomic<int> _estimatedTimeout { 0 }; // Estimated timeout, set from CC std::atomic<int> _estimatedTimeout { 0 }; // Estimated timeout, set from CC
std::atomic<int> _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC std::atomic<int> _syncInterval { udt::DEFAULT_SYN_INTERVAL_USECS }; // Sync interval, set from CC
std::atomic<uint64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) std::atomic<int64_t> _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK)
std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC

View file

@ -120,3 +120,21 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
} }
return shape; return shape;
} }
void ShapeFactory::deleteShape(btCollisionShape* shape) {
assert(shape);
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
btCollisionShape* childShape = compoundShape->getChildShape(i);
if (childShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
// recurse
ShapeFactory::deleteShape(childShape);
} else {
delete childShape;
}
}
}
delete shape;
}

View file

@ -22,6 +22,7 @@
namespace ShapeFactory { namespace ShapeFactory {
btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points); btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points);
btCollisionShape* createShapeFromInfo(const ShapeInfo& info); btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape);
}; };
#endif // hifi_ShapeFactory_h #endif // hifi_ShapeFactory_h

View file

@ -23,7 +23,7 @@ ShapeManager::~ShapeManager() {
int numShapes = _shapeMap.size(); int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) { for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i); ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
delete shapeRef->shape; ShapeFactory::deleteShape(shapeRef->shape);
} }
_shapeMap.clear(); _shapeMap.clear();
} }
@ -32,13 +32,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) { if (info.getType() == SHAPE_TYPE_NONE) {
return NULL; return NULL;
} }
// Very small or large objects are not supported. if (info.getType() != SHAPE_TYPE_COMPOUND) {
float diagonal = 4.0f * glm::length2(info.getHalfExtents()); // Very small or large non-compound objects are not supported.
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube float diagonal = 4.0f * glm::length2(info.getHalfExtents());
//const float MAX_SHAPE_DIAGONAL_SQUARED = 3.0e6f; // 1000 m cube const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED /* || diagonal > MAX_SHAPE_DIAGONAL_SQUARED*/ ) { if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) {
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL; return NULL;
}
} }
DoubleHashKey key = info.getHash(); DoubleHashKey key = info.getHash();
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
@ -58,14 +59,14 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
} }
// private helper method // private helper method
bool ShapeManager::releaseShape(const DoubleHashKey& key) { bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef) { if (shapeRef) {
if (shapeRef->refCount > 0) { if (shapeRef->refCount > 0) {
shapeRef->refCount--; shapeRef->refCount--;
if (shapeRef->refCount == 0) { if (shapeRef->refCount == 0) {
_pendingGarbage.push_back(key); _pendingGarbage.push_back(key);
const int MAX_GARBAGE_CAPACITY = 127; const int MAX_GARBAGE_CAPACITY = 255;
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) { if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
collectGarbage(); collectGarbage();
} }
@ -82,16 +83,12 @@ bool ShapeManager::releaseShape(const DoubleHashKey& key) {
return false; return false;
} }
bool ShapeManager::releaseShape(const ShapeInfo& info) {
return releaseShape(info.getHash());
}
bool ShapeManager::releaseShape(const btCollisionShape* shape) { bool ShapeManager::releaseShape(const btCollisionShape* shape) {
int numShapes = _shapeMap.size(); int numShapes = _shapeMap.size();
for (int i = 0; i < numShapes; ++i) { for (int i = 0; i < numShapes; ++i) {
ShapeReference* shapeRef = _shapeMap.getAtIndex(i); ShapeReference* shapeRef = _shapeMap.getAtIndex(i);
if (shape == shapeRef->shape) { if (shape == shapeRef->shape) {
return releaseShape(shapeRef->key); return releaseShapeByKey(shapeRef->key);
} }
} }
return false; return false;
@ -103,17 +100,7 @@ void ShapeManager::collectGarbage() {
DoubleHashKey& key = _pendingGarbage[i]; DoubleHashKey& key = _pendingGarbage[i];
ShapeReference* shapeRef = _shapeMap.find(key); ShapeReference* shapeRef = _shapeMap.find(key);
if (shapeRef && shapeRef->refCount == 0) { if (shapeRef && shapeRef->refCount == 0) {
// if the shape we're about to delete is compound, delete the children first. ShapeFactory::deleteShape(shapeRef->shape);
if (shapeRef->shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE) {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shapeRef->shape);
const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
delete childShape;
}
}
delete shapeRef->shape;
_shapeMap.remove(key); _shapeMap.remove(key);
} }
} }

View file

@ -29,7 +29,6 @@ public:
btCollisionShape* getShape(const ShapeInfo& info); btCollisionShape* getShape(const ShapeInfo& info);
/// \return true if shape was found and released /// \return true if shape was found and released
bool releaseShape(const ShapeInfo& info);
bool releaseShape(const btCollisionShape* shape); bool releaseShape(const btCollisionShape* shape);
/// delete shapes that have zero references /// delete shapes that have zero references
@ -39,10 +38,10 @@ public:
int getNumShapes() const { return _shapeMap.size(); } int getNumShapes() const { return _shapeMap.size(); }
int getNumReferences(const ShapeInfo& info) const; int getNumReferences(const ShapeInfo& info) const;
int getNumReferences(const btCollisionShape* shape) const; int getNumReferences(const btCollisionShape* shape) const;
bool hasShape(const btCollisionShape* shape) const; bool hasShape(const btCollisionShape* shape) const;
private: private:
bool releaseShape(const DoubleHashKey& key); bool releaseShapeByKey(const DoubleHashKey& key);
struct ShapeReference { struct ShapeReference {
int refCount; int refCount;

View file

@ -59,7 +59,7 @@ float fetchOcclusionMap(vec2 uv) {
<@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@> <@func fetchMaterialTextures(matKey, texcoord0, albedo, roughness, normal, metallic, emissive, occlusion)@>
<@if albedo@> <@if albedo@>
vec4 <$albedo$> = (((<$matKey$> & ALBEDO_MAP_BIT) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0)); vec4 <$albedo$> = (((<$matKey$> & (ALBEDO_MAP_BIT | OPACITY_MASK_MAP_BIT | OPACITY_TRANSLUCENT_MAP_BIT)) != 0) ? fetchAlbedoMap(<$texcoord0$>) : vec4(1.0));
<@endif@> <@endif@>
<@if roughness@> <@if roughness@>
float <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? fetchRoughnessMap(<$texcoord0$>) : 1.0); float <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? fetchRoughnessMap(<$texcoord0$>) : 1.0);
@ -112,6 +112,23 @@ vec3 fetchLightmapMap(vec2 uv) {
} }
<@endfunc@> <@endfunc@>
<@func evalMaterialOpacity(fetchedOpacity, materialOpacity, matKey, opacity)@>
{
const float OPACITY_MASK_THRESHOLD = 0.5;
<$opacity$> = (((<$matKey$> & (OPACITY_TRANSLUCENT_MAP_BIT | OPACITY_MASK_MAP_BIT)) != 0) ?
(((<$matKey$> & OPACITY_MASK_MAP_BIT) != 0) ? step(OPACITY_MASK_THRESHOLD, <$fetchedOpacity$>) : <$fetchedOpacity$>) :
1.0) * <$materialOpacity$>;
}
<@endfunc@>
<@func $discardTransparent(opacity)@>
{
if (<$opacity$> < 1.0) {
discard;
}
}
<@endfunc@>
<@func evalMaterialRoughness(fetchedRoughness, materialRoughness, matKey, roughness)@> <@func evalMaterialRoughness(fetchedRoughness, materialRoughness, matKey, roughness)@>
{ {
<$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? <$fetchedRoughness$> : <$materialRoughness$>); <$roughness$> = (((<$matKey$> & ROUGHNESS_MAP_BIT) != 0) ? <$fetchedRoughness$> : <$materialRoughness$>);

View file

@ -81,7 +81,7 @@ ItemKey MeshPartPayload::getKey() const {
if (_drawMaterial) { if (_drawMaterial) {
auto matKey = _drawMaterial->getKey(); auto matKey = _drawMaterial->getKey();
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) { if (matKey.isTranslucent()) {
builder.withTransparent(); builder.withTransparent();
} }
} }
@ -100,7 +100,7 @@ ShapeKey MeshPartPayload::getShapeKey() const {
} }
ShapeKey::Builder builder; ShapeKey::Builder builder;
if (drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap()) { if (drawMaterialKey.isTranslucent()) {
builder.withTranslucent(); builder.withTranslucent();
} }
if (drawMaterialKey.isNormalMap()) { if (drawMaterialKey.isNormalMap()) {
@ -365,7 +365,7 @@ ItemKey ModelMeshPartPayload::getKey() const {
if (_drawMaterial) { if (_drawMaterial) {
auto matKey = _drawMaterial->getKey(); auto matKey = _drawMaterial->getKey();
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) { if (matKey.isTranslucent()) {
builder.withTransparent(); builder.withTransparent();
} }
} }
@ -412,8 +412,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
drawMaterialKey = _drawMaterial->getKey(); drawMaterialKey = _drawMaterial->getKey();
} }
bool isTranslucent = bool isTranslucent = drawMaterialKey.isTranslucent();
drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap();
bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty(); bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty();
bool hasSpecular = drawMaterialKey.isMetallicMap(); bool hasSpecular = drawMaterialKey.isMetallicMap();
bool hasLightmap = drawMaterialKey.isLightmapMap(); bool hasLightmap = drawMaterialKey.isLightmapMap();

View file

@ -29,6 +29,10 @@ void main(void) {
int matKey = getMaterialKey(mat); int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat); vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color; albedo *= _color;
@ -41,7 +45,7 @@ void main(void) {
packDeferredFragment( packDeferredFragment(
normalize(_normal.xyz), normalize(_normal.xyz),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), opacity,
albedo, albedo,
roughness, roughness,
getMaterialMetallic(mat), getMaterialMetallic(mat),

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat); int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$> <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat); vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color; albedo *= _color;
@ -45,7 +49,7 @@ void main(void) {
packDeferredFragment( packDeferredFragment(
viewNormal, viewNormal,
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), opacity,
albedo, albedo,
roughness, roughness,
getMaterialMetallic(mat), getMaterialMetallic(mat),

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat); int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$> <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, normalTex, metallicTex, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat); vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color; albedo *= _color;
@ -49,7 +53,7 @@ void main(void) {
packDeferredFragment( packDeferredFragment(
normalize(viewNormal.xyz), normalize(viewNormal.xyz),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), opacity,
albedo, albedo,
roughness, roughness,
metallic, metallic,

View file

@ -30,6 +30,10 @@ void main(void) {
int matKey = getMaterialKey(mat); int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$> <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, metallicTex, emissiveTex, occlusionTex)$>
float opacity = 1.0;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat); vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color; albedo *= _color;
@ -45,7 +49,7 @@ void main(void) {
packDeferredFragment( packDeferredFragment(
normalize(_normal), normalize(_normal),
evalOpaqueFinalAlpha(getMaterialOpacity(mat), albedoTex.a), opacity,
albedo, albedo,
roughness, roughness,
metallic, metallic,

View file

@ -36,6 +36,10 @@ void main(void) {
int matKey = getMaterialKey(mat); int matKey = getMaterialKey(mat);
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$> <$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
float opacity = getMaterialOpacity(mat) * _alpha;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)&>;
<$discardTransparent(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat); vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
albedo *= _color; albedo *= _color;
@ -52,8 +56,6 @@ void main(void) {
vec3 fragPosition = _position.xyz; vec3 fragPosition = _position.xyz;
vec3 fragNormal = normalize(_normal); vec3 fragNormal = normalize(_normal);
float fragOpacity = getMaterialOpacity(mat) * albedoTex.a * _alpha;
TransformCamera cam = getTransformCamera(); TransformCamera cam = getTransformCamera();
_fragColor = vec4(evalAmbientSphereGlobalColor( _fragColor = vec4(evalAmbientSphereGlobalColor(
@ -66,5 +68,5 @@ void main(void) {
metallic, metallic,
emissive, emissive,
roughness), roughness),
fragOpacity); opacity);
} }

View file

@ -114,6 +114,7 @@ void FetchSpatialTree::run(const SceneContextPointer& sceneContext, const Render
void CullSpatialSelection::configure(const Config& config) { void CullSpatialSelection::configure(const Config& config) {
_justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum); _justFrozeFrustum = _justFrozeFrustum || (config.freezeFrustum && !_freezeFrustum);
_freezeFrustum = config.freezeFrustum; _freezeFrustum = config.freezeFrustum;
_skipCulling = config.skipCulling;
} }
void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
@ -191,60 +192,112 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
// visibility cull if partially selected ( octree cell contianing it was partial) // visibility cull if partially selected ( octree cell contianing it was partial)
// distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item)
// inside & fit items: easy, just filter if (_skipCulling) {
{ // inside & fit items: filter only, culling is disabled
PerformanceTimer perfTimer("insideFitItems"); {
for (auto id : inSelection.insideItems) { PerformanceTimer perfTimer("insideFitItems");
auto& item = scene->getItem(id); for (auto id : inSelection.insideItems) {
if (_filter.test(item.getKey())) { auto& item = scene->getItem(id);
ItemBound itemBound(id, item.getBound()); if (_filter.test(item.getKey())) {
outItems.emplace_back(itemBound); ItemBound itemBound(id, item.getBound());
}
}
}
// inside & subcell items: filter & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound); outItems.emplace_back(itemBound);
} }
} }
} }
}
// partial & fit items: filter & frustum cull // inside & subcell items: filter only, culling is disabled
{ {
PerformanceTimer perfTimer("partialFitItems"); PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.partialItems) { for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id); auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) { if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound()); ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
outItems.emplace_back(itemBound); outItems.emplace_back(itemBound);
} }
} }
} }
}
// partial & subcell items:: filter & frutum cull & solidangle cull // partial & fit items: filter only, culling is disabled
{ {
PerformanceTimer perfTimer("partialSmallItems"); PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialSubcellItems) { for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id); auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) { if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound()); ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) { outItems.emplace_back(itemBound);
}
}
}
// partial & subcell items: filter only, culling is disabled
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
} else {
// inside & fit items: easy, just filter
{
PerformanceTimer perfTimer("insideFitItems");
for (auto id : inSelection.insideItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
outItems.emplace_back(itemBound);
}
}
}
// inside & subcell items: filter & distance cull
{
PerformanceTimer perfTimer("insideSmallItems");
for (auto id : inSelection.insideSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.solidAngleTest(itemBound.bound)) { if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound); outItems.emplace_back(itemBound);
} }
} }
} }
} }
// partial & fit items: filter & frustum cull
{
PerformanceTimer perfTimer("partialFitItems");
for (auto id : inSelection.partialItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
// partial & subcell items:: filter & frutum cull & solidangle cull
{
PerformanceTimer perfTimer("partialSmallItems");
for (auto id : inSelection.partialSubcellItems) {
auto& item = scene->getItem(id);
if (_filter.test(item.getKey())) {
ItemBound itemBound(id, item.getBound());
if (test.frustumTest(itemBound.bound)) {
if (test.solidAngleTest(itemBound.bound)) {
outItems.emplace_back(itemBound);
}
}
}
}
}
} }
details._rendered += (int)outItems.size(); details._rendered += (int)outItems.size();

View file

@ -70,14 +70,16 @@ namespace render {
Q_OBJECT Q_OBJECT
Q_PROPERTY(int numItems READ getNumItems) Q_PROPERTY(int numItems READ getNumItems)
Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum) Q_PROPERTY(bool freezeFrustum MEMBER freezeFrustum WRITE setFreezeFrustum)
Q_PROPERTY(bool skipCulling MEMBER skipCulling WRITE setSkipCulling)
public: public:
int numItems{ 0 }; int numItems{ 0 };
int getNumItems() { return numItems; } int getNumItems() { return numItems; }
bool freezeFrustum{ false }; bool freezeFrustum{ false };
bool skipCulling{ false };
public slots: public slots:
void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); } void setFreezeFrustum(bool enabled) { freezeFrustum = enabled; emit dirty(); }
void setSkipCulling(bool enabled) { skipCulling = enabled; emit dirty(); }
signals: signals:
void dirty(); void dirty();
}; };
@ -85,6 +87,7 @@ namespace render {
class CullSpatialSelection { class CullSpatialSelection {
bool _freezeFrustum{ false }; // initialized by Config bool _freezeFrustum{ false }; // initialized by Config
bool _justFrozeFrustum{ false }; bool _justFrozeFrustum{ false };
bool _skipCulling{ false };
ViewFrustum _frozenFrutstum; ViewFrustum _frozenFrutstum;
public: public:
using Config = CullSpatialSelectionConfig; using Config = CullSpatialSelectionConfig;

View file

@ -20,6 +20,10 @@ public:
template <typename F> template <typename F>
Finally(F f) : _f(f) {} Finally(F f) : _f(f) {}
~Finally() { _f(); } ~Finally() { _f(); }
void trigger() {
_f();
_f = [] {};
}
private: private:
std::function<void()> _f; std::function<void()> _f;
}; };

View file

@ -63,7 +63,7 @@ public:
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; } void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
float computeVolume() const; float computeVolume() const;
/// Returns whether point is inside the shape /// Returns whether point is inside the shape
/// For compound shapes it will only return whether it is inside the bounding box /// For compound shapes it will only return whether it is inside the bounding box
bool contains(const glm::vec3& point) const; bool contains(const glm::vec3& point) const;

View file

@ -8,6 +8,7 @@
#include "NsightHelpers.h" #include "NsightHelpers.h"
#ifdef _WIN32
#if defined(NSIGHT_FOUND) #if defined(NSIGHT_FOUND)
#include "nvToolsExt.h" #include "nvToolsExt.h"
@ -15,8 +16,28 @@ ProfileRange::ProfileRange(const char *name) {
nvtxRangePush(name); nvtxRangePush(name);
} }
ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) {
nvtxEventAttributes_t eventAttrib = {0};
eventAttrib.version = NVTX_VERSION;
eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
eventAttrib.colorType = NVTX_COLOR_ARGB;
eventAttrib.color = argbColor;
eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;
eventAttrib.message.ascii = name;
eventAttrib.payload.llValue = payload;
eventAttrib.payloadType = NVTX_PAYLOAD_TYPE_UNSIGNED_INT64;
nvtxRangePushEx(&eventAttrib);
}
ProfileRange::~ProfileRange() { ProfileRange::~ProfileRange() {
nvtxRangePop(); nvtxRangePop();
} }
#else
ProfileRange::ProfileRange(const char *name) {}
ProfileRange::ProfileRange(const char *name, uint32_t argbColor, uint64_t payload) {}
ProfileRange::~ProfileRange() {}
#endif #endif
#endif // _WIN32

View file

@ -9,16 +9,21 @@
#ifndef hifi_gl_NsightHelpers_h #ifndef hifi_gl_NsightHelpers_h
#define hifi_gl_NsightHelpers_h #define hifi_gl_NsightHelpers_h
#if defined(NSIGHT_FOUND) #ifdef _WIN32
class ProfileRange { #include <stdint.h>
public:
ProfileRange(const char *name); class ProfileRange {
~ProfileRange(); public:
}; ProfileRange(const char *name);
ProfileRange(const char *name, uint32_t argbColor, uint64_t payload);
~ProfileRange();
};
#define PROFILE_RANGE(name) ProfileRange profileRangeThis(name); #define PROFILE_RANGE(name) ProfileRange profileRangeThis(name);
#define PROFILE_RANGE_EX(name, argbColor, payload) ProfileRange profileRangeThis(name, argbColor, payload);
#else #else
#define PROFILE_RANGE(name) #define PROFILE_RANGE(name)
#define PROFILE_RANGE_EX(name, argbColor, payload)
#endif #endif
#endif
#endif

View file

@ -14,6 +14,8 @@
#include <QtQml/QQmlContext> #include <QtQml/QQmlContext>
#include <QtWebChannel/QWebChannel>
#include <QtScript/QScriptContext> #include <QtScript/QScriptContext>
#include <QtScript/QScriptEngine> #include <QtScript/QScriptEngine>
@ -31,19 +33,39 @@ static const char* const URL_PROPERTY = "source";
// Method called by Qt scripts to create a new web window in the overlay // Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine,
[&](QObject* object) { return new QmlWebWindowClass(object); }); [&](QObject* object) { return new QmlWebWindowClass(object); });
} }
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) {
_uid = QUuid::createUuid().toString();
asQuickItem()->setProperty("uid", _uid);
auto webchannelVar = qmlWindow->property("webChannel");
_webchannel = qvariant_cast<QWebChannel*>(webchannelVar);
Q_ASSERT(_webchannel);
_webchannel->registerObject(_uid, this);
} }
void QmlWebWindowClass::emitScriptEvent(const QVariant& scriptMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
} else {
emit scriptEventReceived(scriptMessage);
}
}
// FIXME remove. void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
void QmlWebWindowClass::handleNavigation(const QString& url) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
} else {
emit webEventReceived(webMessage);
}
} }
QString QmlWebWindowClass::getURL() const { QString QmlWebWindowClass::getURL() const {
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant { QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
if (_qmlWindow.isNull()) {
return QVariant();
}
return _qmlWindow->property(URL_PROPERTY); return _qmlWindow->property(URL_PROPERTY);
}); });
return result.toString(); return result.toString();
@ -54,6 +76,8 @@ extern QString fixupHifiUrl(const QString& urlString);
void QmlWebWindowClass::setURL(const QString& urlString) { void QmlWebWindowClass::setURL(const QString& urlString) {
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] { DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
_qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString)); if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString));
}
}); });
} }

View file

@ -11,10 +11,13 @@
#include "QmlWindowClass.h" #include "QmlWindowClass.h"
class QWebChannel;
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWebWindowClass : public QmlWindowClass { class QmlWebWindowClass : public QmlWindowClass {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString url READ getURL CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT)
Q_PROPERTY(QString uid READ getUid CONSTANT)
public: public:
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
@ -23,12 +26,18 @@ public:
public slots: public slots:
QString getURL() const; QString getURL() const;
void setURL(const QString& url); void setURL(const QString& url);
const QString& getUid() const { return _uid; }
void emitScriptEvent(const QVariant& scriptMessage);
void emitWebEvent(const QVariant& webMessage);
signals: signals:
void urlChanged(); void urlChanged();
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
private slots: private:
void handleNavigation(const QString& url); QString _uid;
QWebChannel* _webchannel { nullptr };
}; };
#endif #endif

View file

@ -26,10 +26,6 @@
#include "OffscreenUi.h" #include "OffscreenUi.h"
QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
static const char* const SOURCE_PROPERTY = "source"; static const char* const SOURCE_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title"; static const char* const TITLE_PROPERTY = "title";
static const char* const WIDTH_PROPERTY = "width"; static const char* const WIDTH_PROPERTY = "width";
@ -37,54 +33,6 @@ static const char* const HEIGHT_PROPERTY = "height";
static const char* const VISIBILE_PROPERTY = "visible"; static const char* const VISIBILE_PROPERTY = "visible";
static const char* const TOOLWINDOW_PROPERTY = "toolWindow"; static const char* const TOOLWINDOW_PROPERTY = "toolWindow";
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
}
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> builder) std::function<QmlWindowClass*(QObject*)> builder)
@ -168,10 +116,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
} }
offscreenUi->returnFromUiThread([&] { offscreenUi->returnFromUiThread([&] {
setupServer();
retVal = builder(newTab); retVal = builder(newTab);
retVal->_toolWindow = true; retVal->_toolWindow = true;
registerObject(url.toLower(), retVal);
return QVariant(); return QVariant();
}); });
} else { } else {
@ -179,10 +125,8 @@ QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource,
QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(offscreenUi.data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, qmlSource), Q_ARG(const QString&, qmlSource),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) { Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
setupServer();
retVal = builder(object); retVal = builder(object);
context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership);
registerObject(url.toLower(), retVal);
if (!title.isEmpty()) { if (!title.isEmpty()) {
retVal->setTitle(title); retVal->setTitle(title);
} }
@ -209,12 +153,9 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine*
}); });
} }
QmlWindowClass::QmlWindowClass(QObject* qmlWindow) QmlWindowClass::QmlWindowClass(QObject* qmlWindow) : _qmlWindow(qmlWindow) {
: _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow); Q_ASSERT(_qmlWindow);
Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow)); Q_ASSERT(dynamic_cast<const QQuickItem*>(_qmlWindow.data()));
// Forward messages received from QML on to the script // Forward messages received from QML on to the script
connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(sendToScript(QVariant)), this, SIGNAL(fromQml(const QVariant&)), Qt::QueuedConnection);
} }
@ -228,19 +169,11 @@ QmlWindowClass::~QmlWindowClass() {
close(); close();
} }
void QmlWindowClass::registerObject(const QString& name, QObject* object) {
webChannel.registerObject(name, object);
}
void QmlWindowClass::deregisterObject(QObject* object) {
webChannel.deregisterObject(object);
}
QQuickItem* QmlWindowClass::asQuickItem() const { QQuickItem* QmlWindowClass::asQuickItem() const {
if (_toolWindow) { if (_toolWindow) {
return DependencyManager::get<OffscreenUi>()->getToolWindow(); return DependencyManager::get<OffscreenUi>()->getToolWindow();
} }
return dynamic_cast<QQuickItem*>(_qmlWindow); return _qmlWindow.isNull() ? nullptr : dynamic_cast<QQuickItem*>(_qmlWindow.data());
} }
void QmlWindowClass::setVisible(bool visible) { void QmlWindowClass::setVisible(bool visible) {
@ -260,32 +193,34 @@ void QmlWindowClass::setVisible(bool visible) {
bool QmlWindowClass::isVisible() const { bool QmlWindowClass::isVisible() const {
// The tool window itself has special logic based on whether any tabs are enabled // The tool window itself has special logic based on whether any tabs are enabled
if (_toolWindow) { return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] {
auto targetTab = dynamic_cast<QQuickItem*>(_qmlWindow); if (_qmlWindow.isNull()) {
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] { return QVariant::fromValue(false);
return QVariant::fromValue(targetTab->isEnabled()); }
}).toBool(); if (_toolWindow) {
} else { return QVariant::fromValue(dynamic_cast<QQuickItem*>(_qmlWindow.data())->isEnabled());
QQuickItem* targetWindow = asQuickItem(); } else {
return DependencyManager::get<OffscreenUi>()->returnFromUiThread([&] { return QVariant::fromValue(asQuickItem()->isVisible());
return QVariant::fromValue(targetWindow->isVisible()); }
}).toBool(); }).toBool();
}
} }
glm::vec2 QmlWindowClass::getPosition() const { glm::vec2 QmlWindowClass::getPosition() const {
QQuickItem* targetWindow = asQuickItem();
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant { QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
return targetWindow->position(); if (_qmlWindow.isNull()) {
return QVariant(QPointF(0, 0));
}
return asQuickItem()->position();
}); });
return toGlm(result.toPointF()); return toGlm(result.toPointF());
} }
void QmlWindowClass::setPosition(const glm::vec2& position) { void QmlWindowClass::setPosition(const glm::vec2& position) {
QQuickItem* targetWindow = asQuickItem();
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] { DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetWindow->setPosition(QPointF(position.x, position.y)); if (!_qmlWindow.isNull()) {
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
}); });
} }
@ -299,17 +234,21 @@ glm::vec2 toGlm(const QSizeF& size) {
} }
glm::vec2 QmlWindowClass::getSize() const { glm::vec2 QmlWindowClass::getSize() const {
QQuickItem* targetWindow = asQuickItem();
QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant { QVariant result = DependencyManager::get<OffscreenUi>()->returnFromUiThread([&]()->QVariant {
if (_qmlWindow.isNull()) {
return QVariant(QSizeF(0, 0));
}
QQuickItem* targetWindow = asQuickItem();
return QSizeF(targetWindow->width(), targetWindow->height()); return QSizeF(targetWindow->width(), targetWindow->height());
}); });
return toGlm(result.toSizeF()); return toGlm(result.toSizeF());
} }
void QmlWindowClass::setSize(const glm::vec2& size) { void QmlWindowClass::setSize(const glm::vec2& size) {
QQuickItem* targetWindow = asQuickItem();
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] { DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetWindow->setSize(QSizeF(size.x, size.y)); if (!_qmlWindow.isNull()) {
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
}); });
} }
@ -318,9 +257,10 @@ void QmlWindowClass::setSize(int width, int height) {
} }
void QmlWindowClass::setTitle(const QString& title) { void QmlWindowClass::setTitle(const QString& title) {
QQuickItem* targetWindow = asQuickItem();
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] { DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetWindow->setProperty(TITLE_PROPERTY, title); if (!_qmlWindow.isNull()) {
asQuickItem()->setProperty(TITLE_PROPERTY, title);
}
}); });
} }
@ -345,7 +285,12 @@ void QmlWindowClass::hasClosed() {
} }
void QmlWindowClass::raise() { void QmlWindowClass::raise() {
QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::QueuedConnection); auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
if (!_qmlWindow.isNull()) {
QMetaObject::invokeMethod(asQuickItem(), "raise", Qt::DirectConnection);
}
});
} }
#include "QmlWindowClass.moc" #include "QmlWindowClass.moc"

View file

@ -10,42 +10,21 @@
#define hifi_ui_QmlWindowClass_h #define hifi_ui_QmlWindowClass_h
#include <QtCore/QObject> #include <QtCore/QObject>
#include <GLMHelpers.h> #include <QtCore/QPointer>
#include <QtScript/QScriptValue> #include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem> #include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
#include <GLMHelpers.h>
class QScriptEngine; class QScriptEngine;
class QScriptContext; class QScriptContext;
class QmlWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
public:
QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {}
public slots :
void emitWebEvent(const QString& data);
void emitScriptEvent(const QString& data);
signals:
void webEventReceived(const QString& data);
void scriptEventReceived(int windowId, const QString& data);
private:
const QmlWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
class QmlWindowClass : public QObject { class QmlWindowClass : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT)
Q_PROPERTY(int windowId READ getWindowId CONSTANT) Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged)
Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged)
public: public:
@ -64,21 +43,19 @@ public slots:
glm::vec2 getSize() const; glm::vec2 getSize() const;
void setSize(const glm::vec2& size); void setSize(const glm::vec2& size);
void setSize(int width, int height); void setSize(int width, int height);
void setTitle(const QString& title); void setTitle(const QString& title);
// Ugh.... do not want to do
Q_INVOKABLE void raise(); Q_INVOKABLE void raise();
Q_INVOKABLE void close(); Q_INVOKABLE void close();
Q_INVOKABLE int getWindowId() const { return _windowId; }; Q_INVOKABLE QObject* getEventBridge() { return this; };
Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; };
// Scripts can use this to send a message to the QML object // Scripts can use this to send a message to the QML object
void sendToQml(const QVariant& message); void sendToQml(const QVariant& message);
signals: signals:
void visibilityChanged(bool visible); // Tool window void visibilityChanged(bool visible); // Tool window
void positionChanged();
void sizeChanged();
void moved(glm::vec2 position); void moved(glm::vec2 position);
void resized(QSizeF size); void resized(QSizeF size);
void closed(); void closed();
@ -92,19 +69,13 @@ protected:
static QScriptValue internalConstructor(const QString& qmlSource, static QScriptValue internalConstructor(const QString& qmlSource,
QScriptContext* context, QScriptEngine* engine, QScriptContext* context, QScriptEngine* engine,
std::function<QmlWindowClass*(QObject*)> function); std::function<QmlWindowClass*(QObject*)> function);
static void setupServer();
static void registerObject(const QString& name, QObject* object);
static void deregisterObject(QObject* object);
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const; QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
// FIXME needs to be initialized in the ctor once we have support // FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML // for tool window panes in QML
bool _toolWindow { false }; bool _toolWindow { false };
const int _windowId; QPointer<QObject> _qmlWindow;
QObject* _qmlWindow;
QString _source; QString _source;
}; };

View file

@ -6,6 +6,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "OculusDisplayPlugin.h" #include "OculusDisplayPlugin.h"
#include <shared/NsightHelpers.h>
#include "OculusHelpers.h" #include "OculusHelpers.h"
const QString OculusDisplayPlugin::NAME("Oculus Rift"); const QString OculusDisplayPlugin::NAME("Oculus Rift");
@ -54,6 +55,9 @@ void OculusDisplayPlugin::updateFrameData() {
} }
void OculusDisplayPlugin::hmdPresent() { void OculusDisplayPlugin::hmdPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
if (!_currentSceneTexture) { if (!_currentSceneTexture) {
return; return;
} }

View file

@ -21,7 +21,7 @@
#include <PerfStat.h> #include <PerfStat.h>
#include <plugins/PluginContainer.h> #include <plugins/PluginContainer.h>
#include <ViewFrustum.h> #include <ViewFrustum.h>
#include <shared/NsightHelpers.h>
#include "OpenVrHelpers.h" #include "OpenVrHelpers.h"
Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_DECLARE_LOGGING_CATEGORY(displayplugins)
@ -69,6 +69,9 @@ void OpenVrDisplayPlugin::internalActivate() {
_compositor = vr::VRCompositor(); _compositor = vr::VRCompositor();
Q_ASSERT(_compositor); Q_ASSERT(_compositor);
// enable async time warp
// _compositor->ForceInterleavedReprojectionOn(true);
// set up default sensor space such that the UI overlay will align with the front of the room. // set up default sensor space such that the UI overlay will align with the front of the room.
auto chaperone = vr::VRChaperone(); auto chaperone = vr::VRChaperone();
if (chaperone) { if (chaperone) {
@ -119,14 +122,11 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) {
float vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float); float vsyncToPhotons = _system->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SecondsFromVsyncToPhotons_Float);
#if THREADED_PRESENT #if THREADED_PRESENT
// TODO: this seems awfuly long, 44ms total, but it produced the best results. // 3 frames of prediction + vsyncToPhotons = 44ms total
const float NUM_PREDICTION_FRAMES = 3.0f; const float NUM_PREDICTION_FRAMES = 3.0f;
float predictedSecondsFromNow = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons; float predictedSecondsFromNow = NUM_PREDICTION_FRAMES * frameDuration + vsyncToPhotons;
#else #else
uint64_t frameCounter; float predictedSecondsFromNow = frameDuration + vsyncToPhotons;
float timeSinceLastVsync;
_system->GetTimeSinceLastVsync(&timeSinceLastVsync, &frameCounter);
float predictedSecondsFromNow = 3.0f * frameDuration - timeSinceLastVsync + vsyncToPhotons;
#endif #endif
vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; vr::TrackedDevicePose_t predictedTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
@ -144,6 +144,9 @@ void OpenVrDisplayPlugin::updateHeadPose(uint32_t frameIndex) {
} }
void OpenVrDisplayPlugin::hmdPresent() { void OpenVrDisplayPlugin::hmdPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
// Flip y-axis since GL UV coords are backwards. // Flip y-axis since GL UV coords are backwards.
static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 };
static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 };
@ -152,6 +155,10 @@ void OpenVrDisplayPlugin::hmdPresent() {
_compositor->Submit(vr::Eye_Left, &texture, &leftBounds); _compositor->Submit(vr::Eye_Left, &texture, &leftBounds);
_compositor->Submit(vr::Eye_Right, &texture, &rightBounds); _compositor->Submit(vr::Eye_Right, &texture, &rightBounds);
}
void OpenVrDisplayPlugin::postPreview() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentRenderFrameIndex)
vr::TrackedDevicePose_t currentTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; vr::TrackedDevicePose_t currentTrackedDevicePose[vr::k_unMaxTrackedDeviceCount];
_compositor->WaitGetPoses(currentTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0); _compositor->WaitGetPoses(currentTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, nullptr, 0);

View file

@ -35,6 +35,7 @@ protected:
void hmdPresent() override; void hmdPresent() override;
bool isHmdMounted() const override; bool isHmdMounted() const override;
void postPreview() override;
private: private:
vr::IVRSystem* _system { nullptr }; vr::IVRSystem* _system { nullptr };

View file

@ -51,7 +51,7 @@ vr::IVRSystem* acquireOpenVrSystem() {
if (!activeHmd) { if (!activeHmd) {
qCDebug(displayplugins) << "openvr: No vr::IVRSystem instance active, building"; qCDebug(displayplugins) << "openvr: No vr::IVRSystem instance active, building";
vr::EVRInitError eError = vr::VRInitError_None; vr::EVRInitError eError = vr::VRInitError_None;
activeHmd = vr::VR_Init(&eError); activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
qCDebug(displayplugins) << "openvr display: HMD is " << activeHmd << " error is " << eError; qCDebug(displayplugins) << "openvr display: HMD is " << activeHmd << " error is " << eError;
} }
if (activeHmd) { if (activeHmd) {

View file

@ -44,6 +44,14 @@ ready = function() {
var domainServer = remote.getGlobal('domainServer'); var domainServer = remote.getGlobal('domainServer');
var acMonitor = remote.getGlobal('acMonitor'); var acMonitor = remote.getGlobal('acMonitor');
var pendingLines = {
'ds': new Array(),
'ac': new Array()
};
var UPDATE_INTERVAL = 16; // Update log at ~60 fps
var interval = setInterval(flushPendingLines, UPDATE_INTERVAL);
var logWatchers = { var logWatchers = {
'ds': { 'ds': {
}, },
@ -83,7 +91,7 @@ ready = function() {
var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 }); var logTail = new Tail(cleanFilePath, '\n', { start: start, interval: 500 });
logTail.on('line', function(msg) { logTail.on('line', function(msg) {
appendLogMessage(msg, stream); pendingLines[stream].push(msg);
}); });
logTail.on('error', function(error) { logTail.on('error', function(error) {
@ -107,6 +115,7 @@ ready = function() {
} }
window.onbeforeunload = function(e) { window.onbeforeunload = function(e) {
clearInterval(interval);
domainServer.removeListener('logs-updated', updateLogFiles); domainServer.removeListener('logs-updated', updateLogFiles);
acMonitor.removeListener('logs-updated', updateLogFiles); acMonitor.removeListener('logs-updated', updateLogFiles);
}; };
@ -164,14 +173,23 @@ ready = function() {
return !filter || message.toLowerCase().indexOf(filter) >= 0; return !filter || message.toLowerCase().indexOf(filter) >= 0;
} }
function appendLogMessage(msg, name) { function appendLogMessages(name) {
var array = pendingLines[name];
if (array.length === 0) {
return;
}
if (array.length > maxLogLines) {
array = array.slice(-maxLogLines);
}
console.log(name, array.length);
var id = name == "ds" ? "domain-server" : "assignment-client"; var id = name == "ds" ? "domain-server" : "assignment-client";
var $pidLog = $('#' + id); var $pidLog = $('#' + id);
var size = ++tabStates[id].size; var size = tabStates[id].size + array.length;
if (size > maxLogLines) { if (size > maxLogLines) {
$pidLog.find('div.log-line:first').remove(); $pidLog.find('div.log-line:lt(' + (size - maxLogLines) + ')').remove();
removed = true;
} }
var wasAtBottom = false; var wasAtBottom = false;
@ -179,17 +197,25 @@ ready = function() {
wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height()); wasAtBottom = $pidLog[0].scrollTop >= ($pidLog[0].scrollHeight - $pidLog.height());
} }
var $logLine = $('<div class="log-line">').text(msg); for (line in array) {
if (!shouldDisplayLogMessage(msg)) { var $logLine = $('<div class="log-line">').text(array[line]);
$logLine.hide(); if (!shouldDisplayLogMessage(array[line])) {
$logLine.hide();
}
$pidLog.append($logLine);
} }
$pidLog.append($logLine); delete pendingLines[name];
pendingLines[name] = new Array();
if (wasAtBottom) { if (wasAtBottom) {
$pidLog.scrollTop($pidLog[0].scrollHeight); $pidLog.scrollTop($pidLog[0].scrollHeight);
} }
}
function flushPendingLines() {
appendLogMessages('ds');
appendLogMessages('ac');
} }
// handle filtering of table rows on input change // handle filtering of table rows on input change

View file

@ -14,6 +14,8 @@
#include <BulletUtil.h> #include <BulletUtil.h>
#include <functional>
// Implements functionality in QTestExtensions.h for glm types // Implements functionality in QTestExtensions.h for glm types
// There are 3 functions in here (which need to be defined for all types that use them): // There are 3 functions in here (which need to be defined for all types that use them):
// //

View file

@ -33,7 +33,7 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() {
// verity we can compute the inertia tensor of a box in two different ways: // verity we can compute the inertia tensor of a box in two different ways:
// (a) as one box // (a) as one box
// (b) as a combination of two partial boxes. // (b) as a combination of two partial boxes.
btScalar bigBoxX = 7.0f; btScalar bigBoxX = 7.0f;
btScalar bigBoxY = 9.0f; btScalar bigBoxY = 9.0f;
btScalar bigBoxZ = 11.0f; btScalar bigBoxZ = 11.0f;
@ -62,9 +62,9 @@ void MeshMassPropertiesTests::testParallelAxisTheorem() {
} }
void MeshMassPropertiesTests::testTetrahedron(){ void MeshMassPropertiesTests::testTetrahedron(){
// given the four vertices of a tetrahedron verify the analytic formula for inertia // given the four vertices of a tetrahedron verify the analytic formula for inertia
// agrees with expected results // agrees with expected results
// these numbers from the Tonon paper: // these numbers from the Tonon paper:
btVector3 points[4]; btVector3 points[4];
points[0] = btVector3(8.33220f, -11.86875f, 0.93355f); points[0] = btVector3(8.33220f, -11.86875f, 0.93355f);
@ -102,14 +102,14 @@ void MeshMassPropertiesTests::testTetrahedron(){
} }
btMatrix3x3 inertia; btMatrix3x3 inertia;
computeTetrahedronInertia(volume, points, inertia); computeTetrahedronInertia(volume, points, inertia);
QCOMPARE_WITH_ABS_ERROR(volume, expectedVolume, acceptableRelativeError * volume); QCOMPARE_WITH_ABS_ERROR(volume, expectedVolume, acceptableRelativeError * volume);
QCOMPARE_WITH_RELATIVE_ERROR(inertia, expectedInertia, acceptableRelativeError); QCOMPARE_WITH_RELATIVE_ERROR(inertia, expectedInertia, acceptableRelativeError);
} }
void MeshMassPropertiesTests::testOpenTetrahedonMesh() { void MeshMassPropertiesTests::testOpenTetrahedonMesh() {
// given the simplest possible mesh (open, with one triangle) // given the simplest possible mesh (open, with one triangle)
// verify MeshMassProperties computes the right nubers // verify MeshMassProperties computes the right nubers
// these numbers from the Tonon paper: // these numbers from the Tonon paper:
@ -155,7 +155,7 @@ void MeshMassPropertiesTests::testOpenTetrahedonMesh() {
void MeshMassPropertiesTests::testClosedTetrahedronMesh() { void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
// given a tetrahedron as a closed mesh of four tiangles // given a tetrahedron as a closed mesh of four tiangles
// verify MeshMassProperties computes the right nubers // verify MeshMassProperties computes the right nubers
// these numbers from the Tonon paper: // these numbers from the Tonon paper:
VectorOfPoints points; VectorOfPoints points;
points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f)); points.push_back(btVector3(8.33220f, -11.86875f, 0.93355f));
@ -186,7 +186,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
// compute mass properties // compute mass properties
MeshMassProperties mesh(points, triangles); MeshMassProperties mesh(points, triangles);
// verify // verify
QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume); QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume);
QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError); QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError);
@ -210,7 +210,7 @@ void MeshMassPropertiesTests::testClosedTetrahedronMesh() {
void MeshMassPropertiesTests::testBoxAsMesh() { void MeshMassPropertiesTests::testBoxAsMesh() {
// verify that a mesh box produces the same mass properties as the analytic box. // verify that a mesh box produces the same mass properties as the analytic box.
// build a box: // build a box:
// / // /
// y // y
@ -265,7 +265,7 @@ void MeshMassPropertiesTests::testBoxAsMesh() {
MeshMassProperties mesh(points, triangles); MeshMassProperties mesh(points, triangles);
// verify // verify
QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume); QCOMPARE_WITH_ABS_ERROR(mesh._volume, expectedVolume, acceptableRelativeError * expectedVolume);
QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError); QCOMPARE_WITH_ABS_ERROR(mesh._centerOfMass, expectedCenterOfMass, acceptableAbsoluteError);

View file

@ -21,7 +21,7 @@ void ShapeManagerTests::testShapeAccounting() {
ShapeManager shapeManager; ShapeManager shapeManager;
ShapeInfo info; ShapeInfo info;
info.setBox(glm::vec3(1.0f, 1.0f, 1.0f)); info.setBox(glm::vec3(1.0f, 1.0f, 1.0f));
int numReferences = shapeManager.getNumReferences(info); int numReferences = shapeManager.getNumReferences(info);
QCOMPARE(numReferences, 0); QCOMPARE(numReferences, 0);
@ -42,10 +42,10 @@ void ShapeManagerTests::testShapeAccounting() {
QCOMPARE(numReferences, expectedNumReferences); QCOMPARE(numReferences, expectedNumReferences);
// release all references // release all references
bool released = shapeManager.releaseShape(info); bool released = shapeManager.releaseShape(shape);
numReferences--; numReferences--;
while (numReferences > 0) { while (numReferences > 0) {
released = shapeManager.releaseShape(info) && released; released = shapeManager.releaseShape(shape) && released;
numReferences--; numReferences--;
} }
QCOMPARE(released, true); QCOMPARE(released, true);
@ -69,7 +69,7 @@ void ShapeManagerTests::testShapeAccounting() {
QCOMPARE(numReferences, 1); QCOMPARE(numReferences, 1);
// release reference and verify that it is collected as garbage // release reference and verify that it is collected as garbage
released = shapeManager.releaseShape(info); released = shapeManager.releaseShape(shape);
shapeManager.collectGarbage(); shapeManager.collectGarbage();
QCOMPARE(shapeManager.getNumShapes(), 0); QCOMPARE(shapeManager.getNumShapes(), 0);
QCOMPARE(shapeManager.hasShape(shape), false); QCOMPARE(shapeManager.hasShape(shape), false);
@ -183,3 +183,58 @@ void ShapeManagerTests::addCapsuleShape() {
QCOMPARE(shape, otherShape); QCOMPARE(shape, otherShape);
*/ */
} }
void ShapeManagerTests::addCompoundShape() {
// initialize some points for generating tetrahedral convex hulls
QVector<glm::vec3> tetrahedron;
tetrahedron.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
tetrahedron.push_back(glm::vec3(1.0f, -1.0f, -1.0f));
tetrahedron.push_back(glm::vec3(-1.0f, 1.0f, -1.0f));
tetrahedron.push_back(glm::vec3(-1.0f, -1.0f, 1.0f));
int numHullPoints = tetrahedron.size();
// compute the points of the hulls
QVector< QVector<glm::vec3> > hulls;
int numHulls = 5;
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
for (int i = 0; i < numHulls; ++i) {
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
QVector<glm::vec3> hull;
float radius = (float)(i + 1);
for (int j = 0; j < numHullPoints; ++j) {
glm::vec3 point = radius * tetrahedron[j] + offset;
hull.push_back(point);
}
hulls.push_back(hull);
}
// create the ShapeInfo
ShapeInfo info;
info.setConvexHulls(hulls);
// create the shape
ShapeManager shapeManager;
btCollisionShape* shape = shapeManager.getShape(info);
QVERIFY(shape != nullptr);
// verify the shape is correct type
QCOMPARE(shape->getShapeType(), (int)COMPOUND_SHAPE_PROXYTYPE);
// verify the shape has correct number of children
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
QCOMPARE(compoundShape->getNumChildShapes(), numHulls);
// verify manager has only one shape
QCOMPARE(shapeManager.getNumShapes(), 1);
QCOMPARE(shapeManager.getNumReferences(info), 1);
// release the shape
shapeManager.releaseShape(shape);
QCOMPARE(shapeManager.getNumShapes(), 1);
QCOMPARE(shapeManager.getNumReferences(info), 0);
// collect garbage
shapeManager.collectGarbage();
QCOMPARE(shapeManager.getNumShapes(), 0);
QCOMPARE(shapeManager.getNumReferences(info), 0);
}

View file

@ -16,7 +16,7 @@
class ShapeManagerTests : public QObject { class ShapeManagerTests : public QObject {
Q_OBJECT Q_OBJECT
private slots: private slots:
void testShapeAccounting(); void testShapeAccounting();
void addManyShapes(); void addManyShapes();
@ -24,6 +24,7 @@ private slots:
void addSphereShape(); void addSphereShape();
void addCylinderShape(); void addCylinderShape();
void addCapsuleShape(); void addCapsuleShape();
void addCompoundShape();
}; };
#endif // hifi_ShapeManagerTests_h #endif // hifi_ShapeManagerTests_h

View file

@ -77,7 +77,7 @@ UDTTest::UDTTest(int& argc, char** argv) :
// randomize the seed for packet size randomization // randomize the seed for packet size randomization
srand(time(NULL)); srand(time(NULL));
_socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt()); _socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt());
qDebug() << "Test socket is listening on" << _socket.localPort(); qDebug() << "Test socket is listening on" << _socket.localPort();