You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
9.1 KiB
JavaScript
315 lines
9.1 KiB
JavaScript
import defaultValue from "./defaultValue.js";
|
|
import defined from "./defined.js";
|
|
import DeveloperError from "./DeveloperError.js";
|
|
|
|
/**
|
|
* Encapsulates an algorithm to optimize triangles for the post
|
|
* vertex-shader cache. This is based on the 2007 SIGGRAPH paper
|
|
* 'Fast Triangle Reordering for Vertex Locality and Reduced Overdraw.'
|
|
* The runtime is linear but several passes are made.
|
|
*
|
|
* @namespace Tipsify
|
|
*
|
|
* @see <a href='http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf'>
|
|
* Fast Triangle Reordering for Vertex Locality and Reduced Overdraw</a>
|
|
* by Sander, Nehab, and Barczak
|
|
*
|
|
* @private
|
|
*/
|
|
var Tipsify = {};
|
|
|
|
/**
|
|
* Calculates the average cache miss ratio (ACMR) for a given set of indices.
|
|
*
|
|
* @param {Object} options Object with the following properties:
|
|
* @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
|
|
* in the vertex buffer that define the geometry's triangles.
|
|
* @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
|
|
* If not supplied, this value will be computed.
|
|
* @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
|
|
* @returns {Number} The average cache miss ratio (ACMR).
|
|
*
|
|
* @exception {DeveloperError} indices length must be a multiple of three.
|
|
* @exception {DeveloperError} cacheSize must be greater than two.
|
|
*
|
|
* @example
|
|
* var indices = [0, 1, 2, 3, 4, 5];
|
|
* var maxIndex = 5;
|
|
* var cacheSize = 3;
|
|
* var acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
|
|
*/
|
|
Tipsify.calculateACMR = function (options) {
|
|
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
|
|
var indices = options.indices;
|
|
var maximumIndex = options.maximumIndex;
|
|
var cacheSize = defaultValue(options.cacheSize, 24);
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(indices)) {
|
|
throw new DeveloperError("indices is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var numIndices = indices.length;
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (numIndices < 3 || numIndices % 3 !== 0) {
|
|
throw new DeveloperError("indices length must be a multiple of three.");
|
|
}
|
|
if (maximumIndex <= 0) {
|
|
throw new DeveloperError("maximumIndex must be greater than zero.");
|
|
}
|
|
if (cacheSize < 3) {
|
|
throw new DeveloperError("cacheSize must be greater than two.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
// Compute the maximumIndex if not given
|
|
if (!defined(maximumIndex)) {
|
|
maximumIndex = 0;
|
|
var currentIndex = 0;
|
|
var intoIndices = indices[currentIndex];
|
|
while (currentIndex < numIndices) {
|
|
if (intoIndices > maximumIndex) {
|
|
maximumIndex = intoIndices;
|
|
}
|
|
++currentIndex;
|
|
intoIndices = indices[currentIndex];
|
|
}
|
|
}
|
|
|
|
// Vertex time stamps
|
|
var vertexTimeStamps = [];
|
|
for (var i = 0; i < maximumIndex + 1; i++) {
|
|
vertexTimeStamps[i] = 0;
|
|
}
|
|
|
|
// Cache processing
|
|
var s = cacheSize + 1;
|
|
for (var j = 0; j < numIndices; ++j) {
|
|
if (s - vertexTimeStamps[indices[j]] > cacheSize) {
|
|
vertexTimeStamps[indices[j]] = s;
|
|
++s;
|
|
}
|
|
}
|
|
|
|
return (s - cacheSize + 1) / (numIndices / 3);
|
|
};
|
|
|
|
/**
|
|
* Optimizes triangles for the post-vertex shader cache.
|
|
*
|
|
* @param {Object} options Object with the following properties:
|
|
* @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
|
|
* in the vertex buffer that define the geometry's triangles.
|
|
* @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
|
|
* If not supplied, this value will be computed.
|
|
* @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
|
|
* @returns {Number[]} A list of the input indices in an optimized order.
|
|
*
|
|
* @exception {DeveloperError} indices length must be a multiple of three.
|
|
* @exception {DeveloperError} cacheSize must be greater than two.
|
|
*
|
|
* @example
|
|
* var indices = [0, 1, 2, 3, 4, 5];
|
|
* var maxIndex = 5;
|
|
* var cacheSize = 3;
|
|
* var reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
|
|
*/
|
|
Tipsify.tipsify = function (options) {
|
|
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
|
|
var indices = options.indices;
|
|
var maximumIndex = options.maximumIndex;
|
|
var cacheSize = defaultValue(options.cacheSize, 24);
|
|
|
|
var cursor;
|
|
|
|
function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
|
|
while (deadEnd.length >= 1) {
|
|
// while the stack is not empty
|
|
var d = deadEnd[deadEnd.length - 1]; // top of the stack
|
|
deadEnd.splice(deadEnd.length - 1, 1); // pop the stack
|
|
|
|
if (vertices[d].numLiveTriangles > 0) {
|
|
return d;
|
|
}
|
|
}
|
|
|
|
while (cursor < maximumIndexPlusOne) {
|
|
if (vertices[cursor].numLiveTriangles > 0) {
|
|
++cursor;
|
|
return cursor - 1;
|
|
}
|
|
++cursor;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function getNextVertex(
|
|
indices,
|
|
cacheSize,
|
|
oneRing,
|
|
vertices,
|
|
s,
|
|
deadEnd,
|
|
maximumIndexPlusOne
|
|
) {
|
|
var n = -1;
|
|
var p;
|
|
var m = -1;
|
|
var itOneRing = 0;
|
|
while (itOneRing < oneRing.length) {
|
|
var index = oneRing[itOneRing];
|
|
if (vertices[index].numLiveTriangles) {
|
|
p = 0;
|
|
if (
|
|
s -
|
|
vertices[index].timeStamp +
|
|
2 * vertices[index].numLiveTriangles <=
|
|
cacheSize
|
|
) {
|
|
p = s - vertices[index].timeStamp;
|
|
}
|
|
if (p > m || m === -1) {
|
|
m = p;
|
|
n = index;
|
|
}
|
|
}
|
|
++itOneRing;
|
|
}
|
|
if (n === -1) {
|
|
return skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (!defined(indices)) {
|
|
throw new DeveloperError("indices is required.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
var numIndices = indices.length;
|
|
|
|
//>>includeStart('debug', pragmas.debug);
|
|
if (numIndices < 3 || numIndices % 3 !== 0) {
|
|
throw new DeveloperError("indices length must be a multiple of three.");
|
|
}
|
|
if (maximumIndex <= 0) {
|
|
throw new DeveloperError("maximumIndex must be greater than zero.");
|
|
}
|
|
if (cacheSize < 3) {
|
|
throw new DeveloperError("cacheSize must be greater than two.");
|
|
}
|
|
//>>includeEnd('debug');
|
|
|
|
// Determine maximum index
|
|
var maximumIndexPlusOne = 0;
|
|
var currentIndex = 0;
|
|
var intoIndices = indices[currentIndex];
|
|
var endIndex = numIndices;
|
|
if (defined(maximumIndex)) {
|
|
maximumIndexPlusOne = maximumIndex + 1;
|
|
} else {
|
|
while (currentIndex < endIndex) {
|
|
if (intoIndices > maximumIndexPlusOne) {
|
|
maximumIndexPlusOne = intoIndices;
|
|
}
|
|
++currentIndex;
|
|
intoIndices = indices[currentIndex];
|
|
}
|
|
if (maximumIndexPlusOne === -1) {
|
|
return 0;
|
|
}
|
|
++maximumIndexPlusOne;
|
|
}
|
|
|
|
// Vertices
|
|
var vertices = [];
|
|
var i;
|
|
for (i = 0; i < maximumIndexPlusOne; i++) {
|
|
vertices[i] = {
|
|
numLiveTriangles: 0,
|
|
timeStamp: 0,
|
|
vertexTriangles: [],
|
|
};
|
|
}
|
|
currentIndex = 0;
|
|
var triangle = 0;
|
|
while (currentIndex < endIndex) {
|
|
vertices[indices[currentIndex]].vertexTriangles.push(triangle);
|
|
++vertices[indices[currentIndex]].numLiveTriangles;
|
|
vertices[indices[currentIndex + 1]].vertexTriangles.push(triangle);
|
|
++vertices[indices[currentIndex + 1]].numLiveTriangles;
|
|
vertices[indices[currentIndex + 2]].vertexTriangles.push(triangle);
|
|
++vertices[indices[currentIndex + 2]].numLiveTriangles;
|
|
++triangle;
|
|
currentIndex += 3;
|
|
}
|
|
|
|
// Starting index
|
|
var f = 0;
|
|
|
|
// Time Stamp
|
|
var s = cacheSize + 1;
|
|
cursor = 1;
|
|
|
|
// Process
|
|
var oneRing = [];
|
|
var deadEnd = []; //Stack
|
|
var vertex;
|
|
var intoVertices;
|
|
var currentOutputIndex = 0;
|
|
var outputIndices = [];
|
|
var numTriangles = numIndices / 3;
|
|
var triangleEmitted = [];
|
|
for (i = 0; i < numTriangles; i++) {
|
|
triangleEmitted[i] = false;
|
|
}
|
|
var index;
|
|
var limit;
|
|
while (f !== -1) {
|
|
oneRing = [];
|
|
intoVertices = vertices[f];
|
|
limit = intoVertices.vertexTriangles.length;
|
|
for (var k = 0; k < limit; ++k) {
|
|
triangle = intoVertices.vertexTriangles[k];
|
|
if (!triangleEmitted[triangle]) {
|
|
triangleEmitted[triangle] = true;
|
|
currentIndex = triangle + triangle + triangle;
|
|
for (var j = 0; j < 3; ++j) {
|
|
// Set this index as a possible next index
|
|
index = indices[currentIndex];
|
|
oneRing.push(index);
|
|
deadEnd.push(index);
|
|
|
|
// Output index
|
|
outputIndices[currentOutputIndex] = index;
|
|
++currentOutputIndex;
|
|
|
|
// Cache processing
|
|
vertex = vertices[index];
|
|
--vertex.numLiveTriangles;
|
|
if (s - vertex.timeStamp > cacheSize) {
|
|
vertex.timeStamp = s;
|
|
++s;
|
|
}
|
|
++currentIndex;
|
|
}
|
|
}
|
|
}
|
|
f = getNextVertex(
|
|
indices,
|
|
cacheSize,
|
|
oneRing,
|
|
vertices,
|
|
s,
|
|
deadEnd,
|
|
maximumIndexPlusOne
|
|
);
|
|
}
|
|
|
|
return outputIndices;
|
|
};
|
|
export default Tipsify;
|