You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
8.1 KiB
183 lines
8.1 KiB
(function (global) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Performance visualization panel.
|
|
* @constructor
|
|
* @param {InstanceType<typeof PWPerfSampler>} sampler
|
|
*/
|
|
function PerfPanel(sampler) {
|
|
this.sampler = sampler;
|
|
this.el = null;
|
|
this.charts = {};
|
|
this._stripTimer = 0;
|
|
}
|
|
|
|
PerfPanel.prototype.mount = function () {
|
|
var self = this;
|
|
var root = document.createElement('div');
|
|
root.className = 'pw-panel-inner';
|
|
root.style.height = '100%';
|
|
root.style.display = 'flex';
|
|
root.style.flexDirection = 'column';
|
|
root.innerHTML =
|
|
'<div class="pw-panel-head">' +
|
|
'<span class="pw-panel-title">性能</span>' +
|
|
'<div class="pw-panel-tools">' +
|
|
'<button type="button" id="pw-perf-pause" class="pw-btn">暂停</button>' +
|
|
'<button type="button" id="pw-perf-clear" class="pw-btn">清空</button>' +
|
|
'<button type="button" id="pw-perf-mark" class="pw-btn">打标记</button>' +
|
|
'<input id="pw-perf-mark-text" class="pw-input" placeholder="标记" style="width:80px" />' +
|
|
'<button type="button" id="pw-perf-csv" class="pw-btn">导出CSV</button>' +
|
|
'<select id="pw-perf-window" class="pw-select">' +
|
|
'<option value="300">窗口 60秒</option>' +
|
|
'<option value="1500">窗口 5分钟</option>' +
|
|
'</select>' +
|
|
'<select id="pw-perf-interval" class="pw-select">' +
|
|
'<option value="100">采样 100ms</option>' +
|
|
'<option value="200" selected>采样 200ms</option>' +
|
|
'<option value="500">采样 500ms</option>' +
|
|
'</select>' +
|
|
'</div></div>' +
|
|
'<div class="pw-panel-body">' +
|
|
'<div id="pw-perf-summary" class="pw-summary"></div>' +
|
|
'<div id="pw-perf-markers" class="pw-muted" style="margin-bottom:6px;max-height:36px;overflow:auto"></div>' +
|
|
'<div class="pw-charts" id="pw-perf-charts"></div>' +
|
|
'</div>';
|
|
this.el = root;
|
|
|
|
var defs = [
|
|
{ key: 'fps', title: '帧率 FPS', color: '#89d185' },
|
|
{ key: 'frame', title: '帧耗时 (ms)', color: '#3794ff' },
|
|
{ key: 'logic', title: '逻辑 (ms)', color: '#4ec9b0' },
|
|
{ key: 'render', title: '渲染 (ms)', color: '#f0a020' },
|
|
{ key: 'present', title: '呈现 (ms)', color: '#c586c0' },
|
|
{ key: 'draws', title: 'DrawCall', color: '#dcdcaa' },
|
|
{ key: 'tricount', title: '三角面', color: '#9cdcfe' },
|
|
{ key: 'textureMemory', title: '纹理显存 (MB)', color: '#f14c4c' },
|
|
{ key: 'bufferMemory', title: '缓冲显存 (MB)', color: '#ce9178' },
|
|
{ key: 'jsHeap', title: 'JS 堆 (MB)', color: '#b5cea8' },
|
|
{ key: 'assetCount', title: '资源数量', color: '#569cd6' },
|
|
];
|
|
var chartsEl = root.querySelector('#pw-perf-charts');
|
|
for (var i = 0; i < defs.length; i++) {
|
|
var d = defs[i];
|
|
var card = document.createElement('div');
|
|
card.className = 'pw-chart-card';
|
|
card.innerHTML = '<h4>' + d.title + '</h4><canvas data-key="' + d.key + '"></canvas>';
|
|
chartsEl.appendChild(card);
|
|
var canvas = card.querySelector('canvas');
|
|
var chart = new global.PWRingChart(canvas);
|
|
chart.color = d.color;
|
|
chart.fill = hexToRgba(d.color, 0.15);
|
|
this.charts[d.key] = chart;
|
|
}
|
|
|
|
root.querySelector('#pw-perf-pause').addEventListener('click', function (ev) {
|
|
self.sampler.paused = !self.sampler.paused;
|
|
ev.currentTarget.textContent = self.sampler.paused ? '继续' : '暂停';
|
|
ev.currentTarget.classList.toggle('active', self.sampler.paused);
|
|
});
|
|
root.querySelector('#pw-perf-clear').addEventListener('click', function () {
|
|
self.sampler.clear();
|
|
self.render();
|
|
});
|
|
root.querySelector('#pw-perf-mark').addEventListener('click', function () {
|
|
var text = root.querySelector('#pw-perf-mark-text').value;
|
|
self.sampler.userMark(text);
|
|
self.render();
|
|
});
|
|
root.querySelector('#pw-perf-csv').addEventListener('click', function () {
|
|
var csv = self.sampler.toCsv();
|
|
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
var a = document.createElement('a');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = 'workbench-perf.csv';
|
|
a.click();
|
|
URL.revokeObjectURL(a.href);
|
|
});
|
|
root.querySelector('#pw-perf-window').addEventListener('change', function (ev) {
|
|
self.sampler.setCapacity(parseInt(ev.target.value, 10) || 300);
|
|
});
|
|
root.querySelector('#pw-perf-interval').addEventListener('change', function (ev) {
|
|
self.sampler.setIntervalMs(parseInt(ev.target.value, 10) || 200);
|
|
});
|
|
|
|
document.addEventListener('keydown', function (ev) {
|
|
if (ev.altKey && (ev.key === 'm' || ev.key === 'M')) {
|
|
self.sampler.userMark('');
|
|
self.render();
|
|
}
|
|
});
|
|
|
|
this.sampler.onSample(function () { self.render(); });
|
|
return root;
|
|
};
|
|
|
|
PerfPanel.prototype.startStrip = function () {
|
|
// 布局 v2 使用停靠页签,不再需要底栏摘要条
|
|
};
|
|
|
|
PerfPanel.prototype.render = function () {
|
|
if (!this.el) return;
|
|
var s = this.sampler;
|
|
var latest = s.latest();
|
|
var sum = this.el.querySelector('#pw-perf-summary');
|
|
if (sum) {
|
|
var fpsArr = s.series.fps;
|
|
var avg = average(fpsArr);
|
|
var min = minOf(fpsArr);
|
|
var max = maxOf(fpsArr);
|
|
var low = 0;
|
|
for (var i = 0; i < fpsArr.length; i++) if (fpsArr[i] < 30) low++;
|
|
var lowPct = fpsArr.length ? Math.round((low / fpsArr.length) * 100) : 0;
|
|
sum.innerHTML =
|
|
'<span>帧率 <b>' + Math.round(latest.fps) + '</b> 均 ' + Math.round(avg) + ' 最低 ' + Math.round(min) + ' 最高 ' + Math.round(max) + '</span>' +
|
|
'<span><30帧占比 <b>' + lowPct + '%</b></span>' +
|
|
'<span>DrawCall <b>' + Math.round(latest.draws) + '</b></span>' +
|
|
'<span>帧耗时 <b>' + (Math.round(latest.frame * 10) / 10) + 'ms</b></span>' +
|
|
'<span>纹理 <b>' + (Math.round(latest.textureMemory * 100) / 100) + 'MB</b></span>' +
|
|
'<span>缓冲 <b>' + (Math.round(latest.bufferMemory * 100) / 100) + 'MB</b></span>' +
|
|
'<span>堆内存 <b>' + (Math.round(latest.jsHeap * 100) / 100) + 'MB</b></span>';
|
|
}
|
|
var markEl = this.el.querySelector('#pw-perf-markers');
|
|
if (markEl) {
|
|
var marks = s.markers.slice(-8);
|
|
markEl.textContent = marks.length
|
|
? ('标记:' + marks.map(function (m) { return m.label; }).join(' | '))
|
|
: '标记:(自动 scene/bundle + Alt+M 手动)';
|
|
}
|
|
for (var key in this.charts) {
|
|
if (!Object.prototype.hasOwnProperty.call(this.charts, key)) continue;
|
|
this.charts[key].draw(s.series[key], s.markers, s.times);
|
|
}
|
|
};
|
|
|
|
function average(arr) {
|
|
if (!arr.length) return 0;
|
|
var t = 0;
|
|
for (var i = 0; i < arr.length; i++) t += arr[i];
|
|
return t / arr.length;
|
|
}
|
|
function minOf(arr) {
|
|
if (!arr.length) return 0;
|
|
var m = arr[0];
|
|
for (var i = 1; i < arr.length; i++) if (arr[i] < m) m = arr[i];
|
|
return m;
|
|
}
|
|
function maxOf(arr) {
|
|
if (!arr.length) return 0;
|
|
var m = arr[0];
|
|
for (var i = 1; i < arr.length; i++) if (arr[i] > m) m = arr[i];
|
|
return m;
|
|
}
|
|
function hexToRgba(hex, a) {
|
|
var h = hex.replace('#', '');
|
|
var r = parseInt(h.slice(0, 2), 16);
|
|
var g = parseInt(h.slice(2, 4), 16);
|
|
var b = parseInt(h.slice(4, 6), 16);
|
|
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
|
|
}
|
|
|
|
global.PWPerfPanel = PerfPanel;
|
|
})(window);
|
|
|