Benchmark.hx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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 dispatches : Int;
  9. public var next : QueryObject;
  10. public function new() {
  11. driver = h3d.Engine.getCurrent().driver;
  12. q = driver.allocQuery(TimeStamp);
  13. }
  14. public function sync() {
  15. value = driver.queryResult(q);
  16. }
  17. public function isAvailable() {
  18. return driver.queryResultAvailable(q);
  19. }
  20. public function dispose() {
  21. driver.deleteQuery(q);
  22. q = null;
  23. }
  24. }
  25. private class StatsObject {
  26. public var name : String;
  27. public var time : Float;
  28. public var drawCalls : Int;
  29. public var dispatches : Int;
  30. public var next : StatsObject;
  31. public var xPos : Int;
  32. public var xSize : Int;
  33. public function new() {
  34. }
  35. }
  36. class Benchmark extends h2d.Graphics {
  37. var cachedStats : StatsObject;
  38. var currentStats : StatsObject;
  39. var cachedQueries : QueryObject;
  40. var currentFrame : QueryObject;
  41. var waitFrames : Array<QueryObject>;
  42. var engine : h3d.Engine;
  43. var stats : StatsObject;
  44. var labels : Array<h2d.Text>;
  45. var interact : h2d.Interactive;
  46. public var estimateWait = false;
  47. public var enable(default,set) : Bool;
  48. public var width : Null<Int>;
  49. public var height = 16;
  50. public var textColor = 0;
  51. public var colors = new Array<Int>();
  52. public var font : h2d.Font;
  53. public var recalTime = 1e9;
  54. public var smoothTime = 0.95;
  55. public var measureCpu = false;
  56. public var displayTriangleCount = true;
  57. #if target.threaded public var measureCpuThread: sys.thread.Thread = null; #end
  58. var tip : h2d.Text;
  59. var tipCurrent : StatsObject;
  60. var tipCurName : String;
  61. var curWidth : Int;
  62. var prevFrame : Float;
  63. var frameTime : Float;
  64. public function new(?parent) {
  65. super(parent);
  66. waitFrames = [];
  67. labels = [];
  68. engine = h3d.Engine.getCurrent();
  69. interact = new h2d.Interactive(0,0,this);
  70. interact.onMove = onMove;
  71. interact.cursor = Default;
  72. interact.onOut = function(_) {
  73. if( tip == null ) return;
  74. tip.parent.remove();
  75. tip = null;
  76. tipCurrent = null;
  77. }
  78. enable = engine.driver.hasFeature(Queries);
  79. }
  80. function set_enable(e) {
  81. if( !e )
  82. cleanup();
  83. return enable = e;
  84. }
  85. function cleanup() {
  86. while( waitFrames.length > 0 ) {
  87. var w = waitFrames.pop();
  88. while( w != null ) {
  89. w.dispose();
  90. w = w.next;
  91. }
  92. }
  93. while( cachedQueries != null ) {
  94. cachedQueries.dispose();
  95. cachedQueries = cachedQueries.next;
  96. }
  97. while( currentFrame != null ) {
  98. currentFrame.dispose();
  99. currentFrame = currentFrame.next;
  100. }
  101. }
  102. override function clear() {
  103. super.clear();
  104. if( labels != null ) {
  105. for( t in labels ) t.remove();
  106. labels = [];
  107. }
  108. if( interact != null ) interact.width = interact.height = 0;
  109. }
  110. override function onRemove() {
  111. super.onRemove();
  112. cleanup();
  113. }
  114. function onMove(e:hxd.Event) {
  115. var s = currentStats;
  116. while( s != null ) {
  117. if( e.relX >= s.xPos && e.relX <= s.xPos + s.xSize )
  118. break;
  119. s = s.next;
  120. }
  121. if( tip == null ) {
  122. var fl = new h2d.Flow(this);
  123. fl.y = -23;
  124. fl.backgroundTile = h2d.Tile.fromColor(0,1,1,0.8);
  125. fl.padding = 5;
  126. tip = new h2d.Text(font, fl);
  127. tip.dropShadow = { dx : 0, dy : 1, color : 0, alpha : 1 };
  128. }
  129. tipCurrent = s;
  130. tipCurName = s == null ? null : s.name;
  131. syncTip(s);
  132. }
  133. function syncTip(s:StatsObject) {
  134. inline function fmt(i : Int, n : String) {
  135. return i != 0 ? (i + ' ${n} ') : "";
  136. }
  137. if( s == null ) {
  138. tip.text = "total "+fmt(engine.drawCalls,"draws")+ fmt(engine.dispatches, "dispatches");
  139. if ( displayTriangleCount )
  140. tip.text += hxd.Math.fmt(engine.drawTriangles/1000000)+" Mtri";
  141. }
  142. else {
  143. var t = s.name + "( " + Std.int(s.time / 1e6) + "." + StringTools.lpad(""+(Std.int(s.time/1e4)%100),"0",2) + " ms";
  144. #if target.threaded
  145. if (measureCpuThread == null)
  146. #end
  147. t += " " + fmt(s.drawCalls, "draws")+ fmt(s.dispatches, "dispatches");
  148. t += ")";
  149. tip.text = t;
  150. }
  151. var tw = tip.textWidth + 10;
  152. var tx = s == null ? curWidth : s.xPos + ((s.xSize - tw) * .5);
  153. if( tx + tw > curWidth ) tx = curWidth - tw;
  154. if( tx < 0 ) tx = 0;
  155. if( hxd.Math.abs(tip.parent.x - tx) > 5 ) tip.parent.x = Std.int(tx);
  156. }
  157. public function begin(withVisual=true) {
  158. if( !enable ) return;
  159. var t0 = haxe.Timer.stamp();
  160. var ft = (t0 - prevFrame) * 1e9;
  161. if( hxd.Math.abs(ft - frameTime) > recalTime )
  162. frameTime = ft;
  163. else
  164. frameTime = frameTime * smoothTime + ft * (1 - smoothTime);
  165. prevFrame = t0;
  166. // end was not called...
  167. if( currentFrame != null )
  168. end();
  169. // sync with available frame
  170. var changed = false;
  171. while( waitFrames.length > 0 ) {
  172. var q = waitFrames[0];
  173. if( #if target.threaded measureCpuThread == null && #end !q.isAvailable() )
  174. break;
  175. waitFrames.shift();
  176. // recycle previous stats
  177. var st = currentStats;
  178. while( st != null ) {
  179. var n = st.next;
  180. st.next = cachedStats;
  181. cachedStats = st;
  182. st = n;
  183. }
  184. currentStats = null;
  185. var prev : QueryObject = null;
  186. var totalTime = 0.;
  187. while( q != null ) {
  188. if( !measureCpu ) q.sync();
  189. if( prev != null ) {
  190. var dt = prev.value - q.value;
  191. var s = allocStat(q.name, dt);
  192. totalTime += dt;
  193. s.drawCalls = prev.drawCalls - q.drawCalls;
  194. if( s.drawCalls < 0 ) s.drawCalls = 0;
  195. s.dispatches = prev.dispatches - q.dispatches;
  196. if ( s.dispatches < 0 ) s.dispatches = 0;
  197. }
  198. // recycle
  199. var n = q.next;
  200. q.next = cachedQueries;
  201. cachedQueries = q;
  202. prev = q;
  203. q = n;
  204. }
  205. if( estimateWait ) {
  206. var waitT = frameTime - totalTime;
  207. if( waitT > 0 ) {
  208. if( hxd.Window.getInstance().vsync ) {
  209. var vst = 1e9 / hxd.System.getDefaultFrameRate() - totalTime;
  210. if( vst > waitT ) vst = waitT;
  211. if( vst > 0 ) {
  212. var s = allocStat("vsync", vst);
  213. s.drawCalls = 0;
  214. s.dispatches = 0;
  215. waitT -= vst;
  216. }
  217. }
  218. if( waitT > 0.5e6 /* 0.5 ms */ ) {
  219. var s = allocStat(measureCpu ? "gpuwait" : "cpuwait", waitT);
  220. s.drawCalls = 0;
  221. s.dispatches = 0;
  222. }
  223. }
  224. }
  225. // stats updated
  226. changed = true;
  227. }
  228. if (withVisual) {
  229. if( allocated && visible )
  230. syncVisual();
  231. }
  232. measure("begin");
  233. }
  234. public function syncVisual() {
  235. var s2d = getScene();
  236. var old = labels;
  237. labels = null;
  238. clear();
  239. labels = old;
  240. var width = width == null ? s2d.width : width;
  241. curWidth = width;
  242. beginFill(0, 0.5);
  243. drawRect(0, 0, width, height);
  244. interact.width = width;
  245. interact.height = height;
  246. var totalTime = 0.;
  247. var s = currentStats;
  248. while( s != null ) {
  249. totalTime += s.time;
  250. s = s.next;
  251. }
  252. var space = 57;
  253. width -= space;
  254. var count = 0;
  255. var xPos = 0;
  256. var curTime = 0.;
  257. var s = currentStats;
  258. while( s != null ) {
  259. if( colors.length <= count ) {
  260. var color = new h3d.Vector();
  261. var m = new h3d.Matrix();
  262. m.identity();
  263. m.colorHue(count);
  264. color.setColor(0x3399FF);
  265. color.transform(m);
  266. colors.push(color.toColor());
  267. }
  268. curTime += s.time;
  269. var xEnd = Math.ceil(width * (curTime / totalTime));
  270. var xSize = xEnd - xPos;
  271. beginFill(colors[count]);
  272. drawRect(xPos, 0, xSize, height);
  273. var l = allocLabel(count);
  274. if( xSize < s.name.length * 6 )
  275. l.visible = false;
  276. else {
  277. l.visible = true;
  278. l.textColor = textColor;
  279. l.text = s.name;
  280. l.x = xPos + Std.int((xSize - l.textWidth) * .5);
  281. }
  282. s.xPos = xPos;
  283. s.xSize = xSize;
  284. if( tipCurrent == s && tipCurName == s.name )
  285. syncTip(s);
  286. xPos = xEnd;
  287. count++;
  288. s = s.next;
  289. }
  290. if( tip != null && tipCurrent == null )
  291. syncTip(null);
  292. var time = allocLabel(count++);
  293. time.x = xPos + 3;
  294. time.y = -1;
  295. time.visible = true;
  296. time.textColor = 0xFFFFFF;
  297. var timeMs = totalTime / 1e6;
  298. var totalName = measureCpu ? "cpu" : "gpu";
  299. #if target.threaded
  300. if (measureCpuThread != null) {
  301. var n = measureCpuThread.getName();
  302. if (n != null)
  303. totalName = n;
  304. }
  305. #end
  306. time.text = Std.int(timeMs) + "." + Std.int((timeMs * 10) % 10) + " " + totalName;
  307. while( labels.length > count )
  308. labels.pop().remove();
  309. }
  310. function allocLabel(index) {
  311. var l = labels[index];
  312. if( l != null )
  313. return l;
  314. if( font == null ) font = hxd.res.DefaultFont.get();
  315. l = new h2d.Text(font, this);
  316. labels[index] = l;
  317. return l;
  318. }
  319. public function end() {
  320. if( !enable ) return;
  321. measure("end");
  322. waitFrames.push(currentFrame);
  323. currentFrame = null;
  324. }
  325. function allocStat( name, time : Float ) {
  326. var s = cachedStats;
  327. if( s != null )
  328. cachedStats = s.next;
  329. else
  330. s = new StatsObject();
  331. if( name == s.name ) {
  332. // smooth
  333. var et = hxd.Math.abs(time - s.time);
  334. if( et > recalTime )
  335. s.time = time;
  336. else
  337. s.time = s.time * smoothTime + time * (1 - smoothTime);
  338. } else {
  339. s.name = name;
  340. s.time = time;
  341. }
  342. s.next = currentStats;
  343. currentStats = s;
  344. return s;
  345. }
  346. function allocQuery() {
  347. var q = cachedQueries;
  348. if( q != null )
  349. cachedQueries = q.next;
  350. else
  351. q = new QueryObject();
  352. return q;
  353. }
  354. public function measure( name : String ) {
  355. if( !enable ) return;
  356. if( currentFrame != null && currentFrame.name == name )
  357. return;
  358. #if target.threaded
  359. if( measureCpuThread != null && sys.thread.Thread.current() != measureCpuThread )
  360. return;
  361. #end
  362. var q = allocQuery();
  363. q.name = name;
  364. q.drawCalls = engine.drawCalls;
  365. q.dispatches = engine.dispatches;
  366. q.next = currentFrame;
  367. currentFrame = q;
  368. engine.driver.endQuery(q.q);
  369. if( measureCpu ) q.value = haxe.Timer.stamp() * 1e9;
  370. }
  371. public function getCurrentId() {
  372. if ( currentFrame != null )
  373. return currentFrame.name;
  374. return null;
  375. }
  376. public static function takeControl( app : hxd.App, ?s3d : h3d.scene.Scene ) @:privateAccess {
  377. if( s3d == null ) s3d = app.s3d;
  378. var cur = hxd.System.getCurrentLoop();
  379. var prevInt = s3d.interactives;
  380. var prevScenes = app.sevents.scenes;
  381. app.sevents.scenes = [s3d];
  382. s3d.interactives = [];
  383. var camCtrl = new h3d.scene.CameraController(s3d);
  384. var prevStates = new Map();
  385. var frustum = s3d.camera.frustum;
  386. function getRec( obj : h3d.scene.Object ) {
  387. if( obj.cullingCollider != null ) {
  388. prevStates.set(obj,{ cul : obj.cullingCollider, culled : obj.culled });
  389. obj.culled = obj.culled || !obj.cullingCollider.inFrustum(frustum);
  390. obj.cullingCollider = null;
  391. }
  392. for( o in obj )
  393. getRec(o);
  394. }
  395. getRec(s3d);
  396. s3d.ctx.debugCulling = true;
  397. camCtrl.loadFromCamera();
  398. if( app.s2d != null ) app.s2d.setElapsedTime(0);
  399. if( app.s3d != null ) app.s3d.setElapsedTime(0);
  400. hxd.System.setLoop(function() {
  401. app.sevents.checkEvents();
  402. app.engine.render(app);
  403. });
  404. }
  405. }