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
* Fast Triangle Reordering for Vertex Locality and Reduced Overdraw
* by Sander, Nehab, and Barczak
*
* @private
*/
const 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 args.indices
.
* 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
* const indices = [0, 1, 2, 3, 4, 5];
* const maxIndex = 5;
* const cacheSize = 3;
* const acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
*/
Tipsify.calculateACMR = function (options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
const indices = options.indices;
let maximumIndex = options.maximumIndex;
const cacheSize = defaultValue(options.cacheSize, 24);
//>>includeStart('debug', pragmas.debug);
if (!defined(indices)) {
throw new DeveloperError("indices is required.");
}
//>>includeEnd('debug');
const 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;
let currentIndex = 0;
let intoIndices = indices[currentIndex];
while (currentIndex < numIndices) {
if (intoIndices > maximumIndex) {
maximumIndex = intoIndices;
}
++currentIndex;
intoIndices = indices[currentIndex];
}
}
// Vertex time stamps
const vertexTimeStamps = [];
for (let i = 0; i < maximumIndex + 1; i++) {
vertexTimeStamps[i] = 0;
}
// Cache processing
let s = cacheSize + 1;
for (let 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 args.indices
.
* 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
* const indices = [0, 1, 2, 3, 4, 5];
* const maxIndex = 5;
* const cacheSize = 3;
* const reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
*/
Tipsify.tipsify = function (options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
const indices = options.indices;
const maximumIndex = options.maximumIndex;
const cacheSize = defaultValue(options.cacheSize, 24);
let cursor;
function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
while (deadEnd.length >= 1) {
// while the stack is not empty
const 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
) {
let n = -1;
let p;
let m = -1;
let itOneRing = 0;
while (itOneRing < oneRing.length) {
const 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');
const 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
let maximumIndexPlusOne = 0;
let currentIndex = 0;
let intoIndices = indices[currentIndex];
const 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
const vertices = [];
let i;
for (i = 0; i < maximumIndexPlusOne; i++) {
vertices[i] = {
numLiveTriangles: 0,
timeStamp: 0,
vertexTriangles: [],
};
}
currentIndex = 0;
let 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
let f = 0;
// Time Stamp
let s = cacheSize + 1;
cursor = 1;
// Process
let oneRing = [];
const deadEnd = []; //Stack
let vertex;
let intoVertices;
let currentOutputIndex = 0;
const outputIndices = [];
const numTriangles = numIndices / 3;
const triangleEmitted = [];
for (i = 0; i < numTriangles; i++) {
triangleEmitted[i] = false;
}
let index;
let limit;
while (f !== -1) {
oneRing = [];
intoVertices = vertices[f];
limit = intoVertices.vertexTriangles.length;
for (let k = 0; k < limit; ++k) {
triangle = intoVertices.vertexTriangles[k];
if (!triangleEmitted[triangle]) {
triangleEmitted[triangle] = true;
currentIndex = triangle + triangle + triangle;
for (let 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;