Tipsify.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. * @namespace 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. const 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. * const indices = [0, 1, 2, 3, 4, 5];
  35. * const maxIndex = 5;
  36. * const cacheSize = 3;
  37. * const acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
  38. */
  39. Tipsify.calculateACMR = function (options) {
  40. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  41. const indices = options.indices;
  42. let maximumIndex = options.maximumIndex;
  43. const 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. const 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. let currentIndex = 0;
  65. let 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. const vertexTimeStamps = [];
  76. for (let i = 0; i < maximumIndex + 1; i++) {
  77. vertexTimeStamps[i] = 0;
  78. }
  79. // Cache processing
  80. let s = cacheSize + 1;
  81. for (let 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. * const indices = [0, 1, 2, 3, 4, 5];
  105. * const maxIndex = 5;
  106. * const cacheSize = 3;
  107. * const reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
  108. */
  109. Tipsify.tipsify = function (options) {
  110. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  111. const indices = options.indices;
  112. const maximumIndex = options.maximumIndex;
  113. const cacheSize = defaultValue(options.cacheSize, 24);
  114. let cursor;
  115. function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
  116. while (deadEnd.length >= 1) {
  117. // while the stack is not empty
  118. const 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(
  134. indices,
  135. cacheSize,
  136. oneRing,
  137. vertices,
  138. s,
  139. deadEnd,
  140. maximumIndexPlusOne
  141. ) {
  142. let n = -1;
  143. let p;
  144. let m = -1;
  145. let itOneRing = 0;
  146. while (itOneRing < oneRing.length) {
  147. const index = oneRing[itOneRing];
  148. if (vertices[index].numLiveTriangles) {
  149. p = 0;
  150. if (
  151. s -
  152. vertices[index].timeStamp +
  153. 2 * vertices[index].numLiveTriangles <=
  154. cacheSize
  155. ) {
  156. p = s - vertices[index].timeStamp;
  157. }
  158. if (p > m || m === -1) {
  159. m = p;
  160. n = index;
  161. }
  162. }
  163. ++itOneRing;
  164. }
  165. if (n === -1) {
  166. return skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne);
  167. }
  168. return n;
  169. }
  170. //>>includeStart('debug', pragmas.debug);
  171. if (!defined(indices)) {
  172. throw new DeveloperError("indices is required.");
  173. }
  174. //>>includeEnd('debug');
  175. const numIndices = indices.length;
  176. //>>includeStart('debug', pragmas.debug);
  177. if (numIndices < 3 || numIndices % 3 !== 0) {
  178. throw new DeveloperError("indices length must be a multiple of three.");
  179. }
  180. if (maximumIndex <= 0) {
  181. throw new DeveloperError("maximumIndex must be greater than zero.");
  182. }
  183. if (cacheSize < 3) {
  184. throw new DeveloperError("cacheSize must be greater than two.");
  185. }
  186. //>>includeEnd('debug');
  187. // Determine maximum index
  188. let maximumIndexPlusOne = 0;
  189. let currentIndex = 0;
  190. let intoIndices = indices[currentIndex];
  191. const endIndex = numIndices;
  192. if (defined(maximumIndex)) {
  193. maximumIndexPlusOne = maximumIndex + 1;
  194. } else {
  195. while (currentIndex < endIndex) {
  196. if (intoIndices > maximumIndexPlusOne) {
  197. maximumIndexPlusOne = intoIndices;
  198. }
  199. ++currentIndex;
  200. intoIndices = indices[currentIndex];
  201. }
  202. if (maximumIndexPlusOne === -1) {
  203. return 0;
  204. }
  205. ++maximumIndexPlusOne;
  206. }
  207. // Vertices
  208. const vertices = [];
  209. let i;
  210. for (i = 0; i < maximumIndexPlusOne; i++) {
  211. vertices[i] = {
  212. numLiveTriangles: 0,
  213. timeStamp: 0,
  214. vertexTriangles: [],
  215. };
  216. }
  217. currentIndex = 0;
  218. let triangle = 0;
  219. while (currentIndex < endIndex) {
  220. vertices[indices[currentIndex]].vertexTriangles.push(triangle);
  221. ++vertices[indices[currentIndex]].numLiveTriangles;
  222. vertices[indices[currentIndex + 1]].vertexTriangles.push(triangle);
  223. ++vertices[indices[currentIndex + 1]].numLiveTriangles;
  224. vertices[indices[currentIndex + 2]].vertexTriangles.push(triangle);
  225. ++vertices[indices[currentIndex + 2]].numLiveTriangles;
  226. ++triangle;
  227. currentIndex += 3;
  228. }
  229. // Starting index
  230. let f = 0;
  231. // Time Stamp
  232. let s = cacheSize + 1;
  233. cursor = 1;
  234. // Process
  235. let oneRing = [];
  236. const deadEnd = []; //Stack
  237. let vertex;
  238. let intoVertices;
  239. let currentOutputIndex = 0;
  240. const outputIndices = [];
  241. const numTriangles = numIndices / 3;
  242. const triangleEmitted = [];
  243. for (i = 0; i < numTriangles; i++) {
  244. triangleEmitted[i] = false;
  245. }
  246. let index;
  247. let limit;
  248. while (f !== -1) {
  249. oneRing = [];
  250. intoVertices = vertices[f];
  251. limit = intoVertices.vertexTriangles.length;
  252. for (let k = 0; k < limit; ++k) {
  253. triangle = intoVertices.vertexTriangles[k];
  254. if (!triangleEmitted[triangle]) {
  255. triangleEmitted[triangle] = true;
  256. currentIndex = triangle + triangle + triangle;
  257. for (let j = 0; j < 3; ++j) {
  258. // Set this index as a possible next index
  259. index = indices[currentIndex];
  260. oneRing.push(index);
  261. deadEnd.push(index);
  262. // Output index
  263. outputIndices[currentOutputIndex] = index;
  264. ++currentOutputIndex;
  265. // Cache processing
  266. vertex = vertices[index];
  267. --vertex.numLiveTriangles;
  268. if (s - vertex.timeStamp > cacheSize) {
  269. vertex.timeStamp = s;
  270. ++s;
  271. }
  272. ++currentIndex;
  273. }
  274. }
  275. }
  276. f = getNextVertex(
  277. indices,
  278. cacheSize,
  279. oneRing,
  280. vertices,
  281. s,
  282. deadEnd,
  283. maximumIndexPlusOne
  284. );
  285. }
  286. return outputIndices;
  287. };
  288. export default Tipsify;