Skip to content

Commit 3b9983c

Browse files
authored
Mini-stats update with functionality and flexibility (playcanvas#2428)
* Mini-stats - separated file into per-class files. * fixed typo in comment * implemented new features * lint fix * small improvements * small changes (white text, black clear color, smaller gaps between graphs) * text labels are slightly darker than numbers * tracking basic frame timing and drawCall count even in release build * spacing updates * black background when graph is disabled * size adjustement
1 parent 5389039 commit 3b9983c

File tree

13 files changed

+891
-408
lines changed

13 files changed

+891
-408
lines changed

examples/examples.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ var categories = [
6666
}, {
6767
name: "misc",
6868
examples: [
69+
"mini-stats",
6970
"multi-application"
7071
]
7172
}, {

examples/misc/mini-stats.html

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>PlayCanvas Mini Stats</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link rel="icon" type="image/png" href="../playcanvas-favicon.png" />
8+
<script src="../../build/playcanvas.prf.js"></script>
9+
<script src="../../build/playcanvas-extras.js"></script>
10+
<style>
11+
body {
12+
margin: 0;
13+
overflow-y: hidden;
14+
}
15+
</style>
16+
</head>
17+
18+
<body>
19+
<!-- The canvas element -->
20+
<canvas id="application-canvas"></canvas>
21+
22+
<!-- The script -->
23+
<script>
24+
var canvas = document.getElementById("application-canvas");
25+
26+
// Create the application and start the update loop
27+
var app = new pc.Application(canvas);
28+
app.start();
29+
30+
// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
31+
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
32+
app.setCanvasResolution(pc.RESOLUTION_AUTO);
33+
34+
window.addEventListener("resize", function () {
35+
app.resizeCanvas(canvas.width, canvas.height);
36+
});
37+
38+
// set up options for mini-stats, start with the default options
39+
var options = pcx.MiniStats.getDefaultOptions();
40+
41+
// configure sizes
42+
options.sizes = [
43+
{ width: 128, height: 16, spacing: 0, graphs: false },
44+
{ width: 256, height: 32, spacing: 2, graphs: true },
45+
{ width: 500, height: 64, spacing: 2, graphs: true }
46+
];
47+
48+
// when the application starts, use the largest size
49+
options.startSizeIndex = 2;
50+
51+
// display additional counters
52+
// Note: for most of thees to report values, either debug or profiling engine build needs to be used.
53+
options.stats = [
54+
55+
// frame update time in ms
56+
{
57+
name: "Update",
58+
stats: ["frame.updateTime"],
59+
decimalPlaces: 1,
60+
unitsName: "ms",
61+
watermark: 33
62+
},
63+
64+
// total number of draw calls
65+
{
66+
name: "DrawCalls",
67+
stats: ["drawCalls.total"],
68+
watermark: 2000
69+
},
70+
71+
// total number of triangles, in 1000s
72+
{
73+
name: "triCount",
74+
stats: ["frame.triangles"],
75+
decimalPlaces: 1,
76+
multiplier: 1 / 1000,
77+
unitsName: "k",
78+
watermark: 500
79+
},
80+
81+
// number of materials used in a frame
82+
{
83+
name: "materials",
84+
stats: ["frame.materials"],
85+
watermark: 2000
86+
},
87+
88+
// frame time it took to do frustum culling
89+
{
90+
name: "cull",
91+
stats: ["frame.cullTime"],
92+
decimalPlaces: 1,
93+
watermark: 1,
94+
unitsName: "ms",
95+
},
96+
97+
// used VRAM, displayed using 2 colors - red for textures, green for geometry
98+
{
99+
name: "VRAM",
100+
stats: ["vram.tex", "vram.geom"],
101+
decimalPlaces: 1,
102+
multiplier: 1 / (1024 * 1024),
103+
unitsName: "MB",
104+
watermark: 100
105+
},
106+
107+
// frames per second
108+
{
109+
name: "FPS",
110+
stats: ["frame.fps"],
111+
watermark: 60
112+
},
113+
114+
// delta time
115+
{
116+
name: "Frame",
117+
stats: ["frame.ms"],
118+
decimalPlaces: 1,
119+
unitsName: "ms",
120+
watermark: 33
121+
}
122+
];
123+
124+
// create mini-stats system
125+
var miniStats = new pcx.MiniStats(app, options);
126+
127+
// add directional lights to the scene
128+
var light = new pc.Entity();
129+
light.addComponent("light", {
130+
type: "directional"
131+
});
132+
app.root.addChild(light);
133+
light.setLocalEulerAngles(45, 30, 0);
134+
135+
// Create an entity with a camera component
136+
var camera = new pc.Entity();
137+
camera.addComponent("camera", {
138+
clearColor: new pc.Color(0.1, 0.1, 0.1)
139+
});
140+
app.root.addChild(camera);
141+
camera.setLocalPosition(20, 10, 10);
142+
camera.lookAt(pc.Vec3.ZERO);
143+
144+
// helper function to create a primitive with shape type, position, scale
145+
function createPrimitive(primitiveType, position, scale) {
146+
147+
// create material of random color
148+
var material = new pc.StandardMaterial();
149+
material.diffuse = new pc.Color(Math.random(), Math.random(), Math.random());
150+
material.update();
151+
152+
// create primitive
153+
var primitive = new pc.Entity();
154+
primitive.addComponent('model', {
155+
type: primitiveType
156+
});
157+
primitive.model.material = material;
158+
159+
// set position and scale
160+
primitive.setLocalPosition(position);
161+
primitive.setLocalScale(scale);
162+
163+
return primitive;
164+
}
165+
166+
// list of all created engine resources
167+
var entities = [];
168+
var vertexBuffers = [];
169+
var textures = [];
170+
171+
// update function called every frame
172+
var adding = true;
173+
var step = 10, max = 2000;
174+
var entity, vertexBuffer, texture;
175+
app.on("update", function (dt) {
176+
177+
// execute some tasks multiple times per frame
178+
for (var i = 0; i < step; i++) {
179+
180+
// allocating resouces
181+
if (adding) {
182+
183+
// add entity (they used shared geometry internally, and we create individual material for each)
184+
var shape = Math.random() < 0.5 ? "box" : "sphere";
185+
var position = new pc.Vec3(Math.random() * 10, Math.random() * 10, Math.random() * 10);
186+
var scale = 0.5 + Math.random();
187+
entity = createPrimitive(shape, position, new pc.Vec3(scale, scale, scale));
188+
entities.push(entity);
189+
app.root.addChild(entity);
190+
191+
// if allocation reached the max limit, switch to removing mode
192+
if (entities.length >= max) {
193+
adding = false;
194+
}
195+
196+
// add vertex buffer
197+
var vertexCount = 500;
198+
var data = new Float32Array(vertexCount * 16);
199+
vertexBuffer = new pc.VertexBuffer(app.graphicsDevice, pc.VertexFormat.defaultInstancingFormat, vertexCount, pc.BUFFER_STATIC, data);
200+
vertexBuffers.push(vertexBuffer);
201+
202+
// allocate texture
203+
var texture = new pc.Texture(app.graphicsDevice, {
204+
width: 64,
205+
height: 64,
206+
format: pc.PIXELFORMAT_R8_G8_B8,
207+
autoMipmap: false
208+
});
209+
textures.push(texture);
210+
211+
// ensure texture is uploaded (actual VRAM is allocated)
212+
var dest = texture.lock();
213+
texture.unlock();
214+
app.graphicsDevice.setTexture(texture, 0);
215+
216+
} else { // de-allocating resources
217+
218+
if (entities.length > 0) {
219+
220+
// desotry entities
221+
entity = entities[entities.length - 1];
222+
entity.destroy();
223+
entities.length = entities.length - 1;
224+
225+
// destroy vertex buffer
226+
vertexBuffer = vertexBuffers[vertexBuffers.length - 1];
227+
vertexBuffer.destroy();
228+
vertexBuffers.length = vertexBuffers.length - 1;
229+
230+
// destroy texture
231+
texture = textures[textures.length - 1];
232+
texture.destroy();
233+
textures.length = textures.length - 1;
234+
235+
} else {
236+
adding = true;
237+
}
238+
}
239+
}
240+
});
241+
</script>
242+
</body>
243+
</html>

extras/mini-stats/cpu-timer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ function CpuTimer(app) {
33
this._frameTimings = [];
44
this._timings = [];
55
this._prevTimings = [];
6+
this.unitsName = "ms";
7+
this.decimalPlaces = 1;
68

79
this.enabled = true;
810

extras/mini-stats/gpu-timer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ function GpuTimer(app) {
1010
this._prevTimings = [];
1111

1212
this.enabled = true;
13+
this.unitsName = "ms";
14+
this.decimalPlaces = 1;
1315

1416
app.on('frameupdate', this.begin.bind(this, 'update'));
1517
app.on('framerender', this.mark.bind(this, 'render'));

extras/mini-stats/graph.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Realtime performance graph visual
2+
function Graph(name, app, watermark, textRefreshRate, timer) {
3+
this.name = name;
4+
this.device = app.graphicsDevice;
5+
this.timer = timer;
6+
this.watermark = watermark;
7+
this.enabled = false;
8+
this.textRefreshRate = textRefreshRate;
9+
10+
this.avgTotal = 0;
11+
this.avgTimer = 0;
12+
this.avgCount = 0;
13+
this.timingText = "";
14+
15+
this.texture = null;
16+
this.yOffset = 0;
17+
this.cursor = 0;
18+
this.sample = new Uint8ClampedArray(4);
19+
this.sample.set([0, 0, 0, 255]);
20+
21+
app.on('frameupdate', this.update.bind(this));
22+
23+
this.counter = 0;
24+
}
25+
26+
Object.assign(Graph.prototype, {
27+
update: function (ms) {
28+
var timings = this.timer.timings;
29+
30+
// calculate stacked total
31+
var total = timings.reduce(function (a, v) {
32+
return a + v;
33+
}, 0);
34+
35+
// update averages
36+
this.avgTotal += total;
37+
this.avgTimer += ms;
38+
this.avgCount++;
39+
40+
if (this.avgTimer > this.textRefreshRate) {
41+
this.timingText = (this.avgTotal / this.avgCount).toFixed(this.timer.decimalPlaces);
42+
this.avgTimer = 0;
43+
this.avgTotal = 0;
44+
this.avgCount = 0;
45+
}
46+
47+
if (this.enabled) {
48+
// update timings
49+
var value = 0;
50+
var range = 1.5 * this.watermark;
51+
for (var i = 0; i < timings.length; ++i) {
52+
53+
// scale the value into the range
54+
value += Math.floor(timings[i] / range * 255);
55+
this.sample[i] = value;
56+
}
57+
58+
// .a store watermark
59+
this.sample[3] = this.watermark / range * 255;
60+
61+
// write latest sample to the texture
62+
var gl = this.device.gl;
63+
this.device.bindTexture(this.texture);
64+
gl.texSubImage2D(gl.TEXTURE_2D,
65+
0,
66+
this.cursor,
67+
this.yOffset,
68+
1,
69+
1,
70+
gl.RGBA,
71+
gl.UNSIGNED_BYTE,
72+
this.sample);
73+
74+
// update cursor position
75+
this.cursor++;
76+
if (this.cursor === this.texture.width) {
77+
this.cursor = 0;
78+
}
79+
}
80+
},
81+
82+
render: function (render2d, x, y, w, h) {
83+
render2d.quad(this.texture,
84+
x + w,
85+
y,
86+
-w,
87+
h,
88+
this.cursor,
89+
0.5 + this.yOffset,
90+
-w, 0,
91+
this.enabled);
92+
}
93+
});
94+
95+
export { Graph };

0 commit comments

Comments
 (0)