瀏覽代碼

implemented queries and benchmark visualization

Nicolas Cannasse 8 年之前
父節點
當前提交
56093c99a7
共有 5 個文件被更改,包括 413 次插入5 次删除
  1. 292 0
      h3d/impl/Benchmark.hx
  2. 44 0
      h3d/impl/Driver.hx
  3. 43 1
      h3d/impl/GlDriver.hx
  4. 5 1
      h3d/scene/Renderer.hx
  5. 29 3
      samples/Sao.hx

+ 292 - 0
h3d/impl/Benchmark.hx

@@ -0,0 +1,292 @@
+package h3d.impl;
+
+private class QueryObject {
+
+	var driver : h3d.impl.Driver;
+
+	public var q : h3d.impl.Driver.Query;
+	public var value : Float;
+	public var name : String;
+	public var drawCalls : Int;
+	public var next : QueryObject;
+
+	public function new() {
+		driver = h3d.Engine.getCurrent().driver;
+		q = driver.allocQuery(TimeStamp);
+	}
+
+	public function sync() {
+		value = driver.queryResult(q);
+	}
+
+	public function isAvailable() {
+		return driver.queryResultAvailable(q);
+	}
+
+}
+
+private class StatsObject {
+	public var name : String;
+	public var time : Float;
+	public var drawCalls : Int;
+	public var next : StatsObject;
+	public var xPos : Int;
+	public var xSize : Int;
+	public function new() {
+	}
+}
+
+class Benchmark extends h2d.Graphics {
+
+	var cachedStats : StatsObject;
+	var currentStats : StatsObject;
+	var cachedQueries : QueryObject;
+	var currentFrame : QueryObject;
+	var waitFrames : Array<QueryObject>;
+	var engine : h3d.Engine;
+	var stats : StatsObject;
+	var labels : Array<h2d.Text>;
+	var interact : h2d.Interactive;
+
+	public var enable : Bool;
+
+	public var width : Null<Int>;
+	public var height = 16;
+	public var textColor = 0;
+	public var colors = new Array<Int>();
+	public var font : h2d.Font;
+
+	var tip : h2d.Text;
+	var tipCurrent : StatsObject;
+	var tipCurName : String;
+	var curWidth : Int;
+
+	public function new(?parent) {
+		super(parent);
+		waitFrames = [];
+		labels = [];
+		engine = h3d.Engine.getCurrent();
+		enable = engine.driver.hasFeature(Queries);
+		interact = new h2d.Interactive(0,0,this);
+		interact.onMove = onMove;
+		interact.cursor = Default;
+		interact.onOut = function(_) {
+			if( tip == null ) return;
+			tip.remove();
+			tip = null;
+			tipCurrent = null;
+		}
+	}
+
+	function onMove(e:hxd.Event) {
+		var s = currentStats;
+		while( s != null ) {
+			if( e.relX >= s.xPos && e.relX <= s.xPos + s.xSize )
+				break;
+			s = s.next;
+		}
+		if( tip != null ) {
+			tip.remove();
+			tip = null;
+			tipCurrent = null;
+		}
+		if( s == null )
+			return;
+		tip = new h2d.Text(font, this);
+		tip.y = -20;
+		tipCurrent = s;
+		tipCurName = s.name;
+		syncTip(s);
+	}
+
+	function syncTip(s:StatsObject) {
+		tip.text = s.name+"(" + Std.int(s.time / 1000) + " us " + s.drawCalls + " draws)";
+		var tw = tip.textWidth;
+		var tx = s.xPos + ((s.xSize - tw) >> 1);
+		if( tx + tw > curWidth ) tx = curWidth - tw;
+		if( tx < 0 ) tx = 0;
+		if( hxd.Math.abs(tip.x - tx) > 5 ) tip.x = tx;
+	}
+
+	public function begin() {
+
+		if( !enable ) return;
+
+		// end was not called...
+		if( currentFrame != null )
+			end();
+
+		// sync with available frame
+		var changed = false;
+		while( waitFrames.length > 0 ) {
+			var q = waitFrames[0];
+			if( !q.isAvailable() )
+				break;
+			waitFrames.shift();
+
+			// recycle previous stats
+			var st = currentStats;
+			while( st != null ) {
+				var n = st.next;
+				st.next = cachedStats;
+				cachedStats = st;
+				st = n;
+			}
+			currentStats = null;
+
+			var prev : QueryObject = null;
+			while( q != null ) {
+				q.sync();
+				if( prev != null ) {
+					var s = allocStat();
+					var dt = prev.value - q.value;
+					if( s.name == q.name ) {
+						// smooth
+						var et = hxd.Math.abs(dt - s.time);
+						if( et > 4e6 )
+							s.time = dt;
+						else
+							s.time = s.time * 0.9 + dt * 0.1;
+					} else {
+						s.name = q.name;
+						s.time = dt;
+					}
+					s.drawCalls = prev.drawCalls - q.drawCalls;
+					s.next = currentStats;
+					currentStats = s;
+				}
+				// recycle
+				var n = q.next;
+				q.next = cachedQueries;
+				cachedQueries = q;
+				prev = q;
+				q = n;
+			}
+			// stats updated
+			changed = true;
+		}
+
+		if( allocated && visible )
+			syncVisual();
+
+		measure("begin");
+	}
+
+	function syncVisual() {
+		var s2d = getScene();
+		clear();
+		var width = width == null ? s2d.width : width;
+		curWidth = width;
+		beginFill(0, 0.5);
+		drawRect(0, 0, width, height);
+
+		interact.width = width;
+		interact.height = height;
+
+		var totalTime = 0.;
+		var s = currentStats;
+		while( s != null ) {
+			totalTime += s.time;
+			s = s.next;
+		}
+
+		var space = 40;
+		width -= space;
+
+		var count = 0;
+		var xPos = 0;
+		var curTime = 0.;
+		var s = currentStats;
+		while( s != null ) {
+			if( colors.length < count ) {
+				var color = new h3d.Vector();
+				var m = new h3d.Matrix();
+				m.identity();
+				m.colorHue(count);
+				color.setColor(0x3399FF);
+				color.transform(m);
+				colors.push(color.toColor());
+			}
+
+			curTime += s.time;
+			var xEnd = Math.ceil(width * (curTime / totalTime));
+			var xSize = xEnd - xPos;
+			beginFill(colors[count]);
+			drawRect(xPos, 0, xSize, height);
+
+			var l = allocLabel(count);
+			if( xSize < s.name.length * 4 )
+				l.visible = false;
+			else {
+				l.text = s.name;
+				l.x = xPos + ((xSize - l.textWidth) >> 1);
+			}
+
+			s.xPos = xPos;
+			s.xSize = xSize;
+
+			if( tipCurrent == s && tipCurName == s.name )
+				syncTip(s);
+
+			xPos = xEnd;
+			count++;
+			s = s.next;
+		}
+
+		var time = allocLabel(count++);
+		time.x = xPos + 10;
+		time.textColor = 0xFFFFFF;
+		var timeMs = totalTime / 1e6;
+		time.text = Std.int(timeMs) + "." + Std.int((timeMs * 10) % 10);
+
+		while( labels.length > count )
+			labels.pop().remove();
+	}
+
+	function allocLabel(index) {
+		var l = labels[index];
+		if( l != null )
+			return l;
+		if( font == null ) font = hxd.res.DefaultFont.get();
+		l = new h2d.Text(font, this);
+		l.textColor = textColor;
+		labels[index] = l;
+		return l;
+	}
+
+	public function end() {
+		if( !enable ) return;
+		measure("end");
+		waitFrames.push(currentFrame);
+		currentFrame = null;
+	}
+
+	function allocStat() {
+		var s = cachedStats;
+		if( s != null )
+			cachedStats = s.next;
+		else
+			s = new StatsObject();
+		return s;
+	}
+
+	function allocQuery() {
+		var q = cachedQueries;
+		if( q != null )
+			cachedQueries = q.next;
+		else
+			q = new QueryObject();
+		return q;
+	}
+
+	public function measure( name : String ) {
+		if( !enable ) return;
+		var q = allocQuery();
+		q.name = name;
+		q.drawCalls = engine.drawCalls;
+		q.next = currentFrame;
+		currentFrame = q;
+		engine.driver.endQuery(q.q);
+	}
+
+}

