Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/cpuprofiler.js
4128 views
1
/**
2
* @license
3
* Copyright 2015 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
// cpuprofiler.js is an interactive CPU execution profiler which measures the
8
// time spent in executing code that utilizes requestAnimationFrame(),
9
// setTimeout() and/or setInterval() handlers to run.
10
11
// performance.now() might get faked later (this is done in the openwebgames.com
12
// test harness), so save the real one for cpu profiler.
13
// However, in Safari, assigning to the performance object will mysteriously
14
// vanish in other imported .js <script>, so for that, replace the whole object.
15
// That doesn't work for Chrome in turn, so need to resort to user agent
16
// sniffing.. (sad :/)
17
if (!performance.realNow) {
18
var isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
19
if (isSafari) {
20
var realPerformance = performance;
21
performance = {
22
realNow: () => realPerformance.now(),
23
now: () => realPerformance.now()
24
};
25
} else {
26
performance.realNow = performance.now;
27
}
28
}
29
30
var emscriptenCpuProfiler = {
31
// UI update interval in milliseconds.
32
uiUpdateInterval: 1,
33
34
// Specifies the pixel column where the previous UI update finished at.
35
// (current "draw cursor" position next draw will resume from)
36
lastUiUpdateEndX: 0,
37
38
// An array which stores samples of msec durations spent in the emscripten
39
// main loop (emscripten_set_main_loop).
40
// Carries # samples equal to the pixel width of the profiler display window,
41
// and old samples are erased in a rolling window fashion.
42
timeSpentInMainloop: [],
43
44
// Similar to 'timeSpentInMainloop', except this stores msec durations outside
45
// the emscripten main loop callback. (either idle CPU time, browser
46
// processing, or something else)
47
timeSpentOutsideMainloop: [],
48
49
// Specifies the sample coordinate into the timeSpentIn/OutsideMainloop arrays
50
// that is currently being populated.
51
currentHistogramX: 0,
52
53
// Wallclock time denoting when the currently executing main loop callback
54
// tick began.
55
currentFrameStartTime: 0,
56
57
// Wallclock time denoting when the previously executing main loop was
58
// finished.
59
previousFrameEndTime: 0,
60
61
// The total time spent in a frame can be further subdivided down into
62
// 'sections'. This array stores info structures representing each section.
63
sections: [],
64
65
// The 2D canvas DOM element to which the CPU profiler graph is rendered to.
66
canvas: null,
67
68
// The 2D drawing context on the canvas.
69
drawContext: null,
70
71
// How many milliseconds in total to fit vertically into the displayed CPU
72
// profiler window? Frametimes longer than this are out the graph and not
73
// visible.
74
verticalTimeScale: 40,
75
76
// History of wallclock times of N most recent frame times. Used to estimate current FPS.
77
fpsCounterTicks: [],
78
79
// When was the FPS UI display last updated?
80
fpsCounterLastPrint: performance.realNow(),
81
82
fpsCounterNumMostRecentFrames: 120,
83
84
fpsCounterUpdateInterval: 2000, // msecs
85
86
// Used to detect recursive entries to the main loop, which can happen in
87
// certain complex cases, e.g. if not using rAF to tick rendering to the
88
// canvas.
89
insideMainLoopRecursionCounter: 0,
90
91
// fpsCounter() is called once per frame to record an executed frame time, and
92
// to periodically update the FPS counter display.
93
fpsCounter() {
94
// Record the new frame time sample, and prune the history to K most recent frames.
95
var now = performance.realNow();
96
if (this.fpsCounterTicks.length < this.fpsCounterNumMostRecentFrames) {
97
this.fpsCounterTicks.push(now);
98
} else {
99
for (var i = 0; i < this.fpsCounterTicks.length-1; ++i) this.fpsCounterTicks[i] = this.fpsCounterTicks[i+1];
100
this.fpsCounterTicks[this.fpsCounterTicks.length-1] = now;
101
}
102
103
if (now - this.fpsCounterLastPrint > this.fpsCounterUpdateInterval) {
104
var fps = ((this.fpsCounterTicks.length - 1) * 1000.0 / (this.fpsCounterTicks[this.fpsCounterTicks.length - 1] - this.fpsCounterTicks[0]));
105
var totalDt = 0;
106
var totalRAFDt = 0;
107
var minDt = 99999999;
108
var maxDt = 0;
109
var nSamples = 0;
110
111
var numSamplesToAccount = Math.min(this.timeSpentInMainloop.length, 120);
112
var startX = (this.currentHistogramX - numSamplesToAccount + this.canvas.width) % this.canvas.width;
113
for (var i = 0; i < numSamplesToAccount; ++i) {
114
var x = (startX + i) % this.canvas.width;
115
var dt = this.timeSpentInMainloop[x] + this.timeSpentOutsideMainloop[x];
116
totalRAFDt += this.timeSpentInMainloop[x];
117
if (dt > 0) ++nSamples;
118
totalDt += dt;
119
minDt = Math.min(minDt, dt);
120
maxDt = Math.max(maxDt, dt);
121
}
122
var avgDt = totalDt / nSamples;
123
var avgFps = 1000.0 / avgDt;
124
var dtVariance = 0;
125
for (var i = 1; i < numSamplesToAccount; ++i) {
126
var x = (startX + i) % this.canvas.width;
127
var dt = this.timeSpentInMainloop[x] + this.timeSpentOutsideMainloop[x];
128
var d = dt - avgDt;
129
dtVariance += d*d;
130
}
131
dtVariance /= nSamples;
132
133
var asmJSLoad = totalRAFDt * 100.0 / totalDt;
134
135
// Compute the overhead added by WebGL:
136
var hotGL = this.sections[0];
137
var coldGL = this.sections[1];
138
var webGLMSecsInsideMainLoop = (hotGL ? hotGL.accumulatedFrameTimeInsideMainLoop(startX, numSamplesToAccount) : 0) + (coldGL ? coldGL.accumulatedFrameTimeInsideMainLoop(startX, numSamplesToAccount) : 0);
139
var webGLMSecsOutsideMainLoop = (hotGL ? hotGL.accumulatedFrameTimeOutsideMainLoop(startX, numSamplesToAccount) : 0) + (coldGL ? coldGL.accumulatedFrameTimeOutsideMainLoop(startX, numSamplesToAccount) : 0);
140
var webGLMSecs = webGLMSecsInsideMainLoop + webGLMSecsOutsideMainLoop;
141
142
var setIntervalSection = this.sections[2];
143
var setTimeoutSection = this.sections[3];
144
var totalCPUMsecs = totalRAFDt + setIntervalSection.accumulatedFrameTimeOutsideMainLoop(startX, numSamplesToAccount) + setTimeoutSection.accumulatedFrameTimeOutsideMainLoop(startX, numSamplesToAccount);
145
146
// Update full FPS counter
147
var str = 'Last FPS: ' + fps.toFixed(2) + ', avg FPS:' + avgFps.toFixed(2) + ', min/avg/max dt: '
148
+ minDt.toFixed(2) + '/' + avgDt.toFixed(2) + '/' + maxDt.toFixed(2) + ' msecs, dt variance: ' + dtVariance.toFixed(3)
149
+ ', JavaScript CPU load: ' + asmJSLoad.toFixed(2) + '%';
150
151
if (hotGL || coldGL) {
152
str += '. WebGL CPU load: ' + (webGLMSecs * 100.0 / totalDt).toFixed(2) + '% (' + (webGLMSecs * 100.0 / totalCPUMsecs).toFixed(2) + '% of all CPU work)';
153
}
154
document.getElementById('fpsResult').innerHTML = str;
155
156
// Update lite FPS counter
157
if (this.fpsOverlay1) {
158
this.fpsOverlay1.innerText = fps.toFixed(1) + ' (' + asmJSLoad.toFixed(1) + '%)';
159
this.fpsOverlay1.style.color = fps >= 30 ? 'lightgreen' : fps >= 15 ? 'yellow' : 'red';
160
this.fpsOverlay2.innerText = minDt.toFixed(2) + '/' + avgDt.toFixed(2) + '/' + maxDt.toFixed(2) + ' ms';
161
}
162
163
this.fpsCounterLastPrint = now;
164
}
165
},
166
167
// Creates a new section. Call once at startup.
168
createSection(number, name, drawColor, traceable) {
169
while (this.sections.length <= number) {
170
this.sections.push(null); // Keep an array structure.
171
}
172
var sect = this.sections[number];
173
sect ||= {
174
count: 0,
175
name,
176
startTick: 0,
177
accumulatedTimeInsideMainLoop: 0,
178
accumulatedTimeOutsideMainLoop: 0,
179
frametimesInsideMainLoop: [],
180
frametimesOutsideMainLoop: [],
181
drawColor,
182
traceable,
183
accumulatedFrameTimeInsideMainLoop: function(startX, numSamples) {
184
var total = 0;
185
numSamples = Math.min(numSamples, this.frametimesInsideMainLoop.length);
186
for (var i = 0; i < numSamples; ++i) {
187
var x = (startX + i) % this.frametimesInsideMainLoop.length;
188
if (this.frametimesInsideMainLoop[x]) total += this.frametimesInsideMainLoop[x];
189
}
190
return total;
191
},
192
accumulatedFrameTimeOutsideMainLoop: function(startX, numSamples) {
193
var total = 0;
194
numSamples = Math.min(numSamples, this.frametimesInsideMainLoop.length);
195
for (var i = 0; i < numSamples; ++i) {
196
var x = (startX + i) % this.frametimesInsideMainLoop.length;
197
if (this.frametimesOutsideMainLoop[x]) total += this.frametimesOutsideMainLoop[x];
198
}
199
return total;
200
}
201
};
202
sect.name = name;
203
this.sections[number] = sect;
204
},
205
206
// Call at runtime whenever the code execution enter a given profiling section.
207
enterSection(sectionNumber) {
208
var sect = this.sections[sectionNumber];
209
// Handle recursive entering without getting confused (subsequent re-entering is ignored)
210
++sect.count;
211
if (sect.count == 1) sect.startTick = performance.realNow();
212
},
213
214
// Call at runtime when the code execution exits the given profiling section.
215
// Be sure to match each startSection(x) call with a call to endSection(x).
216
endSection(sectionNumber) {
217
var sect = this.sections[sectionNumber];
218
--sect.count;
219
if (sect.count == 0) {
220
var timeInSection = performance.realNow() - sect.startTick;
221
if (sect.traceable && timeInSection > this.logWebGLCallsSlowerThan) {
222
var funcs = new Error().stack.toString().split('\n');
223
var cs = '';
224
for (var i = 2; i < 5 && i < funcs.length; ++i) {
225
if (i != 2) cs += ' <- ';
226
var fn = funcs[i];
227
var at = fn.indexOf('@');
228
if (at != -1) fn = fn.slice(0, at);
229
fn = fn.trim();
230
cs += '"' + fn + '"';
231
}
232
233
console.error('Trace: at t=' + performance.realNow().toFixed(1) + ', section "' + sect.name + '" called via ' + cs + ' took ' + timeInSection.toFixed(2) + ' msecs!');
234
}
235
if (this.insideMainLoopRecursionCounter) {
236
sect.accumulatedTimeInsideMainLoop += timeInSection;
237
} else {
238
sect.accumulatedTimeOutsideMainLoop += timeInSection;
239
}
240
}
241
},
242
243
// Called in the beginning of each main loop frame tick.
244
frameStart() {
245
this.insideMainLoopRecursionCounter++;
246
if (this.insideMainLoopRecursionCounter == 1) {
247
this.currentFrameStartTime = performance.realNow();
248
this.fpsCounter();
249
}
250
},
251
252
// Called in the end of each main loop frame tick.
253
frameEnd() {
254
this.insideMainLoopRecursionCounter--;
255
if (this.insideMainLoopRecursionCounter != 0) return;
256
257
// Aggregate total times spent in each section to memory store to wait until the next stats UI redraw period.
258
for (var i = 0; i < this.sections.length; ++i) {
259
var sect = this.sections[i];
260
if (!sect) continue;
261
sect.frametimesInsideMainLoop[this.currentHistogramX] = sect.accumulatedTimeInsideMainLoop;
262
sect.frametimesOutsideMainLoop[this.currentHistogramX] = sect.accumulatedTimeOutsideMainLoop;
263
sect.accumulatedTimeInsideMainLoop = 0;
264
sect.accumulatedTimeOutsideMainLoop = 0;
265
}
266
267
var t = performance.realNow();
268
var cpuMainLoopDuration = t - this.currentFrameStartTime;
269
var durationBetweenFrameUpdates = t - this.previousFrameEndTime;
270
this.previousFrameEndTime = t;
271
272
this.timeSpentInMainloop[this.currentHistogramX] = cpuMainLoopDuration;
273
this.timeSpentOutsideMainloop[this.currentHistogramX] = durationBetweenFrameUpdates - cpuMainLoopDuration;
274
275
this.currentHistogramX = (this.currentHistogramX + 1) % this.canvas.width;
276
// Redraw the UI if it is now time to do so.
277
if ((this.currentHistogramX - this.lastUiUpdateEndX + this.canvas.width) % this.canvas.width >= this.uiUpdateInterval) {
278
this.updateUi(this.lastUiUpdateEndX, this.currentHistogramX);
279
this.lastUiUpdateEndX = this.currentHistogramX;
280
}
281
},
282
283
colorBackground: '#324B4B',
284
color60FpsBar: '#00FF00',
285
color30FpsBar: '#FFFF00',
286
colorTextLabel: '#C0C0C0',
287
colorCpuTimeSpentInUserCode: '#0000BB',
288
colorWorseThan30FPS: '#A06060',
289
colorWorseThan60FPS: '#A0A030',
290
color60FPS: '#40A040',
291
colorHotGLFunction: '#FF00FF',
292
colorColdGLFunction: '#0099CC',
293
colorSetIntervalSection: '#FF0000',
294
colorSetTimeoutSection: '#00FF00',
295
296
hotGLFunctions: ['activeTexture', 'bindBuffer', 'bindFramebuffer',
297
'bindTexture', 'blendColor', 'blendEquation', 'blendEquationSeparate',
298
'blendFunc', 'blendFuncSeparate', 'bufferSubData', 'clear', 'clearColor',
299
'clearDepth', 'clearStencil', 'colorMask', 'compressedTexSubImage2D',
300
'copyTexSubImage2D', 'cullFace', 'depthFunc', 'depthMask', 'depthRange',
301
'disable', 'disableVertexAttribArray', 'drawArrays', 'drawArraysInstanced',
302
'drawElements', 'drawElementsInstanced', 'enable',
303
'enableVertexAttribArray', 'frontFace', 'lineWidth', 'pixelStorei',
304
'polygonOffset', 'sampleCoverage', 'scissor', 'stencilFunc',
305
'stencilFuncSeparate', 'stencilMask', 'stencilMaskSeparate', 'stencilOp',
306
'stencilOpSeparate', 'texSubImage2D', 'useProgram', 'viewport',
307
'beginQuery', 'endQuery', 'bindVertexArray', 'drawBuffers',
308
'copyBufferSubData', 'blitFramebuffer', 'invalidateFramebuffer',
309
'invalidateSubFramebuffer', 'readBuffer', 'texSubImage3D',
310
'copyTexSubImage3D', 'compressedTexSubImage3D', 'vertexAttribDivisor',
311
'drawRangeElements', 'clearBufferiv', 'clearBufferuiv', 'clearBufferfv',
312
'clearBufferfi', 'bindSampler', 'bindTransformFeedback',
313
'beginTransformFeedback', 'endTransformFeedback',
314
'transformFeedbackVaryings', 'pauseTransformFeedback',
315
'resumeTransformFeedback', 'bindBufferBase', 'bindBufferRange',
316
'uniformBlockBinding'],
317
318
hookedWebGLContexts: [],
319
logWebGLCallsSlowerThan: Infinity,
320
321
toggleHelpTextVisible() {
322
var help = document.getElementById('cpuprofiler_help_text');
323
if (help.style) help.style.display = (help.style.display == 'none') ? 'block' : 'none';
324
},
325
326
// Installs the startup hooks and periodic UI update timer.
327
initialize() {
328
// Hook into requestAnimationFrame function to grab animation even if
329
// application did not use emscripten_set_main_loop() to drive animation,
330
// but e.g. used its own function that performs requestAnimationFrame().
331
if (!window.realRequestAnimationFrame) {
332
window.realRequestAnimationFrame = window.requestAnimationFrame;
333
window.requestAnimationFrame = (cb) => {
334
function hookedCb(p) {
335
emscriptenCpuProfiler.frameStart();
336
cb(performance.now());
337
emscriptenCpuProfiler.frameEnd();
338
}
339
return window.realRequestAnimationFrame(hookedCb);
340
}
341
}
342
343
// Create the UI display if it doesn't yet exist. If you want to customize
344
// the location/style of the cpuprofiler UI, you can manually create this
345
// beforehand.
346
var cpuprofiler = document.getElementById('cpuprofiler');
347
if (!cpuprofiler) {
348
var css = '.colorbox { border: solid 1px black; margin-left: 10px; margin-right: 3px; display: inline-block; width: 20px; height: 10px; } .hastooltip:hover .tooltip { display: block; } .tooltip { display: none; background: #FFFFFF; margin-left: 28px; padding: 5px; position: absolute; z-index: 1000; width:200px; } .hastooltip { margin:0px; }';
349
var style = document.createElement('style');
350
style.type = 'text/css';
351
style.appendChild(document.createTextNode(css));
352
document.head.appendChild(style);
353
354
// Users can provide a container element where to place this if desired.
355
var div = document.getElementById('cpuprofiler_container');
356
if (!div) {
357
div = document.createElement("div");
358
document.body.appendChild(div);
359
360
// It is common to set 'overflow: hidden;' on canvas pages that do
361
// WebGL. When CpuProfiler is being used, there will be a long block of
362
// text on the page, so force-enable scrolling.
363
document.body.style.overflow = '';
364
}
365
var helpText = "<div style='margin-left: 10px;'>Color Legend:";
366
helpText += "<div class='colorbox' style='background-color: " + this.colorCpuTimeSpentInUserCode + ";'></div>Main Loop (C/C++) Code"
367
helpText += "<div class='colorbox' style='background-color: " + this.colorHotGLFunction + ";'></div>Hot WebGL Calls"
368
helpText += "<div class='colorbox' style='background-color: " + this.colorColdGLFunction + ";'></div>Cold WebGL Calls"
369
helpText += "<div class='colorbox' style='background-color: " + this.color60FPS + ";'></div>Browser Execution (&ge; 60fps)"
370
helpText += "<div class='colorbox' style='background-color: " + this.colorWorseThan60FPS + ";'></div>Browser Execution (30-60fps)"
371
helpText += "<div class='colorbox' style='background-color: " + this.colorWorseThan30FPS + ";'></div>Browser Execution (&lt; 30fps)"
372
helpText += "<div class='colorbox' style='background-color: " + this.colorSetIntervalSection + ";'></div>setInterval()"
373
helpText += "<div class='colorbox' style='background-color: " + this.colorSetTimeoutSection + ";'></div>setTimeout()"
374
helpText += "</div>";
375
helpText += "<div id='cpuprofiler_help_text' style='display:none; margin-top: 20px; margin-left: 10px;'>"
376
helpText += "<p>cpuprofiler.js is an interactive CPU execution profiler which measures the time spent in executing code that utilizes requestAnimationFrame(), setTimeout() and/or setInterval() handlers to run. Each one pixel column in the above graph denotes a single executed application frame tick. The vertical axis represents in millisecond units the time taken to render a frame. Use this tool to interactively locate stuttering related events, and then use other profiling tools (<a href='https://developer.mozilla.org/en-US/docs/Tools/Performance'>Firefox profiler</a>, <a href='https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler'>geckoprofiler</a>) to identify their cause."
377
helpText += "<p>The header line above the graph prints out timing statistics:"
378
helpText += "<ul><li><b>Last FPS:</b> Displays the current FPS measured by averaging across " + this.fpsCounterNumMostRecentFrames + " most recently rendered frames.";
379
helpText += "<li><b>Avg FPS:</b> Displays the total FPS measured by averaging across the whole visible graph, i.e. <span id='ntotalframes'>" + (document.documentElement.clientWidth - 32) + "</span> most recently rendered frames.";
380
helpText += "<li><b>min/avg/max dt:</b> Displays the minimum, average and maximum durations that an application frame took overall, across the visible graph. These numbers include the time the browser was idle.";
381
helpText += "<li><b>dt variance:</b> Computes the amount of <a href='https://en.wikipedia.org/wiki/Variance'>statistical variance</a> in the overall frame durations.";
382
helpText += "<li><b>JavaScript CPU load:</b> This field estimates the amount of time the CPU was busy executing user code (requestAnimationFrame, setTimeout and setInterval handlers), with the simple assumption that the browser would be idle the remaining time.";
383
helpText += "<li><b>WebGL CPU load:</b> This field estimates the amount of time the CPU was busy running code inside the browser WebGL API. The value in parentheses shows the ratio of time that WebGL consumes of all per-frame CPU work.";
384
helpText += "</ul>Use the <span style='border: solid 1px #909090;'>Halt</span> button to abort page execution (Emscripten only). ";
385
helpText += "<br>Press the <span style='border: solid 1px #909090;'>Profile WebGL</span> button to toggle the profiling of WebGL CPU overhead. When the button background is displayed in green, WebGL CPU profiling is active. This profiling mode has some overhead by itself, so when recording profiles with other tools, prefer to leave this disabled.";
386
helpText += "<br>With the <span style='border: solid 1px #909090;'>Trace Calls</span> option, you can log WebGL and setInterval()/setTimeout() operations that take a long time to finish. These are typically cold operations like shader compilation or large reallocating buffer uploads, or other long event-based computation. For this option to be able to trace WebGL calls, the option Profile WebGL must also be enabled. The trace results appear in the web page console.";
387
helpText += "<p>The different colors on the graph have the following meaning:";
388
helpText += "<br><div class='colorbox' style='background-color: " + this.colorCpuTimeSpentInUserCode + ";'></div><b>Main Loop (C/C++) Code</b>: This is the time spent executing application JavaScript code inside the main loop event handler, generally via requestAnimationFrame().";
389
helpText += "<br><div class='colorbox' style='background-color: " + this.colorHotGLFunction + ";'></div><b>Hot WebGL Calls</b>: This measures the CPU time spent in running common per-frame rendering related WebGL calls: <div style='margin-left: 100px; margin-top: 10px; max-width: 800px; font-size: 12px;'>" + this.hotGLFunctions.join(', ') + ', uniform* and vertexAttrib*.</div>';
390
helpText += "<br><div class='colorbox' style='background-color: " + this.colorColdGLFunction + ";'></div><b>Cold WebGL Calls</b>: This shows the CPU time spent in all the remaining WebGL functions that are not considered 'hot' (not in the above list).";
391
helpText += "<br><div class='colorbox' style='background-color: " + this.color60FPS + ";'></div><b>Browser Execution (&ge; 60fps)</b>: This is the time taken by browser that falls outside the tracked requestAnimationFrame(), setTimeout() and/or setInterval() handlers. If the page is running at 60fps, the browser time will be drawn with this color. Likely the browser was idle waiting for vsync.";
392
helpText += "<br><div class='colorbox' style='background-color: " + this.colorWorseThan60FPS + ";'></div><b>Browser Execution (30-60fps)</b>: This is the same as above, except that when 60fps is not reached, the browser time is drawn in this color.";
393
helpText += "<br><div class='colorbox' style='background-color: " + this.colorWorseThan30FPS + ";'></div><b>Browser Execution (&lt; 30fps)</b>: Same as above, except that the frame completed slowly, so the browser time is drawn in this color. Long spikes of this color indicate that the browser is running some internal operations (e.g. garbage collection) that can cause stuttering.";
394
helpText += "<br><div class='colorbox' style='background-color: " + this.colorSetIntervalSection + ";'></div><b>setInterval()</b>: Specifies the amount of time spent in executing user code in setInterval() handlers.";
395
helpText += "<br><div class='colorbox' style='background-color: " + this.colorSetTimeoutSection + ";'></div><b>setTimeout()</b>: Specifies the amount of time spent in executing user code in setTimeout() handlers.";
396
helpText += "<p>For bugs and suggestions, visit <a href='https://github.com/emscripten-core/emscripten/issues'>Emscripten bug tracker</a>.";
397
helpText += "</div>";
398
399
div.innerHTML = "<div style='color: black; border: 2px solid black; padding: 2px; margin-bottom: 10px; margin-left: 5px; margin-right: 5px; margin-top: 5px; background-color: #F0F0FF;'><span style='margin-left: 10px;'><b>Cpu Profiler</b><sup style='cursor: pointer;' onclick='emscriptenCpuProfiler.toggleHelpTextVisible();'>[?]</sup></span> <button style='display:inline; border: solid 1px #ADADAD; margin: 2px; background-color: #E1E1E1;' onclick='noExitRuntime=false;Module.exit();'>Halt</button><button id='toggle_webgl_profile' style='display:inline; border: solid 1px #ADADAD; margin: 2px; background-color: #E1E1E1;' onclick='emscriptenCpuProfiler.toggleHookWebGL()'>Profile WebGL</button><button id='toggle_webgl_trace' style='display:inline; border: solid 1px #ADADAD; margin: 2px; background-color: #E1E1E1;' onclick='emscriptenCpuProfiler.toggleTraceWebGL()'>Trace Calls</button> slower than <input id='trace_limit' oninput='emscriptenCpuProfiler.disableTraceWebGL();' style='width:40px;' value='100'></input> msecs. <span id='fpsResult' style='margin-left: 5px;'></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>" + helpText;
400
document.getElementById('trace_limit').onkeydown = (e) => {
401
if (e.which == 13 || e.keycode == 13) {
402
emscriptenCpuProfiler.enableTraceWebGL();
403
} else {
404
emscriptenCpuProfiler.disableTraceWebGL();
405
}
406
};
407
cpuprofiler = document.getElementById('cpuprofiler');
408
409
if (location.search.includes('expandhelp')) this.toggleHelpTextVisible();
410
}
411
412
this.canvas = document.getElementById('cpuprofiler_canvas');
413
this.canvas.width = document.documentElement.clientWidth - 32;
414
this.drawContext = this.canvas.getContext('2d');
415
416
var webglCanvas = document.getElementById('canvas') || document.querySelector('canvas');
417
418
if (webglCanvas) {
419
// Create lite FPS overlay element
420
var fpsOverlay = document.createElement('div');
421
fpsOverlay.classList.add("hastooltip");
422
fpsOverlay.innerHTML = '<div id="fpsOverlay1" style="font-size: 1.5em; color: lightgreen; text-shadow: 3px 3px black;"></div><div id="fpsOverlay2" style="font-size: 1em; color: lightgrey; text-shadow: 3px 3px black;"></div> <span class="tooltip">FPS (CPU usage %)<br>Min/Avg/Max frame times (msecs)</span>';
423
fpsOverlay.style = 'position: fixed; font-weight: bold; padding: 3px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; cursor: pointer;';
424
fpsOverlay.onclick = () => {
425
var view = document.getElementById('cpuprofiler_canvas');
426
view?.scrollIntoView();
427
};
428
fpsOverlay.oncontextmenu = (e) => e.preventDefault();
429
document.body.appendChild(fpsOverlay);
430
this.fpsOverlay1 = document.getElementById('fpsOverlay1');
431
this.fpsOverlay2 = document.getElementById('fpsOverlay2');
432
function positionOverlay() {
433
var rect = webglCanvas.getBoundingClientRect();
434
var overlayHeight = fpsOverlay.getBoundingClientRect().height || fpsOverlay.height;
435
fpsOverlay.height = overlayHeight; // Remember the overlay height when it was visible, if it is hidden.
436
fpsOverlay.style.display = (rect.bottom >= overlayHeight) ? 'block' : 'none';
437
fpsOverlay.style.top = Math.max(rect.top, 0) + 'px';
438
fpsOverlay.style.left = Math.max(rect.left, 0) + 'px';
439
}
440
setTimeout(positionOverlay, 100);
441
setInterval(positionOverlay, 5000);
442
window.addEventListener('scroll', positionOverlay);
443
}
444
445
this.clearUi(0, this.canvas.width);
446
this.drawGraphLabels();
447
this.updateUi();
448
Module['preMainLoop'] = () => emscriptenCpuProfiler.frameStart();
449
Module['postMainLoop'] = () => emscriptenCpuProfiler.frameEnd();
450
},
451
452
drawHorizontalLine(startX, endX, pixelThickness, msecs) {
453
var height = msecs * this.canvas.height / this.verticalTimeScale;
454
this.drawContext.fillRect(startX,this.canvas.height - height, endX - startX, pixelThickness);
455
},
456
457
clearUi(startX, endX) {
458
// Background clear
459
this.drawContext.fillStyle = this.colorBackground;
460
this.drawContext.fillRect(startX, 0, endX - startX, this.canvas.height);
461
462
this.drawContext.fillStyle = this.color60FpsBar;
463
this.drawHorizontalLine(startX, endX, 1, 16.6666666);
464
this.drawContext.fillStyle = this.color30FpsBar;
465
this.drawHorizontalLine(startX, endX, 1, 33.3333333);
466
},
467
468
drawGraphLabels() {
469
this.drawContext.fillStyle = this.colorTextLabel;
470
this.drawContext.font = "bold 10px Arial";
471
this.drawContext.textAlign = "right";
472
this.drawContext.fillText("16.66... ms", this.canvas.width - 3, this.canvas.height - 16.6666 * this.canvas.height / this.verticalTimeScale - 3);
473
this.drawContext.fillText("33.33... ms", this.canvas.width - 3, this.canvas.height - 33.3333 * this.canvas.height / this.verticalTimeScale - 3);
474
},
475
476
drawBar(x) {
477
var timeSpentInSectionsInsideMainLoop = 0;
478
for (var i = 0; i < this.sections.length; ++i) {
479
var sect = this.sections[i];
480
if (!sect) continue;
481
timeSpentInSectionsInsideMainLoop += sect.frametimesInsideMainLoop[x];
482
}
483
var scale = this.canvas.height / this.verticalTimeScale;
484
var y = this.canvas.height;
485
var h = (this.timeSpentInMainloop[x]-timeSpentInSectionsInsideMainLoop) * scale;
486
y -= h;
487
this.drawContext.fillStyle = this.colorCpuTimeSpentInUserCode;
488
this.drawContext.fillRect(x, y, 1, h);
489
for (var i = 0; i < this.sections.length; ++i) {
490
var sect = this.sections[i];
491
if (!sect) continue;
492
h = (sect.frametimesInsideMainLoop[x] + sect.frametimesOutsideMainLoop[x]) * scale;
493
y -= h;
494
this.drawContext.fillStyle = sect.drawColor;
495
this.drawContext.fillRect(x, y, 1, h);
496
}
497
h = this.timeSpentOutsideMainloop[x] * scale;
498
y -= h;
499
var fps60Limit = this.canvas.height - (16.666666666 + 1.0) * this.canvas.height / this.verticalTimeScale; // Be very lax, allow 1msec extra jitter.
500
var fps30Limit = this.canvas.height - (33.333333333 + 1.0) * this.canvas.height / this.verticalTimeScale; // Be very lax, allow 1msec extra jitter.
501
if (y < fps30Limit) this.drawContext.fillStyle = this.colorWorseThan30FPS;
502
else if (y < fps60Limit) this.drawContext.fillStyle = this.colorWorseThan60FPS;
503
else this.drawContext.fillStyle = this.color60FPS;
504
this.drawContext.fillRect(x, y, 1, h);
505
},
506
507
// Main UI update/redraw entry point. Drawing occurs incrementally to touch as
508
// few pixels as possible and to cause the least impact to the overall
509
// performance while profiling.
510
updateUi(startX, endX) {
511
// Poll whether user as changed the browser window, and if so, resize the profiler window and redraw it.
512
if (this.canvas.width != document.documentElement.clientWidth - 32) {
513
this.canvas.width = document.documentElement.clientWidth - 32;
514
if (this.timeSpentInMainloop.length > this.canvas.width) this.timeSpentInMainloop.length = this.canvas.width;
515
if (this.timeSpentOutsideMainloop.length > this.canvas.width) this.timeSpentOutsideMainloop.length = this.canvas.width;
516
if (this.lastUiUpdateEndX >= this.canvas.width) this.lastUiUpdateEndX = 0;
517
if (this.currentHistogramX >= this.canvas.width) this.currentHistogramX = 0;
518
for (var i in this.sections) {
519
var sect = this.sections[i];
520
if (!sect) continue;
521
if (sect.frametimesInsideMainLoop.length > this.canvas.width) sect.frametimesInsideMainLoop.length = this.canvas.width;
522
if (sect.frametimesOutsideMainLoop.length > this.canvas.width) sect.frametimesOutsideMainLoop.length = this.canvas.width;
523
}
524
document.getElementById('ntotalframes').innerHTML = this.canvas.width + '';
525
this.clearUi(0, this.canvas.width);
526
this.drawGraphLabels();
527
startX = 0; // Full redraw all columns.
528
}
529
530
// Also poll to autodetect if there is an Emscripten GL canvas available
531
// that we could hook into. This is a bit clumsy, but there's no good
532
// location to get an event after GL context has been created, so need to
533
// resort to polling.
534
if (location.search.includes('webglprofiler') && !this.automaticallyHookedWebGLProfiler) {
535
this.hookWebGL();
536
if (location.search.includes('tracegl')) {
537
var res = location.search.match(/tracegl=(\d+)/);
538
var traceGl = res[1];
539
document.getElementById('trace_limit').value = traceGl;
540
this.enableTraceWebGL();
541
}
542
if (this.hookedWebGLContexts.length > 0) this.automaticallyHookedWebGLProfiler = true;
543
}
544
545
var clearDistance = this.uiUpdateInterval * 2 + 1;
546
var clearStart = endX + clearDistance;
547
var clearEnd = clearStart + this.uiUpdateInterval;
548
if (endX < startX) {
549
this.clearUi(clearStart, clearEnd);
550
this.clearUi(0, endX + clearDistance+this.uiUpdateInterval);
551
this.drawGraphLabels();
552
} else {
553
this.clearUi(clearStart, clearEnd);
554
}
555
556
if (endX < startX) {
557
for (var x = startX; x < this.canvas.width; ++x) this.drawBar(x);
558
startX = 0;
559
}
560
for (var x = startX; x < endX; ++x) this.drawBar(x);
561
},
562
563
// Work around Microsoft Edge bug where webGLContext.function.length always
564
// returns 0.
565
webGLFunctionLength(f) {
566
var l0 = ['getContextAttributes','isContextLost','getSupportedExtensions','createBuffer','createFramebuffer','createProgram','createRenderbuffer','createTexture','finish','flush','getError', 'createVertexArray', 'createQuery', 'createSampler', 'createTransformFeedback', 'endTransformFeedback', 'pauseTransformFeedback', 'resumeTransformFeedback', 'makeXRCompatible'];
567
var l1 = ['getExtension','activeTexture','blendEquation','checkFramebufferStatus','clear','clearDepth','clearStencil','compileShader','createShader','cullFace','deleteBuffer','deleteFramebuffer','deleteProgram','deleteRenderbuffer','deleteShader','deleteTexture','depthFunc','depthMask','disable','disableVertexAttribArray','enable','enableVertexAttribArray','frontFace','generateMipmap','getAttachedShaders','getParameter','getProgramInfoLog','getShaderInfoLog','getShaderSource','isBuffer','isEnabled','isFramebuffer','isProgram','isRenderbuffer','isShader','isTexture','lineWidth','linkProgram','stencilMask','useProgram','validateProgram', 'deleteQuery', 'isQuery', 'deleteVertexArray', 'bindVertexArray', 'isVertexArray', 'drawBuffers', 'readBuffer', 'endQuery', 'deleteSampler', 'isSampler', 'isSync', 'deleteSync', 'deleteTransformFeedback', 'isTransformFeedback', 'beginTransformFeedback'];
568
var l2 = ['attachShader','bindBuffer','bindFramebuffer','bindRenderbuffer','bindTexture','blendEquationSeparate','blendFunc','depthRange','detachShader','getActiveAttrib','getActiveUniform','getAttribLocation','getBufferParameter','getProgramParameter','getRenderbufferParameter','getShaderParameter','getShaderPrecisionFormat','getTexParameter','getUniform','getUniformLocation','getVertexAttrib','getVertexAttribOffset','hint','pixelStorei','polygonOffset','sampleCoverage','shaderSource','stencilMaskSeparate','uniform1f','uniform1fv','uniform1i','uniform1iv','uniform2fv','uniform2iv','uniform3fv','uniform3iv','uniform4fv','uniform4iv','vertexAttrib1f','vertexAttrib1fv','vertexAttrib2fv','vertexAttrib3fv','vertexAttrib4fv', 'vertexAttribDivisor', 'beginQuery', 'invalidateFramebuffer', 'getFragDataLocation', 'uniform1ui', 'uniform1uiv', 'uniform2uiv', 'uniform3uiv', 'uniform4uiv', 'vertexAttribI4iv', 'vertexAttribI4uiv', 'getQuery', 'getQueryParameter', 'bindSampler', 'getSamplerParameter', 'fenceSync', 'getSyncParameter', 'bindTransformFeedback', 'getTransformFeedbackVarying', 'getIndexedParameter', 'getUniformIndices', 'getUniformBlockIndex', 'getActiveUniformBlockName'];
569
var l3 = ['bindAttribLocation','bufferData','bufferSubData','drawArrays','getFramebufferAttachmentParameter','stencilFunc','stencilOp','texParameterf','texParameteri','uniform2f','uniform2i','uniformMatrix2fv','uniformMatrix3fv','uniformMatrix4fv','vertexAttrib2f', 'getBufferSubData', 'getInternalformatParameter', 'uniform2ui', 'uniformMatrix2x3fv', 'uniformMatrix3x2fv', 'uniformMatrix2x4fv', 'uniformMatrix4x2fv', 'uniformMatrix3x4fv', 'uniformMatrix4x3fv', 'clearBufferiv', 'clearBufferuiv', 'clearBufferfv', 'samplerParameteri', 'samplerParameterf', 'clientWaitSync', 'waitSync', 'transformFeedbackVaryings', 'bindBufferBase', 'getActiveUniforms', 'getActiveUniformBlockParameter', 'uniformBlockBinding'];
570
var l4 = ['blendColor','blendFuncSeparate','clearColor','colorMask','drawElements','framebufferRenderbuffer','renderbufferStorage','scissor','stencilFuncSeparate','stencilOpSeparate','uniform3f','uniform3i','vertexAttrib3f','viewport', 'drawArraysInstanced', 'uniform3ui', 'clearBufferfi'];
571
var l5 = ['framebufferTexture2D','uniform4f','uniform4i','vertexAttrib4f', 'drawElementsInstanced', 'copyBufferSubData', 'framebufferTextureLayer', 'renderbufferStorageMultisample', 'texStorage2D', 'uniform4ui', 'vertexAttribI4i', 'vertexAttribI4ui', 'vertexAttribIPointer', 'bindBufferRange'];
572
var l6 = ['texImage2D', 'vertexAttribPointer', 'invalidateSubFramebuffer', 'texStorage3D', 'drawRangeElements'];
573
var l7 = ['compressedTexImage2D', 'readPixels', 'texSubImage2D'];
574
var l8 = ['compressedTexSubImage2D', 'copyTexImage2D', 'copyTexSubImage2D', 'compressedTexImage3D'];
575
var l9 = ['copyTexSubImage3D'];
576
var l10 = ['blitFramebuffer', 'texImage3D', 'compressedTexSubImage3D'];
577
var l11 = ['texSubImage3D'];
578
if (l0.includes(f)) return 0;
579
if (l1.includes(f)) return 1;
580
if (l2.includes(f)) return 2;
581
if (l3.includes(f)) return 3;
582
if (l4.includes(f)) return 4;
583
if (l5.includes(f)) return 5;
584
if (l6.includes(f)) return 6;
585
if (l7.includes(f)) return 7;
586
if (l8.includes(f)) return 8;
587
if (l9.includes(f)) return 9;
588
if (l10.includes(f)) return 10;
589
if (l11.includes(f)) return 11;
590
console.warn('Unexpected WebGL function ' + f);
591
},
592
593
detectWebGLContext() {
594
if (Module['canvas']?.GLctxObject?.GLctx) return Module['canvas'].GLctxObject.GLctx;
595
else if (typeof GLctx != 'undefined') return GLctx;
596
else if (Module['ctx']) return Module['ctx'];
597
return null;
598
},
599
600
toggleHookWebGL(glCtx) {
601
glCtx ||= this.detectWebGLContext();
602
if (this.hookedWebGLContexts.includes(glCtx)) this.unhookWebGL(glCtx);
603
else this.hookWebGL(glCtx);
604
},
605
606
enableTraceWebGL() {
607
document.getElementById("toggle_webgl_trace").style.background = '#00FF00';
608
this.logWebGLCallsSlowerThan = parseInt(document.getElementById('trace_limit').value, undefined /* https://github.com/google/closure-compiler/issues/3230 / https://github.com/google/closure-compiler/issues/3548 */);
609
},
610
611
disableTraceWebGL() {
612
document.getElementById("toggle_webgl_trace").style.background = '#E1E1E1';
613
this.logWebGLCallsSlowerThan = Infinity;
614
},
615
616
toggleTraceWebGL() {
617
if (this.logWebGLCallsSlowerThan == Infinity) {
618
this.enableTraceWebGL();
619
} else {
620
this.disableTraceWebGL();
621
}
622
},
623
624
unhookWebGL(glCtx) {
625
glCtx ||= this.detectWebGLContext();
626
if (!glCtx.cpuprofilerAlreadyHooked) return;
627
glCtx.cpuprofilerAlreadyHooked = false;
628
this.hookedWebGLContexts.splice(this.hookedWebGLContexts.indexOf(glCtx), 1);
629
document.getElementById("toggle_webgl_profile").style.background = '#E1E1E1';
630
631
for (var f in glCtx) {
632
if (typeof glCtx[f] != 'function' || f.startsWith('real_')) continue;
633
var realf = 'real_' + f;
634
glCtx[f] = glCtx[realf];
635
delete glCtx[realf];
636
}
637
},
638
639
hookWebGLFunction(f, glCtx) {
640
var section = (this.hotGLFunctions.includes(f) || f.startsWith('uniform') || f.startsWith('vertexAttrib')) ? 0 : 1;
641
var realf = 'real_' + f;
642
glCtx[realf] = glCtx[f];
643
var numArgs = this.webGLFunctionLength(f); // On Firefox & Chrome, could do "glCtx[realf].length", but that doesn't work on Edge, which always reports 0.
644
// Accessing 'arguments'/'...' is super slow, so to avoid overhead, statically reason the number of arguments.
645
switch (numArgs) {
646
case 0: glCtx[f] = () => { this.enterSection(section); var ret = glCtx[realf](); this.endSection(section); return ret; }; break;
647
case 1: glCtx[f] = (a1) => { this.enterSection(section); var ret = glCtx[realf](a1); this.endSection(section); return ret; }; break;
648
case 2: glCtx[f] = (a1, a2) => { this.enterSection(section); var ret = glCtx[realf](a1, a2); this.endSection(section); return ret; }; break;
649
case 3: glCtx[f] = (a1, a2, a3) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3); this.endSection(section); return ret; }; break;
650
case 4: glCtx[f] = (a1, a2, a3, a4) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4); this.endSection(section); return ret; }; break;
651
case 5: glCtx[f] = (a1, a2, a3, a4, a5) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5); this.endSection(section); return ret; }; break;
652
case 6: glCtx[f] = (a1, a2, a3, a4, a5, a6) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6); this.endSection(section); return ret; }; break;
653
case 7: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7); this.endSection(section); return ret; }; break;
654
case 8: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8); this.endSection(section); return ret; }; break;
655
case 9: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9); this.endSection(section); return ret; }; break;
656
case 10: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); this.endSection(section); return ret; }; break;
657
case 11: glCtx[f] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) => { this.enterSection(section); var ret = glCtx[realf](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); this.endSection(section); return ret; }; break;
658
default: throw 'hookWebGL failed! Unexpected length ' + glCtx[realf].length;
659
}
660
},
661
662
hookWebGL(glCtx) {
663
glCtx ||= this.detectWebGLContext();
664
if (!glCtx) return;
665
if (!((typeof WebGLRenderingContext != 'undefined' && glCtx instanceof WebGLRenderingContext)
666
|| (typeof WebGL2RenderingContext != 'undefined' && glCtx instanceof WebGL2RenderingContext))) {
667
document.getElementById("toggle_webgl_profile").disabled = true;
668
return;
669
}
670
671
if (glCtx.cpuprofilerAlreadyHooked) return;
672
glCtx.cpuprofilerAlreadyHooked = true;
673
this.hookedWebGLContexts.push(glCtx);
674
document.getElementById("toggle_webgl_profile").style.background = '#00FF00';
675
676
// Hot GL functions are ones that you'd expect to find during render loops
677
// (render calls, dynamic resource uploads), cold GL functions are load time
678
// functions (shader compilation, texture/mesh creation)
679
// Distinguishing between these two allows pinpointing locations of
680
// troublesome GL usage that might cause performance issues.
681
this.createSection(0, 'Hot GL', this.colorHotGLFunction, /*traceable=*/true);
682
this.createSection(1, 'Cold GL', this.colorColdGLFunction, /*traceable=*/true);
683
for (var f in glCtx) {
684
if (typeof glCtx[f] != 'function' || f.startsWith('real_')) continue;
685
this.hookWebGLFunction(f, glCtx);
686
}
687
// The above injection won't work for texImage2D and texSubImage2D, which have multiple overloads.
688
glCtx['texImage2D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => {
689
this.enterSection(1);
690
var ret = (a7 !== undefined) ? glCtx['real_texImage2D'](a1, a2, a3, a4, a5, a6, a7, a8, a9) : glCtx['real_texImage2D'](a1, a2, a3, a4, a5, a6);
691
this.endSection(1);
692
return ret;
693
};
694
glCtx['texSubImage2D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9) => {
695
this.enterSection(0);
696
var ret = (a8 !== undefined) ? glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7, a8, a9) : glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7);
697
this.endSection(0);
698
return ret;
699
};
700
glCtx['texSubImage3D'] = (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) => {
701
this.enterSection(0);
702
var ret = (a9 !== undefined) ? glCtx['real_texSubImage3D'](a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) : glCtx['real_texSubImage2D'](a1, a2, a3, a4, a5, a6, a7, a8);
703
this.endSection(0);
704
return ret;
705
};
706
glCtx['bufferData'] = (a1, a2, a3, a4, a5) => {
707
// WebGL1/2 versions have different parameters (not just extra ones)
708
var ret = (a4 !== undefined) ? glCtx['real_bufferData'](a1, a2, a3, a4, a5) : glCtx['real_bufferData'](a1, a2, a3);
709
return ret;
710
};
711
const matrixFuncs = ['uniformMatrix2fv', 'uniformMatrix3fv', 'uniformMatrix4fv'];
712
matrixFuncs.forEach(f => {
713
glCtx[f] = (a1, a2, a3, a4, a5) => {
714
// WebGL2 version has 2 extra optional parameters, ensure we forward them
715
var ret = (a4 !== undefined) ? glCtx['real_' + f](a1, a2, a3, a4, a5) : glCtx['real_' + f](a1, a2, a3);
716
return ret;
717
}
718
});
719
const ndvFuncs = ['uniform1fv', 'uniform1iv', 'uniform2fv', 'uniform2iv', 'uniform3fv', 'uniform3iv', 'uniform4fv', 'uniform4iv'];
720
ndvFuncs.forEach(f => {
721
glCtx[f] = (a1, a2, a3, a4) => {
722
// WebGL2 version has 1 extra parameter, ensure we forward them
723
var ret = (a4 !== undefined) ? glCtx['real_' + f](a1, a2, a3, a4) : glCtx['real_' + f](a1, a2, a3);
724
return ret;
725
}
726
});
727
}
728
};
729
730
// Hook into setInterval to be able to capture the time spent executing them.
731
emscriptenCpuProfiler.createSection(2, 'setInterval', emscriptenCpuProfiler.colorSetIntervalSection, /*traceable=*/true);
732
var realSetInterval = setInterval;
733
setInterval = (fn, delay) => {
734
function wrappedSetInterval() {
735
emscriptenCpuProfiler.enterSection(2);
736
fn();
737
emscriptenCpuProfiler.endSection(2);
738
};
739
return realSetInterval(wrappedSetInterval, delay);
740
}
741
742
// Hook into setTimeout to be able to capture the time spent executing them.
743
emscriptenCpuProfiler.createSection(3, 'setTimeout', emscriptenCpuProfiler.colorSetTimeoutSection, /*traceable=*/true);
744
var realSetTimeout = setTimeout;
745
setTimeout = (fn, delay) => {
746
function wrappedSetTimeout() {
747
emscriptenCpuProfiler.enterSection(3);
748
fn();
749
emscriptenCpuProfiler.endSection(3);
750
};
751
return realSetTimeout(wrappedSetTimeout, delay);
752
}
753
754
// Backwards compatibility with previously compiled code. Don't call this anymore!
755
function cpuprofiler_add_hooks() {
756
emscriptenCpuProfiler.initialize();
757
}
758
759
if (typeof document != 'undefined') {
760
emscriptenCpuProfiler.initialize();
761
}
762
763
// Declared in globalThis so that `onclick` handlers work when `-sMODULARIZE=1`
764
globalThis.emscriptenCpuProfiler = emscriptenCpuProfiler;
765
766