Source code

Revision control

Copy as Markdown

Other Tools

<!--
Copyright (c) 2019 The Khronos Group Inc.
Use of this source code is governed by an MIT-style license that can be
found in the LICENSE.txt file.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>getBufferSubData non-blocking test</title>
<link rel="stylesheet" href="../resources/js-test-style.css"/>
<script src="../js/js-test-pre.js"></script>
<script src="../js/webgl-test-utils.js"> </script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script>
"use strict";
description("Test that getBufferSubData is non-blocking when used with fenceSync");
var wtu = WebGLTestUtils;
var gl = wtu.create3DContext(undefined, undefined, 2);
const srcData = new Uint8Array([ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 ]);
const zeroData = new Uint8Array(8);
const srcBuffer = gl.createBuffer();
gl.bindBuffer(gl.COPY_READ_BUFFER, srcBuffer);
gl.bufferData(gl.COPY_READ_BUFFER, srcData, gl.STATIC_DRAW);
const readbackBuffer = gl.createBuffer();
gl.bindBuffer(gl.COPY_WRITE_BUFFER, readbackBuffer);
gl.bufferData(gl.COPY_WRITE_BUFFER, 8, gl.STREAM_READ);
// unrelated buffers for tests
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); // used as copy dst
gl.bufferData(gl.ARRAY_BUFFER, 8, gl.STATIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, gl.createBuffer()); // used as copy src
gl.bufferData(gl.UNIFORM_BUFFER, 8, gl.STATIC_DRAW);
const dest = new Uint8Array(8);
// Makes a new "resolvable" Promise
function resolvable() {
let resolve;
const promise = new Promise(res => { resolve = res; });
promise.resolve = resolve;
return promise;
}
function fence() {
const promise = resolvable();
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush();
function check() {
const status = gl.clientWaitSync(sync, 0, 0);
if (status == gl.ALREADY_SIGNALED || status == gl.CONDITION_SATISFIED) {
gl.deleteSync(sync);
promise.resolve();
} else {
setTimeout(check, 0);
}
}
setTimeout(check, 0);
return promise;
}
function writeToReadbackBuffer() {
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 8);
}
function timedGetBufferSubData() {
dest.fill(0);
const t0 = performance.now();
gl.getBufferSubData(gl.COPY_WRITE_BUFFER, 0, dest);
return (performance.now() - t0);
}
function timeBlockingReadback() {
const promise = resolvable();
setTimeout(() => {
writeToReadbackBuffer();
const tBlocking = timedGetBufferSubData();
const tLatency = tBlocking;
promise.resolve({latency: tLatency, blocking: tBlocking});
}, 0);
return promise;
}
function timeNonblockingReadback() {
writeToReadbackBuffer();
const tLatency0 = performance.now();
return fence().then(() => {
const tBlocking = timedGetBufferSubData();
const tLatency = performance.now() - tLatency0;
return {latency: tLatency, blocking: tBlocking};
});
}
function timeReadbackWithUnrelatedCopy() {
writeToReadbackBuffer();
const tLatency0 = performance.now();
const f = fence();
// copy to a buffer unrelated to the readback
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.ARRAY_BUFFER, 0, 0, 8);
return f.then(() => {
const tBlocking = timedGetBufferSubData();
const tLatency = performance.now() - tLatency0;
return {latency: tLatency, blocking: tBlocking};
});
}
function timeReadbackInterrupted() {
writeToReadbackBuffer();
const tLatency0 = performance.now();
const f = fence();
// interrupt the readback by inserting another write
gl.copyBufferSubData(gl.UNIFORM_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 8);
return f.then(() => {
const tBlocking = timedGetBufferSubData();
const tLatency = performance.now() - tLatency0;
return {latency: tLatency, blocking: tBlocking};
});
}
function computeMean(timings) {
let total = 0;
for (let i = 0; i < timings.length; ++i) {
total += timings[i];
}
return total / timings.length;
}
function measureMean(fn, iterations) {
const timingsLatency = Array(iterations);
const timingsBlocking = Array(iterations);
// Chain together `iterations` promises to call `fn` sequentially.
let promise = Promise.resolve();
for (let i = 0; i < iterations; ++i) {
promise = promise
.then(fn)
.then(t => {
timingsLatency[i] = t.latency;
timingsBlocking[i] = t.blocking;
});
}
return promise.then(() => {
const meanLatency = computeMean(timingsLatency);
const meanBlocking = computeMean(timingsBlocking);
return { latency: meanLatency, blocking: meanBlocking };
});
}
let t_blocking, t_nonblocking;
let t_unrelated;
let t_interrupted;
Promise.resolve()
.then(() => {
let iterations = 500;
debug(`blocking readback: mean over ${iterations} iterations...`);
return measureMean(timeBlockingReadback, iterations);
})
.then(t => {
t_blocking = t;
debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
})
.then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
.then(() => debug(""))
.then(() => {
let iterations = 500;
debug(`nonblocking readback: mean over ${iterations} iterations...`);
return measureMean(timeNonblockingReadback, iterations);
})
.then(t => {
t_nonblocking = t;
debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
})
.then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
.then(() => debug(""))
.then(() => {
let iterations = 500;
debug(`readback interrupted by unrelated read from copy source: mean over ${iterations} iterations...`);
return measureMean(timeReadbackWithUnrelatedCopy, iterations);
})
.then(t => {
t_unrelated = t;
debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
})
.then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
.then(() => debug(""))
.then(() => {
let iterations = 500;
debug(`readback interrupted by write to readback source: mean over ${iterations} iterations...`);
return measureMean(timeReadbackInterrupted, iterations);
})
.then(t => {
t_interrupted = t;
debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
})
.then(() => shouldBeTrue("areArraysEqual(dest, zeroData)"))
.then(() => {
debug("");
shouldBeTrue("t_nonblocking.blocking < t_blocking.blocking");
shouldBeTrue("t_unrelated.blocking < t_blocking.blocking");
shouldBeTrue("t_nonblocking.blocking < t_interrupted.blocking");
})
.then(finishTest);
var successfullyParsed = true;
</script>
</body>
</html>