+ 44 - 0
h3d/impl/Driver.hx

@@ -5,31 +5,37 @@ typedef IndexBuffer = flash.display3D.IndexBuffer3D;
 typedef VertexBuffer = Stage3dDriver.VertexWrapper;
 typedef Texture = flash.display3D.textures.TextureBase;
 typedef DepthBuffer = {};
+typedef Query = {};
 #elseif js
 typedef IndexBuffer = js.html.webgl.Buffer;
 typedef VertexBuffer = { b : js.html.webgl.Buffer, stride : Int };
 typedef Texture = { t : js.html.webgl.Texture, width : Int, height : Int, internalFmt : Int, pixelFmt : Int };
 typedef DepthBuffer = { r : js.html.webgl.Renderbuffer };
+typedef Query = {};
 #elseif nme
 typedef IndexBuffer = nme.gl.GLBuffer;
 typedef VertexBuffer = { b : nme.gl.GLBuffer, stride : Int };
 typedef Texture = { t : nme.gl.GLTexture, width : Int, height : Int, internalFmt : Int, pixelFmt : Int };
 typedef DepthBuffer = { r : nme.gl.Renderbuffer };
+typedef Query = {};
 #elseif lime
 typedef IndexBuffer = lime.graphics.opengl.GLBuffer;
 typedef VertexBuffer = { b : lime.graphics.opengl.GLBuffer, stride : Int };
 typedef Texture = { t : lime.graphics.opengl.GLTexture, width : Int, height : Int, internalFmt : Int, pixelFmt : Int };
 typedef DepthBuffer = { r : lime.graphics.opengl.GLRenderbuffer };
