223 lines
6.7 KiB
HTML
223 lines
6.7 KiB
HTML
<!--
|
|
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>
|