<!--
Tests for WebXR dynamic viewport scaling.
-->
<html>
<head>
<link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
</head>
<body>
<canvas id="webgl-canvas"></canvas>
<script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
<script src="../resources/webxr_e2e.js"></script>
<script>var shouldAutoCreateNonImmersiveSession = false;</script>
<script src="../resources/webxr_boilerplate.js"></script>
<script>
// Tests that requesting a viewport scale takes effect immediately if done
// before calling getViewport.
function stepRequestViewportScaleSameFrame() {
let sessionInfo = sessionInfos[sessionTypes.AR];
const referenceSpace = sessionInfo.currentRefSpace;
const session = sessionInfo.currentSession;
let frameCount = 0;
onARFrameCallback = (session, frame) => {
const pose = frame.getViewerPose(referenceSpace);
const baseLayer = session.renderState.baseLayer;
const framebufferPixels = baseLayer.framebufferWidth * baseLayer.framebufferHeight;
let viewportPixels = 0;
for (const view of pose.views) {
view.requestViewportScale(frameCount > 0 ? 0.5 : 1.0);
let viewport = baseLayer.getViewport(view);
viewportPixels += viewport.width * viewport.height;
// Draw something to the framebuffer to ensure that the driver side applies the buffer size.
gl.bindFramebuffer(gl.FRAMEBUFFER, baseLayer.framebuffer);
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
gl.clearColor(0.0, 0.0, 1.0, 0.5);
gl.clear(gl.COLOR_BUFFER_BIT);
}
updateSingleTestProgressMessage(
'state:' +
' frameCount=' + frameCount +
' viewportPixels=' + viewportPixels +
' framebufferPixels=' + framebufferPixels);
assert_greater_than(viewportPixels, 0);
assert_less_than_equal(viewportPixels, framebufferPixels);
if (frameCount == 0) {
// For the ARCore device, assume that the default viewport entirely fills
// the framebuffer. This isn't required by the spec, so this check may
// need to be changed for other devices that don't do this.
assert_equals(viewportPixels, framebufferPixels);
} else {
assert_less_than(viewportPixels, framebufferPixels);
done();
}
++frameCount;
};
}
// Tests that requesting a viewport scale after a preceding getViewport call takes
// effect on the next animation frame, keeping the viewport consistent within a frame.
function stepRequestViewportScaleNextFrame() {
let sessionInfo = sessionInfos[sessionTypes.AR];
const referenceSpace = sessionInfo.currentRefSpace;
const session = sessionInfo.currentSession;
let appliedViewportScaleSameFrame = false;
let frameCount = 0;
onARFrameCallback = (session, frame) => {
const pose = frame.getViewerPose(referenceSpace);
const baseLayer = session.renderState.baseLayer;
const framebufferPixels = baseLayer.framebufferWidth * baseLayer.framebufferHeight;
let viewportPixels = 0;
for (const view of pose.views) {
// Call getViewport first before requesting a viewport scale. This locks in
// the size for the current animation frame.
const viewportA = baseLayer.getViewport(view);
viewportPixels += viewportA.width * viewportA.height;
// Request a size to apply to the next frame.
view.requestViewportScale(frameCount > 0 ? 0.5 : 1.0);
// Calling getViewport again must return the same size as the earlier call.
const viewportB = baseLayer.getViewport(view);
assert_equals(viewportA.width, viewportB.width);
assert_equals(viewportA.height, viewportB.height);
// Draw something to the framebuffer to ensure that the driver side applies the buffer size.
gl.bindFramebuffer(gl.FRAMEBUFFER, baseLayer.framebuffer);
gl.viewport(viewportA.x, viewportA.y, viewportA.width, viewportA.height);
gl.clearColor(0.0, 0.0, 1.0, 0.5);
gl.clear(gl.COLOR_BUFFER_BIT);
}
updateSingleTestProgressMessage(
'state:' +
' frameCount=' + frameCount +
' viewportPixels=' + viewportPixels +
' framebufferPixels=' + framebufferPixels);
assert_greater_than(viewportPixels, 0);
assert_less_than_equal(viewportPixels, framebufferPixels);
if (frameCount == 1) {
assert_equals(viewportPixels, framebufferPixels);
} else if (frameCount > 1) {
assert_less_than(viewportPixels, framebufferPixels);
done();
}
++frameCount;
};
}
// Tests that the recommendedViewportScale exists, is reduced when GPU load increases, then
// increases again when load is low.
function stepRequestRecommendedViewportScale() {
let sessionInfo = sessionInfos[sessionTypes.AR];
const referenceSpace = sessionInfo.currentRefSpace;
const session = sessionInfo.currentSession;
let gotAnyRecommendedViewportScale = false;
let gotReducedRecommendedViewportScale = false;
let gotIncreasedRecommendedViewportScale = false;
let clearRectangleCount = 32;
let minViewportScale = 99999;
let maxViewportScale = 0;
let maxRectangles = 0;
let frameCount = 0;
onARFrameCallback = (session, frame) => {
const pose = frame.getViewerPose(referenceSpace);
gl.enable(gl.SCISSOR_TEST);
for (const view of pose.views) {
const recScale = view.recommendedViewportScale;
if (recScale) {
if (recScale > 0) {
gotAnyRecommendedViewportScale = true;
maxViewportScale = Math.max(maxViewportScale, recScale);
}
if (gotReducedRecommendedViewportScale) {
if (recScale > minViewportScale) {
if (!gotIncreasedRecommendedViewportScale) {
// Only log the increase once - the session may not terminate immediately after calling done().
console.log("got recommendedViewportScale increase from " + minViewportScale + " to " + recScale +
", frameCount=" + frameCount);
}
gotIncreasedRecommendedViewportScale = true;
}
} else {
if (recScale < maxViewportScale) {
gotReducedRecommendedViewportScale = true;
minViewportScale=Math.min(minViewportScale, recScale);
console.log("got recommendedViewportScale=" + recScale + " at clearRectangleCount=" + clearRectangleCount +
", frameCount=" + frameCount);
}
}
}
view.requestViewportScale(view.recommendedViewportScale);
const baseLayer = session.renderState.baseLayer;
let viewport = baseLayer.getViewport(view);
// Draw an exponentially increasing number of translucent rectangles to stress the GPU
// and trigger a decrease of the recommended viewport size. Once the viewport size
// decreased, revert back to a single rectangle and wait for the size to increase
// again.
gl.bindFramebuffer(gl.FRAMEBUFFER, baseLayer.framebuffer);
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
maxRectangles = Math.max(maxRectangles, clearRectangleCount);
for (let i = 0; i < clearRectangleCount; ++i) {
const xOffset = viewport.width * Math.random() / 2;
const yOffset = viewport.height * Math.random() / 2;
gl.scissor(viewport.x + xOffset, viewport.y + yOffset, viewport.width / 2, viewport.height / 2);
gl.clearColor(Math.random(), Math.random(), Math.random(), Math.random() / 2);
gl.clear(gl.COLOR_BUFFER_BIT);
}
if (gotReducedRecommendedViewportScale) {
// Done with the load test, wait for size to increase again.
clearRectangleCount = 1;
} else {
// Add more load every 4th frame. The GPU load feedback is delayed, and we don't want the
// exponential growth to overload the system excessively since that could cause test timeouts.
if (frameCount % 4 == 0) {
clearRectangleCount *= 2;
}
}
}
updateSingleTestProgressMessage(
'state:' +
' frameCount=' + frameCount +
' clearRectangleCount=' + clearRectangleCount +
' (max=' + maxRectangles + ')' +
' gotAnyRecommendedViewportScale=' + gotAnyRecommendedViewportScale +
' gotReducedRecommendedViewportScale=' + gotReducedRecommendedViewportScale +
' gotIncreasedRecommendedViewportScale=' + gotIncreasedRecommendedViewportScale);
gl.disable(gl.SCISSOR_TEST);
if (gotAnyRecommendedViewportScale &&
gotReducedRecommendedViewportScale &&
gotIncreasedRecommendedViewportScale) {
done();
} else {
// Check that the test finishes within a reasonable number of frames.
// This helps ensure that malfunctioning viewport scaling shows up
// as a test failure and not just a generic timeout. It's in the "else"
// branch here to avoid emitting errors for animation frames that are
// still arriving after the test ended.
//
// Since the rectangle count is doubled each frame in the load-increasing
// test phase, this potentially allows drawing up to 2^30 (~1 billion)
// rectangles followed by 20 frames of recovery which should be plenty.
assert_less_than(frameCount, 50, "Viewport scale didn't update within the expected number of frames.")
}
++frameCount;
};
}
</script>
</body>
</html>