+typedef Query = {};
 #elseif hxsdl
 typedef IndexBuffer = sdl.GL.Buffer;
 typedef VertexBuffer = { b : sdl.GL.Buffer, stride : Int };
 typedef Texture = { t : sdl.GL.Texture, width : Int, height : Int, internalFmt : Int, pixelFmt : Int };
 typedef DepthBuffer = { r : sdl.GL.Renderbuffer };
+typedef Query = { q : sdl.GL.Query, kind : QueryKind };
 #else
 typedef IndexBuffer = {};
 typedef VertexBuffer = {};
 typedef Texture = {};
 typedef DepthBuffer = {};
+typedef Query = {};
 #end
 
 enum Feature {
@@ -55,6 +61,21 @@ enum Feature {
 		Allows to render on several render targets with a single draw.
 	*/
 	MultipleRenderTargets;
+	/*
+		Does it supports query objects API.
+	*/
+	Queries;
+}
+
+enum QueryKind {
+	/**
+		The result will give the GPU Timestamp (in nanoseconds, 1e-9 seconds) at the time the endQuery is performed
+	**/
+	TimeStamp;
+	/**
+		The result will give the number of samples that passes the depth buffer between beginQuery/endQuery range
+	**/
+	Samples;
 }
 
 class Driver {
@@ -196,4 +217,27 @@ class Driver {
 	public function uploadTexturePixels( t : h3d.mat.Texture, pixels : hxd.Pixels, mipLevel : Int, side : Int ) {
 	}
 
+	// --- QUERY API
+
+	public function allocQuery( queryKind : QueryKind ) : Query {
+		return null;
+	}
+
+	public function deleteQuery( q : Query ) {
+	}
+
+	public function beginQuery( q : Query ) {
+	}
+
+	public function endQuery( q : Query ) {
+	}
+
+	public function queryResultAvailable( q : Query ) {
+		return true;
+	}
+
+	public function queryResult( q : Query ) {
+		return 0.;
+	}
+
 }

+ 43 - 1
h3d/impl/GlDriver.hx

@@ -40,6 +40,7 @@ private typedef Program = sdl.GL.Program;
 private typedef GLShader = sdl.GL.Shader;
 private typedef Framebuffer = sdl.GL.Framebuffer;
 private typedef Texture = h3d.impl.Driver.Texture;
+private typedef Query = h3d.impl.Driver.Query;
 #if cpp
 private typedef Float32Array = Array<cpp.Float32>;
 #end
@@ -958,7 +959,7 @@ class GlDriver extends Driver {
 	override function hasFeature( f : Feature ) : Bool {
 		return switch( f ) {
 		#if hxsdl
-		case StandardDerivatives, FloatTextures, MultipleRenderTargets:
+		case StandardDerivatives, FloatTextures, MultipleRenderTargets, Queries:
 			true; // runtime extension detect required ?
 		#else
 		case StandardDerivatives:
@@ -971,6 +972,8 @@ class GlDriver extends Driver {
 			#else
 			false; // no support for glDrawBuffers in OpenFL
 			#end
+		case Queries:
+			false;
 		#end
 		case HardwareAccelerated:
 			true;
@@ -989,6 +992,45 @@ class GlDriver extends Driver {
 		#end
 	}
 
+	#if hl
+
+	override function allocQuery(kind:QueryKind) {
+		return { q : GL.createQuery(), kind : kind };
+	}
+
+	override function deleteQuery( q : Query ) {
+		GL.deleteQuery(q.q);
+		q.q = null;
+	}
+
+	override function beginQuery( q : Query ) {
+		switch( q.kind ) {
+		case TimeStamp:
+			throw "use endQuery() for timestamp queries";
+		case Samples:
+			GL.beginQuery(GL.SAMPLES_PASSED, q.q);
+		}
+	}
+
+	override function endQuery( q : Query ) {
+		switch( q.kind ) {
+		case TimeStamp:
+			GL.queryCounter(q.q, GL.TIMESTAMP);
+		case Samples:
+			GL.endQuery(GL.SAMPLES_PASSED);
+		}
+	}
+
+	override function queryResultAvailable(q:Query) {
+		return GL.queryResultAvailable(q.q);
+	}
+
+	override function queryResult(q:Query) {
+		return GL.queryResult(q.q);
+	}
+
+	#end
+
 	static var TFILTERS = [
 		[[GL.NEAREST,GL.NEAREST],[GL.LINEAR,GL.LINEAR]],
 		[[GL.NEAREST,GL.NEAREST_MIPMAP_NEAREST],[GL.LINEAR,GL.LINEAR_MIPMAP_NEAREST]],

+ 5 - 1
h3d/scene/Renderer.hx

@@ -150,6 +150,10 @@ class Renderer {
 		def.draw(get(name));
 	}
 
+	function renderPass( name : String, p : h3d.pass.Base, passes ) {
+		return p.draw(passes);
+	}
+
 	function render() {
 		for( p in allPasses ) {
 			var pdata = passGroups.get(p.name);
@@ -162,7 +166,7 @@ class Renderer {
 					passes = depthSort(passes);
 				if( p.name == "default" )
 					passes = depthSort(passes, true);
-				passes = p.p.draw(passes);
+				passes = renderPass(p.name, p.p, passes);
 				if( pdata != null ) {
 					pdata.passes = passes;
 					pdata.rendered = true;

+ 29 - 3
samples/Sao.hx

@@ -10,6 +10,8 @@ class CustomRenderer extends h3d.scene.Renderer {
 	public var hasMRT : Bool;
 	var out : h3d.mat.Texture;
 
+	public var bench = new h3d.impl.Benchmark();
+
 	public function new() {
 		super();
 		sao = new h3d.pass.ScalableAO();
@@ -18,12 +20,18 @@ class CustomRenderer extends h3d.scene.Renderer {
 		sao.shader.sampleRadius	= 0.2;
 		hasMRT = h3d.Engine.getCurrent().driver.hasFeature(MultipleRenderTargets);
 		if( hasMRT )
-			def = new h3d.pass.MRT(["color","depth","normal"],0,true);
+			def = new h3d.pass.MRT(["color", "depth", "normal"], 0, true);
+	}
+
+	override function renderPass(name, p:h3d.pass.Base, passes) {
+		bench.measure(name);
+		return super.renderPass(name, p, passes);
 	}
 
 	override function render() {
 		super.render();
 		if(mode != 1) {
+			bench.measure("sao");
 			var saoTarget = allocTarget("sao",0,false);
 			setTarget(saoTarget);
 			if( hasMRT )
@@ -31,7 +39,9 @@ class CustomRenderer extends h3d.scene.Renderer {
 			else
 				sao.apply(depth.getTexture(), normal.getTexture(), ctx.camera);
 			resetTarget();
+			bench.measure("saoBlur");
 			saoBlur.apply(saoTarget, allocTarget("saoBlurTmp", 0, false));
+			bench.measure("saoBlend");
 			if( hasMRT ) h3d.pass.Copy.run(def.getTexture(0), null);
 			h3d.pass.Copy.run(saoTarget, null, mode == 0 ? Multiply : null);
 		}
@@ -43,6 +53,7 @@ class Sao extends hxd.App {
 
 	var wscale = 1.;
 	var fui : h2d.Flow;
+	var renderer : CustomRenderer;
 
 	function initMaterial( m : h3d.mat.MeshMaterial ) {
 		m.mainPass.enableLights = true;
@@ -52,6 +63,14 @@ class Sao extends hxd.App {
 		}
 	}
 
+	override function render(e:h3d.Engine) {
+		renderer.bench.begin();
+		s3d.render(e);
+		renderer.bench.measure("ui");
+		s2d.render(e);
+		renderer.bench.end();
+	}
+
 	override function init() {
 
 		fui = new h2d.Flow(s2d);
@@ -60,8 +79,9 @@ class Sao extends hxd.App {
 
 		var r = new hxd.Rand(Std.random(0xFFFFFF));
 
-		var c = new CustomRenderer();
-		s3d.renderer = c;
+		renderer = new CustomRenderer();
+		s2d.add(renderer.bench, 10);
+		s3d.renderer = renderer;
 
 		var floor = new h3d.prim.Grid(40,40,0.25,0.25);
 		floor.addNormals();
@@ -94,11 +114,13 @@ class Sao extends hxd.App {
 		s3d.camera.pos.set(camdist * Math.cos(time), camdist * Math.sin(time), camdist * 0.5);
 		new h3d.scene.CameraController(s3d).loadFromCamera();
 
+		var c = renderer;
 		addSlider("Bias", 0, 0.3, function() return c.sao.shader.bias, function(v) c.sao.shader.bias = v);
 		addSlider("Intensity", 0, 10, function() return c.sao.shader.intensity, function(v) c.sao.shader.intensity = v);
 		addSlider("Radius", 0, 1, function() return c.sao.shader.sampleRadius, function(v) c.sao.shader.sampleRadius = v);
 		addSlider("Blur", 0, 3, function() return c.saoBlur.sigma, function(v) c.saoBlur.sigma = v);
 
+		onResize();
 	}
 
 	function addSlider( text, min : Float, max : Float, get : Void -> Float, set : Float -> Void ) {
@@ -139,6 +161,10 @@ class Sao extends hxd.App {
 		init();
 	}
 
+	override function onResize() {
+		renderer.bench.y = s2d.height - renderer.bench.height;
+	}
+
 	override function update( dt : Float ) {
 
 		if(K.isPressed(K.BACKSPACE))