(function (global) { 'use strict'; /** * Performance visualization panel. * @constructor * @param {InstanceType} 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 = '
' + '性能' + '
' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '
' + '
'; 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 = '

' + d.title + '

'; 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 = '帧率 ' + Math.round(latest.fps) + ' 均 ' + Math.round(avg) + ' 最低 ' + Math.round(min) + ' 最高 ' + Math.round(max) + '' + '<30帧占比 ' + lowPct + '%' + 'DrawCall ' + Math.round(latest.draws) + '' + '帧耗时 ' + (Math.round(latest.frame * 10) / 10) + 'ms' + '纹理 ' + (Math.round(latest.textureMemory * 100) / 100) + 'MB' + '缓冲 ' + (Math.round(latest.bufferMemory * 100) / 100) + 'MB' + '堆内存 ' + (Math.round(latest.jsHeap * 100) / 100) + 'MB'; } 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);