151 lines
5 KiB
HTML
151 lines
5 KiB
HTML
<!DOCTYPE HTML>
|
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
|
|
|
<title>Canvas2D test: CaptureStream() with throttled rAF</title>
|
|
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<script src="captureStream_common.js"></script>
|
|
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
|
<body>
|
|
<script>
|
|
SimpleTest.waitForExplicitFinish();
|
|
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");
|
|
|
|
// CaptureStreamTestHelper holding utility test functions.
|
|
const h = new CaptureStreamTestHelper2D();
|
|
|
|
async function measureRequestAnimationFrameRate() {
|
|
const frameRate = await new Promise(resolve => {
|
|
let start;
|
|
let count;
|
|
const tick = time => {
|
|
if (!start) {
|
|
start = time;
|
|
count = 0;
|
|
} else {
|
|
count += 1;
|
|
}
|
|
if (time - start > 1000) {
|
|
// One second has passed, break.
|
|
resolve(count / ((time - start) / 1000));
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(tick);
|
|
};
|
|
window.requestAnimationFrame(tick);
|
|
});
|
|
return frameRate;
|
|
}
|
|
|
|
async function measureSetTimeoutRate() {
|
|
const start = performance.now();
|
|
const COUNT = 5;
|
|
for(let i = 0; i < COUNT; ++i) {
|
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
}
|
|
return COUNT / ((performance.now() - start) / 1000);
|
|
}
|
|
|
|
async function measureCanvasCaptureFrameRate(captureRate) {
|
|
// Canvas element captured by streams.
|
|
const c = h.createAndAppendElement('canvas', 'c');
|
|
|
|
// Since we are in a background tab, the video element won't get composited,
|
|
// so we cannot look for a frame count there. Instead we use RTCPeerConnection
|
|
// and RTCOutboundRtpStreamStats.framesEncoded.
|
|
const pc1 = new RTCPeerConnection();
|
|
const pc2 = new RTCPeerConnection();
|
|
|
|
// Add the canvas.captureStream track.
|
|
const ctx = c.getContext('2d');
|
|
const [track] = c.captureStream(captureRate).getTracks();
|
|
const sender = pc1.addTrack(track);
|
|
|
|
// Ice candidates signaling
|
|
for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) {
|
|
local.addEventListener("icecandidate", ({candidate}) => {
|
|
if (!candidate || remote.signalingState == "closed") {
|
|
return;
|
|
}
|
|
remote.addIceCandidate(candidate);
|
|
});
|
|
}
|
|
|
|
// Offer/Answer exchange
|
|
await pc1.setLocalDescription(await pc1.createOffer());
|
|
await pc2.setRemoteDescription(pc1.localDescription);
|
|
await pc2.setLocalDescription(await pc2.createAnswer());
|
|
await pc1.setRemoteDescription(pc2.localDescription);
|
|
|
|
// Wait for connection
|
|
while ([pc1, pc2].some(pc => pc.iceConnectionState == "new" ||
|
|
pc.iceConnectionState == "checking")) {
|
|
await Promise.any(
|
|
[pc1, pc2].map(p => new Promise(r => p.oniceconnectionstatechange = r)));
|
|
}
|
|
for (const [pc, name] of [[pc1, "pc1"], [pc2, "pc2"]]) {
|
|
ok(["connected", "completed"].includes(pc.iceConnectionState),
|
|
`${name} connection established (${pc.iceConnectionState})`);
|
|
}
|
|
|
|
// Draw to the canvas
|
|
const intervalMillis = 1000 / 60;
|
|
const getFrameCount = async () => {
|
|
const stats = await sender.getStats();
|
|
const outbound =
|
|
[...stats.values()].find(({type}) => type == "outbound-rtp");
|
|
return outbound?.framesEncoded ?? 0;
|
|
};
|
|
// Wait for frame count change to ensure sender is working.
|
|
while (await getFrameCount() == 0) {
|
|
h.drawColor(c, h.green);
|
|
await new Promise(resolve => setTimeout(resolve, intervalMillis));
|
|
}
|
|
const startFrameCount = await getFrameCount();
|
|
const start = performance.now();
|
|
let end = start;
|
|
while(end - start <= 1000) {
|
|
h.drawColor(c, h.green);
|
|
await new Promise(resolve => setTimeout(resolve, intervalMillis));
|
|
end = performance.now();
|
|
}
|
|
const framerate = (await getFrameCount() - startFrameCount) / ((end - start) / 1000);
|
|
pc1.close();
|
|
pc2.close();
|
|
return framerate;
|
|
}
|
|
|
|
(async () => {
|
|
// Disable background timer throttling so we can use setTimeout to draw to the
|
|
// canvas while the refresh driver is throttled.
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["dom.timeout.enable_budget_timer_throttling", false],
|
|
["dom.min_background_timeout_value", 0],
|
|
["dom.min_background_timeout_value_without_budget_throttling", 0],
|
|
["browser.link.open_newwindow", 3], // window.open in new tab
|
|
],
|
|
});
|
|
// Throttle the canvas' refresh driver by opening a new foreground tab
|
|
const foregroundTab = window.open("about:blank");
|
|
|
|
// Let the throttling take effect
|
|
await new Promise(r => setTimeout(r, 500));
|
|
|
|
const rAFRate = await measureRequestAnimationFrameRate();
|
|
ok(rAFRate < 5, `rAF framerate is at ${rAFRate} fps`);
|
|
|
|
const setTimeoutRate = await measureSetTimeoutRate();
|
|
ok(setTimeoutRate > 30, `setTimeout rate is at ${setTimeoutRate} tps`);
|
|
|
|
const autoRate = await measureCanvasCaptureFrameRate();
|
|
ok(autoRate > 5, `captureStream() framerate is at ${autoRate} fps`);
|
|
|
|
const cappedRate = await measureCanvasCaptureFrameRate(10);
|
|
ok(cappedRate > 5, `captureStream(10) framerate is at ${cappedRate} fps`);
|
|
|
|
foregroundTab.close();
|
|
SimpleTest.finish();
|
|
})();
|
|
</script>
|
|
|