Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3D Tiles - refine to visible children in replacement refinement #4287

Merged
merged 15 commits into from Sep 7, 2016
2 changes: 2 additions & 0 deletions Source/Core/DoublyLinkedList.js
Expand Up @@ -82,9 +82,11 @@ define([
};

DoublyLinkedList.prototype.splice = function(node, nextNode) {
//>>includeStart('debug', pragmas.debug);
if (!defined(node) || !defined(nextNode)) {
throw new DeveloperError('node and nextNode are required.');
}
//>>includeEnd('debug');

if (node === nextNode) {
return;
Expand Down
13 changes: 12 additions & 1 deletion Source/Scene/Cesium3DTile.js
Expand Up @@ -293,6 +293,13 @@ define([
*/
this.replaced = false;

/**
* The stored plane mask from the visibility check during tree traversal.
*
* @type {Boolean}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this CullingVolume?

*/
this.planeMask = true;

/**
* The last frame number the tile was selected in.
*
Expand Down Expand Up @@ -513,9 +520,13 @@ define([
* @private
*/
Cesium3DTile.prototype.contentsVisibility = function(cullingVolume) {
var boundingVolume = this._contentBoundingVolume;
if (!defined(boundingVolume)) {
return Intersect.INSIDE;
}
// PERFORMANCE_IDEA: is it possible to burn less CPU on this test since we know the
// tile's (not the content's) bounding volume intersects the culling volume?
return cullingVolume.computeVisibility(this.contentBoundingVolume);
return cullingVolume.computeVisibility(boundingVolume);
};

/**
Expand Down
95 changes: 54 additions & 41 deletions Source/Scene/Cesium3DTileset.js
Expand Up @@ -838,7 +838,10 @@ define([
}
}

function touch(tileset, tile) {
function touch(tileset, tile, outOfCore) {
if (!outOfCore) {
return;
}
var node = tile.replacementNode;
if (defined(node)) {
tileset._replacementList.splice(tileset._replacementSentinel, node);
Expand All @@ -865,7 +868,7 @@ define([
// may be potentially replaced. Tiles are moved to the right of the sentinel
// when they are selected so they will not be replaced.
var replacementList = tileset._replacementList;
tileset._replacementList.splice(replacementList.tail, tileset._replacementSentinel);
replacementList.splice(replacementList.tail, tileset._replacementSentinel);

var root = tileset._root;
root.distanceToCamera = root.distanceToTile(frameState);
Expand All @@ -876,6 +879,11 @@ define([
return;
}

root.planeMask = root.visibility(cullingVolume);
if (root.planeMask === CullingVolume.MASK_OUTSIDE) {
return;
}

if (root.contentUnloaded) {
requestContent(tileset, root, outOfCore);
return;
Expand All @@ -895,15 +903,10 @@ define([
var parentTransform = defined(t.parent) ? t.parent.computedTransform : tileset.modelMatrix;
t.computedTransform = Matrix4.multiply(parentTransform, t.transform, t.computedTransform);

var planeMask = t.visibility(cullingVolume);
if (planeMask === CullingVolume.MASK_OUTSIDE) {
// Tile is completely outside of the view frustum; therefore
// so are all of its children.
continue;
}
var planeMask = t.planeMask;
var fullyVisible = (planeMask === CullingVolume.MASK_INSIDE);

touch(tileset, t);
touch(tileset, t, outOfCore);

// Tile is inside/intersects the view frustum. How many pixels is its geometric error?
var sse = getScreenSpaceError(t.geometricError, t, frameState);
Expand All @@ -922,6 +925,7 @@ define([
if (t.contentReady) {
child = t.children[0];
child.parentPlaneMask = t.parentPlaneMask;
child.planeMask = t.planeMask;
child.distanceToCamera = t.distanceToCamera;
if (child.contentUnloaded) {
requestContent(tileset, child, outOfCore);
Expand Down Expand Up @@ -950,21 +954,21 @@ define([
// Sort children by distance for (1) request ordering, and (2) early-z
children.sort(sortChildrenByDistanceToCamera);

// With additive refinement, we only request children that are visible, compared
// to replacement refinement where we need all children.
// With additive refinement, we only request or refine when children are visible
for (k = 0; k < childrenLength; ++k) {
child = children[k];
// Store the plane mask so that the child can optimize based on its parent's returned mask
child.parentPlaneMask = planeMask;

// Use parent's geometric error with child's box to see if we already meet the SSE
if (getScreenSpaceError(t.geometricError, child, frameState) > maximumScreenSpaceError) {
if (child.contentUnloaded) {
if (child.visibility(cullingVolume) !== CullingVolume.MASK_OUTSIDE) {
// Store the plane mask so that the child can optimize based on its parent's returned mask
child.parentPlaneMask = planeMask;
child.planeMask = child.visibility(cullingVolume);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throughout, should we rename parentPlaneMask and planeMask to something like visibilityParentPlaneMask and visibilityPlaneMask?

// If the child is visible...
if (child.planeMask !== CullingVolume.MASK_OUTSIDE) {
if (child.contentUnloaded) {
requestContent(tileset, child, outOfCore);
} else {
stack.push(child);
}
} else {
stack.push(child);
}
}
}
Expand All @@ -984,51 +988,60 @@ define([
} else {
// Tile does not meet SSE.

// Get visibility for all children. Check if any visible children are not loaded.
var allVisibleChildrenLoaded = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a PERFORMANCE_TODO but I wonder if we can exploit temporal coherence to burn a bit less CPU here.

var someVisibleChildrenLoaded = false;
for (k = 0; k < childrenLength; ++k) {
child = children[k];
child.parentPlaneMask = planeMask;
child.planeMask = child.visibility(frameState.cullingVolume);
// If the child is visible...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having this comment everywhere consider adding a static isVisible function and pass it child.planeMask so everything is super obvious and explicit.

if (child.planeMask !== CullingVolume.MASK_OUTSIDE) {
if (child.contentReady) {
someVisibleChildrenLoaded = true;
} else {
allVisibleChildrenLoaded = false;
}
}
}

// Only sort children by distance if we are going to refine to them
// or slots are available to request them. If we are just rendering the
// tile (and can't make child requests because no slots are available)
// then the children do not need to be sorted.

var allChildrenLoaded = t.numberOfChildrenWithoutContent === 0;
if (allChildrenLoaded || t.canRequestContent()) {
if (someVisibleChildrenLoaded || t.canRequestContent()) {
// Distance is used for sorting now and for computing SSE when the tile comes off the stack.
computeDistanceToCamera(children, frameState);

// Sort children by distance for (1) request ordering, and (2) early-z
children.sort(sortChildrenByDistanceToCamera);
// TODO: same TODO as above.
}

if (!allChildrenLoaded) {
// Tile does not meet SSE. Add its commands since it is the best we have and request its children.
if (!allVisibleChildrenLoaded) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a comment here for the case empty parent case that we discussed offline today?

// Tile does not meet SSE. Add its commands and the commands of its visible children.
// This will cause the parent tile and child tiles to render simultaneously until
// all visible children are loaded.
selectTile(tileset, t, fullyVisible, frameState);

if (outOfCore) {
for (k = 0; (k < childrenLength) && t.canRequestContent(); ++k) {
child = children[k];
// PERFORMANCE_IDEA: we could spin a bit less CPU here by keeping separate lists for unloaded/ready children.
for (k = 0; k < childrenLength; ++k) {
child = children[k];
// If child is visible...
if (child.planeMask !== CullingVolume.MASK_OUTSIDE) {
if (child.contentUnloaded) {
requestContent(tileset, child, outOfCore);
} else {
// Touch loaded child even though it is not selected this frame since
// we want to keep it in the cache for when all children are loaded
// and this tile can refine to them.
touch(tileset, child);
stack.push(child);
}
}
}
} else {
// Tile does not meet SSE and its children are loaded. Refine to them in front-to-back order.
// Tile does not meet SSE and its visible children are loaded. Refine to them in front-to-back order.
for (k = 0; k < childrenLength; ++k) {
child = children[k];
// Store the plane mask so that the child can optimize based on its parent's returned mask
child.parentPlaneMask = planeMask;
stack.push(child);

// Touch the child tile now even if it turns out not to be visible when
// it comes off the stack. Since replacement refinement requires all child
// tiles to be loaded to refine to them, we want to keep it in the cache.
touch(tileset, child);
if (child.planeMask !== CullingVolume.MASK_OUTSIDE) {
stack.push(child);
}
}

t.replaced = true;
Expand Down Expand Up @@ -1059,7 +1072,7 @@ define([
for (j = 0; j < descendantsLength; ++j) {
descendant = refiningTile.descendantsWithContent[j];
if (!descendant.selected && !descendant.replaced &&
frameState.cullingVolume.computeVisibility(descendant.contentBoundingVolume) !== Intersect.OUTSIDE) {
(frameState.cullingVolume.computeVisibility(descendant.contentBoundingVolume) !== Intersect.OUTSIDE)) {
refinable = false;
break;
}
Expand Down