blob: 5521b2d16e7c36ac291efa92a6979858d8603434 [file] [log] [blame] [edit]
var emscriptenCpuProfiler = {
// UI update interval in milliseconds.
uiUpdateInterval: 1,
// Specifies the pixel column where the previous UI update finished at. (current "draw cursor" position next draw will resume from)
lastUiUpdateEndX: 0,
// An array which stores samples of msec durations spent in the emscripten main loop (emscripten_set_main_loop).
// Carries # samples equal to the pixel width of the profiler display window, and old samples are erased in a rolling window fashion.
timeSpentInMainloop: [],
// Similar to 'timeSpentInMainloop', except this stores msec durations outside the emscripten main loop callback. (either idle CPU time, browser processing, or something else)
timeSpentOutsideMainloop: [],
// Specifies the sample coordinate into the timeSpentIn/OutsideMainloop arrays that is currently being populated.
currentHistogramX: 0,
// Wallclock time denoting when the currently executing main loop callback tick began.
currentFrameStartTime: 0,
// Wallclock time denoting when the previously executing main loop was finished.
previousFrameEndTime: 0,
// The total time spent in a frame can be further subdivided down into 'sections'. This array stores info structures representing each section.
sections: [],
// The 2D canvas DOM element to which the CPU profiler graph is rendered to.
canvas: null,
// The 2D drawing context on the canvas.
drawContext: null,
// How many milliseconds in total to fit vertically into the displayed CPU profiler window? Frametimes longer than this are out the graph and not visible.
verticalTimeScale: 40,
// History of wallclock times of N most recent frame times. Used to estimate current FPS.
fpsCounterTicks: [],
// When was the FPS UI display last updated?
fpsCounterLastPrint: performance.now(),
// fpsCounter() is called once per frame to record an executed frame time, and to periodically update the FPS counter display.
fpsCounter: function fpsCounter() {
// Record the new frame time sample, and prune the history to 120 most recent frames.
var now = performance.now();
this.fpsCounterTicks.push(now);
if (this.fpsCounterTicks.length > 120) this.fpsCounterTicks = this.fpsCounterTicks.slice(this.fpsCounterTicks.length - 120, this.fpsCounterTicks.length);
if (now - this.fpsCounterLastPrint > 2000) {
var fps = ((this.fpsCounterTicks.length - 1) * 1000.0 / (this.fpsCounterTicks[this.fpsCounterTicks.length - 1] - this.fpsCounterTicks[0]));
var totalDt = 0;
var totalRAFDt = 0;
var minDt = 99999999;
var maxDt = 0;
var nSamples = 0;
for (var i = 0; i < this.timeSpentInMainloop.length; ++i) {
var dt = this.timeSpentInMainloop[i] + this.timeSpentOutsideMainloop[i];
totalRAFDt += this.timeSpentInMainloop[i];
if (dt > 0) ++nSamples;
totalDt += dt;
minDt = Math.min(minDt, dt);
maxDt = Math.max(maxDt, dt);
}
var avgDt = totalDt / nSamples;
var avgFps = 1000.0 / avgDt;
var dtVariance = 0;
for (var i = 1; i < this.timeSpentInMainloop.length; ++i) {
var dt = this.timeSpentInMainloop[i] + this.timeSpentOutsideMainloop[i];
var d = dt - avgDt;
dtVariance += d*d;
}
dtVariance /= nSamples;
var asmJSLoad = totalRAFDt * 100.0 / totalDt;
var str = 'Last FPS: ' + fps.toFixed(2) + ', avg FPS:' + avgFps.toFixed(2) + ', min/avg/max dt: '
+ minDt.toFixed(2) + '/' + avgDt.toFixed(2) + '/' + maxDt.toFixed(2) + ' msecs, dt variance: ' + dtVariance.toFixed(3)
+ ', JavaScript CPU load: ' + asmJSLoad.toFixed(2) + '%';
document.getElementById('fpsResult').innerHTML = str;
this.fpsCounterLastPrint = now;
}
},
// Creates a new section. Call once at startup.
createSection: function createSection(number, name, drawColor) {
var sect = this.sections[number];
if (!sect) {
sect = {
count: 0,
name: name,
startTick: 0,
accumulatedTime: 0,
frametimes: [],
drawColor: drawColor
};
}
sect.name = name;
this.sections[number] = sect;
},
// Call at runtime whenever the code execution enter a given profiling section.
enterSection: function enterSection(sectionNumber) {
var sect = this.sections[sectionNumber];
// Handle recursive entering without getting confused (subsequent re-entering is ignored)
++sect.count;
if (sect.count == 1) sect.startTick = performance.now();
},
// Call at runtime when the code execution exits the given profiling section.
// Be sure to match each startSection(x) call with a call to endSection(x).
endSection: function endSection(sectionNumber) {
var sect = this.sections[sectionNumber];
--sect.count;
if (sect.count == 0) sect.accumulatedTime += performance.now() - sect.startTick;
},
// Called in the beginning of each main loop frame tick.
frameStart: function frameStart() {
this.currentFrameStartTime = performance.now();
this.fpsCounter();
},
// Called in the end of each main loop frame tick.
frameEnd: function frameEnd() {
// Aggregate total times spent in each section to memory store to wait until the next stats UI redraw period.
var totalTimeInSections = 0;
for (var i in this.sections) {
var sect = this.sections[i];
sect.frametimes[this.currentHistogramX] = sect.accumulatedTime;
totalTimeInSections += sect.accumulatedTime;
sect.accumulatedTime = 0;
}
var t = performance.now();
var cpuMainLoopDuration = t - this.currentFrameStartTime;
var durationBetweenFrameUpdates = t - this.previousFrameEndTime;
this.previousFrameEndTime = t;
this.timeSpentInMainloop[this.currentHistogramX] = cpuMainLoopDuration - totalTimeInSections;
this.timeSpentOutsideMainloop[this.currentHistogramX] = durationBetweenFrameUpdates - cpuMainLoopDuration;
this.currentHistogramX = (this.currentHistogramX + 1) % this.canvas.width;
// Redraw the UI if it is now time to do so.
if ((this.currentHistogramX - this.lastUiUpdateEndX + this.canvas.width) % this.canvas.width >= this.uiUpdateInterval) {
this.updateUi(this.lastUiUpdateEndX, this.currentHistogramX);
this.lastUiUpdateEndX = this.currentHistogramX;
}
},
// Installs the startup hooks and periodic UI update timer.
initialize: function initialize() {
// Create the UI display if it doesn't yet exist. If you want to customize the location/style of the cpuprofiler UI,
// you can manually create this beforehand.
cpuprofiler = document.getElementById('cpuprofiler');
if (!cpuprofiler) {
var div = document.createElement("div");
div.innerHTML = "<div style='border: 2px solid black; padding: 2px;'><button style='display:inline;' onclick='Module.noExitRuntime=false;Module.exit();'>Halt</button><span id='fpsResult'></span><canvas style='border: 1px solid black; margin-left:auto; margin-right:auto; display: block;' id='cpuprofiler_canvas' width='800px' height='200'></canvas><div id='cpuprofiler'></div>";
document.body.appendChild(div);
cpuprofiler = document.getElementById('cpuprofiler');
}
this.canvas = document.getElementById('cpuprofiler_canvas');
this.canvas.width = document.documentElement.clientWidth - 32;
this.drawContext = this.canvas.getContext('2d');
// if (document.getElementById('output')) document.getElementById('output').style.display = 'none';
// if (document.getElementById('status')) document.getElementById('status').style.display = 'none';
this.clearUi(0, this.canvas.width);
this.drawGraphLabels();
this.updateUi();
Module['preMainLoop'] = function cpuprofiler_frameStart() { emscriptenCpuProfiler.frameStart(); }
Module['postMainLoop'] = function cpuprofiler_frameEnd() { emscriptenCpuProfiler.frameEnd(); }
this.createSection(0, 'GL', '#FF00FF');
},
drawHorizontalLine: function drawHorizontalLine(startX, endX, pixelThickness, msecs) {
var height = msecs * this.canvas.height / this.verticalTimeScale;
this.drawContext.fillRect(startX,this.canvas.height - height, endX - startX, pixelThickness);
},
clearUi: function clearUi(startX, endX) {
// Background clear
this.drawContext.fillStyle="#324B4B";
this.drawContext.fillRect(startX, 0, endX - startX, this.canvas.height);
this.drawContext.fillStyle="#00FF00";
this.drawHorizontalLine(startX, endX, 1, 16.6666666);
this.drawContext.fillStyle="#FFFF00";
this.drawHorizontalLine(startX, endX, 1, 33.3333333);
},
drawGraphLabels: function drawGraphLabels() {
this.drawContext.fillStyle = "#C0C0C0";
this.drawContext.font = "bold 10px Arial";
this.drawContext.textAlign = "right";
this.drawContext.fillText("16.66... ms", this.canvas.width - 3, this.canvas.height - 16.6666 * this.canvas.height / this.verticalTimeScale - 3);
this.drawContext.fillText("33.33... ms", this.canvas.width - 3, this.canvas.height - 33.3333 * this.canvas.height / this.verticalTimeScale - 3);
},
drawBar: function drawBar(x) {
var scale = this.canvas.height / this.verticalTimeScale;
var y = this.canvas.height;
var h = this.timeSpentInMainloop[x] * scale;
y -= h;
this.drawContext.fillStyle = "#0000BB";
this.drawContext.fillRect(x, y, 1, h);
for (var i in this.sections) {
var sect = this.sections[i];
var h = sect.frametimes[x] * scale;
y -= h;
this.drawContext.fillStyle = sect.drawColor;
this.drawContext.fillRect(x, y, 1, h);
}
var h = this.timeSpentOutsideMainloop[x] * scale;
y -= h;
var fps60Limit = this.canvas.height - (16.666666666 + 1.0) * this.canvas.height / this.verticalTimeScale; // Be very lax, allow 1msec extra jitter.
var fps30Limit = this.canvas.height - (33.333333333 + 1.0) * this.canvas.height / this.verticalTimeScale; // Be very lax, allow 1msec extra jitter.
if (y < fps30Limit) this.drawContext.fillStyle = "#FF0000";
else if (y < fps60Limit) this.drawContext.fillStyle = "#FFFF00";
else this.drawContext.fillStyle = "#60A060";
this.drawContext.fillRect(x, y, 1, h);
},
// Main UI update/redraw entry point. Drawing occurs incrementally to touch as few pixels as possible and to cause the least impact to the overall performance
// while profiling.
updateUi: function updateUi(startX, endX) {
// Poll whether user as changed the browser window, and if so, resize the profiler window and redraw it.
if (this.canvas.width != document.documentElement.clientWidth - 32) {
this.canvas.width = document.documentElement.clientWidth - 32;
if (this.timeSpentInMainloop.length > this.canvas.width) this.timeSpentInMainloop.length = this.canvas.width;
if (this.timeSpentOutsideMainloop.length > this.canvas.width) this.timeSpentOutsideMainloop.length = this.canvas.width;
if (this.lastUiUpdateEndX >= this.canvas.width) this.lastUiUpdateEndX = 0;
if (this.currentHistogramX >= this.canvas.width) this.currentHistogramX = 0;
for (var i in this.sections) {
var sect = this.sections[i];
if (sect.frametimes.length > this.canvas.width) sect.frametimes.length = this.canvas.width;
}
this.clearUi(0, this.canvas.width);
this.drawGraphLabels();
startX = 0; // Full redraw all columns.
}
var clearDistance = this.uiUpdateInterval * 2 + 1;
var clearStart = endX + clearDistance;
var clearEnd = clearStart + this.uiUpdateInterval;
if (endX < startX) {
this.clearUi(clearStart, clearEnd);
this.clearUi(0, endX + clearDistance+this.uiUpdateInterval);
this.drawGraphLabels();
} else {
this.clearUi(clearStart, clearEnd);
}
if (endX < startX) {
for (var x = startX; x < this.canvas.width; ++x) this.drawBar(x);
startX = 0;
}
for (var x = startX; x < endX; ++x) this.drawBar(x);
}
};
// Backwards compatibility with previously compiled code. Don't call this anymore!
function cpuprofiler_add_hooks() { emscriptenCpuProfiler.initialize(); }
if (typeof Module !== 'undefined' && typeof document !== 'undefined') emscriptenCpuProfiler.initialize();