Tipsify.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import defaultValue from './defaultValue.js';
  2. import defined from './defined.js';
  3. import DeveloperError from './DeveloperError.js';
  4. /**
  5. * Encapsulates an algorithm to optimize triangles for the post
  6. * vertex-shader cache. This is based on the 2007 SIGGRAPH paper
  7. * 'Fast Triangle Reordering for Vertex Locality and Reduced Overdraw.'
  8. * The runtime is linear but several passes are made.
  9. *
  10. * @exports Tipsify
  11. *
  12. * @see <a href='http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf'>
  13. * Fast Triangle Reordering for Vertex Locality and Reduced Overdraw</a>
  14. * by Sander, Nehab, and Barczak
  15. *
  16. * @private
  17. */
  18. var Tipsify = {};
  19. /**
  20. * Calculates the average cache miss ratio (ACMR) for a given set of indices.
  21. *
  22. * @param {Object} options Object with the following properties:
  23. * @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
  24. * in the vertex buffer that define the geometry's triangles.
  25. * @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
  26. * If not supplied, this value will be computed.
  27. * @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
  28. * @returns {Number} The average cache miss ratio (ACMR).
  29. *
  30. * @exception {DeveloperError} indices length must be a multiple of three.
  31. * @exception {DeveloperError} cacheSize must be greater than two.
  32. *
  33. * @example
  34. * var indices = [0, 1, 2, 3, 4, 5];
  35. * var maxIndex = 5;
  36. * var cacheSize = 3;
  37. * var acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
  38. */
  39. Tipsify.calculateACMR = function(options) {
  40. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  41. var indices = options.indices;
  42. var maximumIndex = options.maximumIndex;
  43. var cacheSize = defaultValue(options.cacheSize, 24);
  44. //>>includeStart('debug', pragmas.debug);
  45. if (!defined(indices)) {
  46. throw new DeveloperError('indices is required.');
  47. }
  48. //>>includeEnd('debug');
  49. var numIndices = indices.length;
  50. //>>includeStart('debug', pragmas.debug);
  51. if (numIndices < 3 || numIndices % 3 !== 0) {
  52. throw new DeveloperError('indices length must be a multiple of three.');
  53. }
  54. if (maximumIndex <= 0) {
  55. throw new DeveloperError('maximumIndex must be greater than zero.');
  56. }
  57. if (cacheSize < 3) {
  58. throw new DeveloperError('cacheSize must be greater than two.');
  59. }
  60. //>>includeEnd('debug');
  61. // Compute the maximumIndex if not given
  62. if (!defined(maximumIndex)) {
  63. maximumIndex = 0;
  64. var currentIndex = 0;
  65. var intoIndices = indices[currentIndex];
  66. while (currentIndex < numIndices) {
  67. if (intoIndices > maximumIndex) {
  68. maximumIndex = intoIndices;
  69. }
  70. ++currentIndex;
  71. intoIndices = indices[currentIndex];
  72. }
  73. }
  74. // Vertex time stamps
  75. var vertexTimeStamps = [];
  76. for ( var i = 0; i < maximumIndex + 1; i++) {
  77. vertexTimeStamps[i] = 0;
  78. }
  79. // Cache processing
  80. var s = cacheSize + 1;
  81. for ( var j = 0; j < numIndices; ++j) {
  82. if ((s - vertexTimeStamps[indices[j]]) > cacheSize) {
  83. vertexTimeStamps[indices[j]] = s;
  84. ++s;
  85. }
  86. }
  87. return (s - cacheSize + 1) / (numIndices / 3);
  88. };
  89. /**
  90. * Optimizes triangles for the post-vertex shader cache.
  91. *
  92. * @param {Object} options Object with the following properties:
  93. * @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
  94. * in the vertex buffer that define the geometry's triangles.
  95. * @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
  96. * If not supplied, this value will be computed.
  97. * @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
  98. * @returns {Number[]} A list of the input indices in an optimized order.
  99. *
  100. * @exception {DeveloperError} indices length must be a multiple of three.
  101. * @exception {DeveloperError} cacheSize must be greater than two.
  102. *
  103. * @example
  104. * var indices = [0, 1, 2, 3, 4, 5];
  105. * var maxIndex = 5;
  106. * var cacheSize = 3;
  107. * var reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
  108. */
  109. Tipsify.tipsify = function(options) {
  110. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  111. var indices = options.indices;
  112. var maximumIndex = options.maximumIndex;
  113. var cacheSize = defaultValue(options.cacheSize, 24);
  114. var cursor;
  115. function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
  116. while (deadEnd.length >= 1) {
  117. // while the stack is not empty
  118. var d = deadEnd[deadEnd.length - 1]; // top of the stack
  119. deadEnd.splice(deadEnd.length - 1, 1); // pop the stack
  120. if (vertices[d].numLiveTriangles > 0) {
  121. return d;
  122. }
  123. }
  124. while (cursor < maximumIndexPlusOne) {
  125. if (vertices[cursor].numLiveTriangles > 0) {
  126. ++cursor;
  127. return cursor - 1;
  128. }
  129. ++cursor;
  130. }
  131. return -1;
  132. }
  133. function getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne) {
  134. var n = -1;
  135. var p;
  136. var m = -1;
  137. var itOneRing = 0;
  138. while (itOneRing < oneRing.length) {
  139. var index = oneRing[itOneRing];
  140. if (vertices[index].numLiveTriangles) {
  141. p = 0;
  142. if ((s - vertices[index].timeStamp + (2 * vertices[index].numLiveTriangles)) <= cacheSize) {
  143. p = s - vertices[index].timeStamp;
  144. }
  145. if ((p > m) || (m === -1)) {
  146. m = p;
  147. n = index;
  148. }
  149. }
  150. ++itOneRing;
  151. }
  152. if (n === -1) {
  153. return skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne);
  154. }
  155. return n;
  156. }
  157. //>>includeStart('debug', pragmas.debug);
  158. if (!defined(indices)) {
  159. throw new DeveloperError('indices is required.');
  160. }
  161. //>>includeEnd('debug');
  162. var numIndices = indices.length;
  163. //>>includeStart('debug', pragmas.debug);
  164. if (numIndices < 3 || numIndices % 3 !== 0) {
  165. throw new DeveloperError('indices length must be a multiple of three.');
  166. }
  167. if (maximumIndex <= 0) {
  168. throw new DeveloperError('maximumIndex must be greater than zero.');
  169. }
  170. if (cacheSize < 3) {
  171. throw new DeveloperError('cacheSize must be greater than two.');
  172. }
  173. //>>includeEnd('debug');
  174. // Determine maximum index
  175. var maximumIndexPlusOne = 0;
  176. var currentIndex = 0;
  177. var intoIndices = indices[currentIndex];
  178. var endIndex = numIndices;
  179. if (defined(maximumIndex)) {
  180. maximumIndexPlusOne = maximumIndex + 1;
  181. } else {
  182. while (currentIndex < endIndex) {
  183. if (intoIndices > maximumIndexPlusOne) {
  184. maximumIndexPlusOne = intoIndices;
  185. }
  186. ++currentIndex;
  187. intoIndices = indices[currentIndex];
  188. }
  189. if (maximumIndexPlusOne === -1) {
  190. return 0;
  191. }
  192. ++maximumIndexPlusOne;
  193. }
  194. // Vertices
  195. var vertices = [];
  196. var i;
  197. for (i = 0; i < maximumIndexPlusOne; i++) {
  198. vertices[i] = {
  199. numLiveTriangles : 0,
  200. timeStamp : 0,
  201. vertexTriangles : []
  202. };
  203. }
  204. currentIndex = 0;
  205. var triangle = 0;
  206. while (currentIndex < endIndex) {
  207. vertices[indices[currentIndex]].vertexTriangles.push(triangle);
  208. ++(vertices[indices[currentIndex]]).numLiveTriangles;
  209. vertices[indices[currentIndex + 1]].vertexTriangles.push(triangle);
  210. ++(vertices[indices[currentIndex + 1]]).numLiveTriangles;
  211. vertices[indices[currentIndex + 2]].vertexTriangles.push(triangle);
  212. ++(vertices[indices[currentIndex + 2]]).numLiveTriangles;
  213. ++triangle;
  214. currentIndex += 3;
  215. }
  216. // Starting index
  217. var f = 0;
  218. // Time Stamp
  219. var s = cacheSize + 1;
  220. cursor = 1;
  221. // Process
  222. var oneRing = [];
  223. var deadEnd = []; //Stack
  224. var vertex;
  225. var intoVertices;
  226. var currentOutputIndex = 0;
  227. var outputIndices = [];
  228. var numTriangles = numIndices / 3;
  229. var triangleEmitted = [];
  230. for (i = 0; i < numTriangles; i++) {
  231. triangleEmitted[i] = false;
  232. }
  233. var index;
  234. var limit;
  235. while (f !== -1) {
  236. oneRing = [];
  237. intoVertices = vertices[f];
  238. limit = intoVertices.vertexTriangles.length;
  239. for ( var k = 0; k < limit; ++k) {
  240. triangle = intoVertices.vertexTriangles[k];
  241. if (!triangleEmitted[triangle]) {
  242. triangleEmitted[triangle] = true;
  243. currentIndex = triangle + triangle + triangle;
  244. for ( var j = 0; j < 3; ++j) {
  245. // Set this index as a possible next index
  246. index = indices[currentIndex];
  247. oneRing.push(index);
  248. deadEnd.push(index);
  249. // Output index
  250. outputIndices[currentOutputIndex] = index;
  251. ++currentOutputIndex;
  252. // Cache processing
  253. vertex = vertices[index];
  254. --vertex.numLiveTriangles;
  255. if ((s - vertex.timeStamp) > cacheSize) {
  256. vertex.timeStamp = s;
  257. ++s;
  258. }
  259. ++currentIndex;
  260. }
  261. }
  262. }
  263. f = getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne);
  264. }
  265. return outputIndices;
  266. };
  267. export default Tipsify;