Benchmark.hx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  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 = false;
  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 = false;
  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.parent.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. var fl = new h2d.Flow(this);
  119. fl.y = -23;
  120. fl.backgroundTile = h2d.Tile.fromColor(0,1,1,0.8);
  121. fl.padding = 5;
  122. tip = new h2d.Text(font, fl);
  123. tip.dropShadow = { dx : 0, dy : 1, color : 0, alpha : 1 };
  124. }
  125. tipCurrent = s;
  126. tipCurName = s == null ? null : s.name;
  127. syncTip(s);
  128. }
  129. function syncTip(s:StatsObject) {
  130. if( s == null )
  131. tip.text = "total "+engine.drawCalls+" draws "+hxd.Math.fmt(engine.drawTriangles/1000000)+" Mtri";
  132. else
  133. tip.text = s.name+"( " + Std.int(s.time / 1e6) + "." + StringTools.lpad(""+(Std.int(s.time/1e4)%100),"0",2) + " ms " + s.drawCalls + " draws )";
  134. var tw = tip.textWidth + 10;
  135. var tx = s == null ? curWidth : s.xPos + ((s.xSize - tw) * .5);
  136. if( tx + tw > curWidth ) tx = curWidth - tw;
  137. if( tx < 0 ) tx = 0;
  138. if( hxd.Math.abs(tip.parent.x - tx) > 5 ) tip.parent.x = Std.int(tx);
  139. }
  140. public function begin() {
  141. if( !enable ) return;
  142. var t0 = haxe.Timer.stamp();
  143. var ft = (t0 - prevFrame) * 1e9;
  144. if( hxd.Math.abs(ft - frameTime) > recalTime )
  145. frameTime = ft;
  146. else
  147. frameTime = frameTime * smoothTime + ft * (1 - smoothTime);
  148. prevFrame = t0;
  149. // end was not called...
  150. if( currentFrame != null )
  151. end();
  152. // sync with available frame
  153. var changed = false;
  154. while( waitFrames.length > 0 ) {
  155. var q = waitFrames[0];
  156. if( !q.isAvailable() )
  157. break;
  158. waitFrames.shift();
  159. // recycle previous stats
  160. var st = currentStats;
  161. while( st != null ) {
  162. var n = st.next;
  163. st.next = cachedStats;
  164. cachedStats = st;
  165. st = n;
  166. }
  167. currentStats = null;
  168. var prev : QueryObject = null;
  169. var totalTime = 0.;
  170. while( q != null ) {
  171. if( !measureCpu ) q.sync();
  172. if( prev != null ) {
  173. var dt = prev.value - q.value;
  174. var s = allocStat(q.name, dt);
  175. totalTime += dt;
  176. s.drawCalls = prev.drawCalls - q.drawCalls;
  177. if( s.drawCalls < 0 ) s.drawCalls = 0;
  178. }
  179. // recycle
  180. var n = q.next;
  181. q.next = cachedQueries;
  182. cachedQueries = q;
  183. prev = q;
  184. q = n;
  185. }
  186. if( estimateWait ) {
  187. var waitT = frameTime - totalTime;
  188. if( waitT > 0 ) {
  189. if( hxd.Window.getInstance().vsync ) {
  190. var vst = 1e9 / hxd.System.getDefaultFrameRate() - totalTime;
  191. if( vst > waitT ) vst = waitT;
  192. if( vst > 0 ) {
  193. var s = allocStat("vsync", vst);
  194. s.drawCalls = 0;
  195. waitT -= vst;
  196. }
  197. }
  198. if( waitT > 0.5e6 /* 0.5 ms */ ) {
  199. var s = allocStat(measureCpu ? "gpuwait" : "cpuwait", waitT);
  200. s.drawCalls = 0;
  201. }
  202. }
  203. }
  204. // stats updated
  205. changed = true;
  206. }
  207. if( allocated && visible )
  208. syncVisual();
  209. measure("begin");
  210. }
  211. function syncVisual() {
  212. var s2d = getScene();
  213. var old = labels;
  214. labels = null;
  215. clear();
  216. labels = old;
  217. var width = width == null ? s2d.width : width;
  218. curWidth = width;
  219. beginFill(0, 0.5);
  220. drawRect(0, 0, width, height);
  221. interact.width = width;
  222. interact.height = height;
  223. var totalTime = 0.;
  224. var s = currentStats;
  225. while( s != null ) {
  226. totalTime += s.time;
  227. s = s.next;
  228. }
  229. var space = 52;
  230. width -= space;
  231. var count = 0;
  232. var xPos = 0;
  233. var curTime = 0.;
  234. var s = currentStats;
  235. while( s != null ) {
  236. if( colors.length <= count ) {
  237. var color = new h3d.Vector();
  238. var m = new h3d.Matrix();
  239. m.identity();
  240. m.colorHue(count);
  241. color.setColor(0x3399FF);
  242. color.transform(m);
  243. colors.push(color.toColor());
  244. }
  245. curTime += s.time;
  246. var xEnd = Math.ceil(width * (curTime / totalTime));
  247. var xSize = xEnd - xPos;
  248. beginFill(colors[count]);
  249. drawRect(xPos, 0, xSize, height);
  250. var l = allocLabel(count);
  251. if( xSize < s.name.length * 6 )
  252. l.visible = false;
  253. else {
  254. l.visible = true;
  255. l.textColor = textColor;
  256. l.text = s.name;
  257. l.x = xPos + Std.int((xSize - l.textWidth) * .5);
  258. }
  259. s.xPos = xPos;
  260. s.xSize = xSize;
  261. if( tipCurrent == s && tipCurName == s.name )
  262. syncTip(s);
  263. xPos = xEnd;
  264. count++;
  265. s = s.next;
  266. }
  267. if( tip != null && tipCurrent == null )
  268. syncTip(null);
  269. var time = allocLabel(count++);
  270. time.x = xPos + 3;
  271. time.y = -1;
  272. time.visible = true;
  273. time.textColor = 0xFFFFFF;
  274. var timeMs = totalTime / 1e6;
  275. time.text = Std.int(timeMs) + "." + Std.int((timeMs * 10) % 10) + (measureCpu?" cpu" : " gpu");
  276. while( labels.length > count )
  277. labels.pop().remove();
  278. }
  279. function allocLabel(index) {
  280. var l = labels[index];
  281. if( l != null )
  282. return l;
  283. if( font == null ) font = hxd.res.DefaultFont.get();
  284. l = new h2d.Text(font, this);
  285. labels[index] = l;
  286. return l;
  287. }
  288. public function end() {
  289. if( !enable ) return;
  290. measure("end");
  291. waitFrames.push(currentFrame);
  292. currentFrame = null;
  293. }
  294. function allocStat( name, time : Float ) {
  295. var s = cachedStats;
  296. if( s != null )
  297. cachedStats = s.next;
  298. else
  299. s = new StatsObject();
  300. if( name == s.name ) {
  301. // smooth
  302. var et = hxd.Math.abs(time - s.time);
  303. if( et > recalTime )
  304. s.time = time;
  305. else
  306. s.time = s.time * smoothTime + time * (1 - smoothTime);
  307. } else {
  308. s.name = name;
  309. s.time = time;
  310. }
  311. s.next = currentStats;
  312. currentStats = s;
  313. return s;
  314. }
  315. function allocQuery() {
  316. var q = cachedQueries;
  317. if( q != null )
  318. cachedQueries = q.next;
  319. else
  320. q = new QueryObject();
  321. return q;
  322. }
  323. public function measure( name : String ) {
  324. if( !enable ) return;
  325. if( currentFrame != null && currentFrame.name == name )
  326. return;
  327. var q = allocQuery();
  328. q.name = name;
  329. q.drawCalls = engine.drawCalls;
  330. q.next = currentFrame;
  331. currentFrame = q;
  332. engine.driver.endQuery(q.q);
  333. if( measureCpu ) q.value = haxe.Timer.stamp() * 1e9;
  334. }
  335. public static function takeControl( app : hxd.App, ?s3d : h3d.scene.Scene ) @:privateAccess {
  336. if( s3d == null ) s3d = app.s3d;
  337. var cur = hxd.System.getCurrentLoop();
  338. var prevInt = s3d.interactives;
  339. var prevScenes = app.sevents.scenes;
  340. app.sevents.scenes = [s3d];
  341. s3d.interactives = [];
  342. var camCtrl = new h3d.scene.CameraController(s3d);
  343. var prevStates = new Map();
  344. var frustum = s3d.camera.frustum;
  345. function getRec( obj : h3d.scene.Object ) {
  346. if( obj.cullingCollider != null ) {
  347. prevStates.set(obj,{ cul : obj.cullingCollider, culled : obj.culled });
  348. obj.culled = obj.culled || !obj.cullingCollider.inFrustum(frustum);
  349. obj.cullingCollider = null;
  350. }
  351. for( o in obj )
  352. getRec(o);
  353. }
  354. getRec(s3d);
  355. s3d.ctx.debugCulling = true;
  356. camCtrl.loadFromCamera();
  357. if( app.s2d != null ) app.s2d.setElapsedTime(0);
  358. if( app.s3d != null ) app.s3d.setElapsedTime(0);
  359. hxd.System.setLoop(function() {
  360. app.sevents.checkEvents();
  361. app.engine.render(app);
  362. });
  363. }
  364. }