Benchmark.hx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. package h3d.impl;
  2. private class QueryObject {
  3. var driver : h3d.impl.Driver;
  4. public var q : h3d.impl.Driver.Query;
  5. public var value : Float;
  6. public var name : String;
  7. public var drawCalls : Int;
  8. public var next : QueryObject;
  9. public function new() {
  10. driver = h3d.Engine.getCurrent().driver;
  11. q = driver.allocQuery(TimeStamp);
  12. }
  13. public function sync() {
  14. value = driver.queryResult(q);
  15. }
  16. public function isAvailable() {
  17. return driver.queryResultAvailable(q);
  18. }
  19. public function dispose() {
  20. driver.deleteQuery(q);
  21. q = null;
  22. }
  23. }
  24. private class StatsObject {
  25. public var name : String;
  26. public var time : Float;
  27. public var drawCalls : Int;
  28. public var next : StatsObject;
  29. public var xPos : Int;
  30. public var xSize : Int;
  31. public function new() {
  32. }
  33. }
  34. class Benchmark extends h2d.Graphics {
  35. var cachedStats : StatsObject;
  36. var currentStats : StatsObject;
  37. var cachedQueries : QueryObject;
  38. var currentFrame : QueryObject;
  39. var waitFrames : Array<QueryObject>;
  40. var engine : h3d.Engine;
  41. var stats : StatsObject;
  42. var labels : Array<h2d.Text>;
  43. var interact : h2d.Interactive;
  44. public var estimateWait = true;
  45. public var enable(default,set) : Bool;
  46. public var width : Null<Int>;
  47. public var height = 16;
  48. public var textColor = 0;
  49. public var colors = new Array<Int>();
  50. public var font : h2d.Font;
  51. public var recalTime = 1e9;
  52. public var smoothTime = 0.95;
  53. public var measureCpu = true;
  54. var tip : h2d.Text;
  55. var tipCurrent : StatsObject;
  56. var tipCurName : String;
  57. var curWidth : Int;
  58. var prevFrame : Float;
  59. var frameTime : Float;
  60. public function new(?parent) {
  61. super(parent);
  62. waitFrames = [];
  63. labels = [];
  64. engine = h3d.Engine.getCurrent();
  65. interact = new h2d.Interactive(0,0,this);
  66. interact.onMove = onMove;
  67. interact.cursor = Default;
  68. interact.onOut = function(_) {
  69. if( tip == null ) return;
  70. tip.remove();
  71. tip = null;
  72. tipCurrent = null;
  73. }
  74. enable = engine.driver.hasFeature(Queries);
  75. }
  76. function set_enable(e) {
  77. if( !e )
  78. cleanup();
  79. return enable = e;
  80. }
  81. function cleanup() {
  82. while( waitFrames.length > 0 ) {
  83. var w = waitFrames.pop();
  84. while( w != null ) {
  85. w.dispose();
  86. w = w.next;
  87. }
  88. }
  89. while( cachedQueries != null ) {
  90. cachedQueries.dispose();
  91. cachedQueries = cachedQueries.next;
  92. }
  93. while( currentFrame != null ) {
  94. currentFrame.dispose();
  95. currentFrame = currentFrame.next;
  96. }
  97. }
  98. override function clear() {
  99. super.clear();
  100. if( labels != null ) {
  101. for( t in labels ) t.remove();
  102. labels = [];
  103. }
  104. if( interact != null ) interact.width = interact.height = 0;
  105. }
  106. override function onRemove() {
  107. super.onRemove();
  108. cleanup();
  109. }
  110. function onMove(e:hxd.Event) {
  111. var s = currentStats;
  112. while( s != null ) {
  113. if( e.relX >= s.xPos && e.relX <= s.xPos + s.xSize )
  114. break;
  115. s = s.next;
  116. }
  117. if( tip != null ) {
  118. tip.remove();
  119. tip = null;
  120. tipCurrent = null;
  121. }
  122. if( s == null )
  123. return;
  124. tip = new h2d.Text(font, this);
  125. tip.dropShadow = { dx : 0, dy : 1, color : 0, alpha : 1 };
  126. tip.y = -20;
  127. tipCurrent = s;
  128. tipCurName = s.name;
  129. syncTip(s);
  130. }
  131. function syncTip(s:StatsObject) {
  132. tip.text = s.name+"( " + Std.int(s.time / 1e6) + "." + StringTools.lpad(""+(Std.int(s.time/1e4)%100),"0",2) + " ms " + s.drawCalls + " draws )";
  133. var tw = tip.textWidth;
  134. var tx = s.xPos + ((s.xSize - tw) >> 1);
  135. if( tx + tw > curWidth ) tx = curWidth - tw;
  136. if( tx < 0 ) tx = 0;
  137. if( hxd.Math.abs(tip.x - tx) > 5 ) tip.x = tx;
  138. }
  139. public function begin() {
  140. if( !enable ) return;
  141. var t0 = haxe.Timer.stamp();
  142. var ft = (t0 - prevFrame) * 1e9;
  143. if( hxd.Math.abs(ft - frameTime) > recalTime )
  144. frameTime = ft;
  145. else
  146. frameTime = frameTime * smoothTime + ft * (1 - smoothTime);
  147. prevFrame = t0;
  148. // end was not called...
  149. if( currentFrame != null )
  150. end();
  151. // sync with available frame
  152. var changed = false;
  153. while( waitFrames.length > 0 ) {
  154. var q = waitFrames[0];
  155. if( !q.isAvailable() )
  156. break;
  157. waitFrames.shift();
  158. // recycle previous stats
  159. var st = currentStats;
  160. while( st != null ) {
  161. var n = st.next;
  162. st.next = cachedStats;
  163. cachedStats = st;
  164. st = n;
  165. }
  166. currentStats = null;
  167. var prev : QueryObject = null;
  168. var totalTime = 0.;
  169. while( q != null ) {
  170. if( !measureCpu ) q.sync();
  171. if( prev != null ) {
  172. var dt = prev.value - q.value;
  173. var s = allocStat(q.name, dt);
  174. totalTime += dt;
  175. s.drawCalls = prev.drawCalls - q.drawCalls;
  176. }
  177. // recycle
  178. var n = q.next;
  179. q.next = cachedQueries;
  180. cachedQueries = q;
  181. prev = q;
  182. q = n;
  183. }
  184. if( estimateWait ) {
  185. var waitT = frameTime - totalTime;
  186. if( waitT > 0 ) {
  187. if( hxd.Stage.getInstance().vsync ) {
  188. var vst = 1e9 / hxd.System.getDefaultFrameRate() - totalTime;
  189. if( vst > waitT ) vst = waitT;
  190. if( vst > 0 ) {
  191. var s = allocStat("vsync", vst);
  192. s.drawCalls = 0;
  193. waitT -= vst;
  194. }
  195. }
  196. if( waitT > 0.5e6 /* 0.5 ms */ ) {
  197. var s = allocStat(measureCpu ? "gpuwait" : "cpuwait", waitT);
  198. s.drawCalls = 0;
  199. }
  200. }
  201. }
  202. // stats updated
  203. changed = true;
  204. }
  205. if( allocated && visible )
  206. syncVisual();
  207. measure("begin");
  208. }
  209. function syncVisual() {
  210. var s2d = getScene();
  211. var old = labels;
  212. labels = null;
  213. clear();
  214. labels = old;
  215. var width = width == null ? s2d.width : width;
  216. curWidth = width;
  217. beginFill(0, 0.5);
  218. drawRect(0, 0, width, height);
  219. interact.width = width;
  220. interact.height = height;
  221. var totalTime = 0.;
  222. var s = currentStats;
  223. while( s != null ) {
  224. totalTime += s.time;
  225. s = s.next;
  226. }
  227. var space = 40;
  228. width -= space;
  229. var count = 0;
  230. var xPos = 0;
  231. var curTime = 0.;
  232. var s = currentStats;
  233. while( s != null ) {
  234. if( colors.length < count ) {
  235. var color = new h3d.Vector();
  236. var m = new h3d.Matrix();
  237. m.identity();
  238. m.colorHue(count);
  239. color.setColor(0x3399FF);
  240. color.transform(m);
  241. colors.push(color.toColor());
  242. }
  243. curTime += s.time;
  244. var xEnd = Math.ceil(width * (curTime / totalTime));
  245. var xSize = xEnd - xPos;
  246. beginFill(colors[count]);
  247. drawRect(xPos, 0, xSize, height);
  248. var l = allocLabel(count);
  249. if( xSize < s.name.length * 6 )
  250. l.visible = false;
  251. else {
  252. l.visible = true;
  253. l.textColor = textColor;
  254. l.text = s.name;
  255. l.x = xPos + ((xSize - l.textWidth) >> 1);
  256. }
  257. s.xPos = xPos;
  258. s.xSize = xSize;
  259. if( tipCurrent == s && tipCurName == s.name )
  260. syncTip(s);
  261. xPos = xEnd;
  262. count++;
  263. s = s.next;
  264. }
  265. var time = allocLabel(count++);
  266. time.x = xPos + 10;
  267. time.visible = true;
  268. time.textColor = 0xFFFFFF;
  269. var timeMs = totalTime / 1e6;
  270. time.text = Std.int(timeMs) + "." + Std.int((timeMs * 10) % 10);
  271. while( labels.length > count )
  272. labels.pop().remove();
  273. }
  274. function allocLabel(index) {
  275. var l = labels[index];
  276. if( l != null )
  277. return l;
  278. if( font == null ) font = hxd.res.DefaultFont.get();
  279. l = new h2d.Text(font, this);
  280. labels[index] = l;
  281. return l;
  282. }
  283. public function end() {
  284. if( !enable ) return;
  285. measure("end");
  286. waitFrames.push(currentFrame);
  287. currentFrame = null;
  288. }
  289. function allocStat( name, time : Float ) {
  290. var s = cachedStats;
  291. if( s != null )
  292. cachedStats = s.next;
  293. else
  294. s = new StatsObject();
  295. if( name == s.name ) {
  296. // smooth
  297. var et = hxd.Math.abs(time - s.time);
  298. if( et > recalTime )
  299. s.time = time;
  300. else
  301. s.time = s.time * smoothTime + time * (1 - smoothTime);
  302. } else {
  303. s.name = name;
  304. s.time = time;
  305. }
  306. s.next = currentStats;
  307. currentStats = s;
  308. return s;
  309. }
  310. function allocQuery() {
  311. var q = cachedQueries;
  312. if( q != null )
  313. cachedQueries = q.next;
  314. else
  315. q = new QueryObject();
  316. return q;
  317. }
  318. public function measure( name : String ) {
  319. if( !enable ) return;
  320. if( currentFrame != null && currentFrame.name == name )
  321. return;
  322. var q = allocQuery();
  323. q.name = name;
  324. q.drawCalls = engine.drawCalls;
  325. q.next = currentFrame;
  326. currentFrame = q;
  327. engine.driver.endQuery(q.q);
  328. if( measureCpu ) q.value = haxe.Timer.stamp() * 1e9;
  329. }
  330. }