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 lang="en">
<head>
<meta charset="utf-8">
<title>Parallel Shader Compile test</title>
<style>
body {
margin: 0;
}
#log {
margin: 16px;
}
@keyframes move {
0% { left: 0%; }
50% { left: calc(100% - 64px); }
100% { left: 0%; }
}
#block {
position: relative;
bottom: 0%;
left: 0%;
width: 32px;
height: 32px;
background-color: #07f;
animation-name: move;
animation-duration: 2000ms;
animation-iteration-count: infinite;
}
.container {
display: flex;
flex-wrap: wrap;
}
.button {
width: 260px
}
</style>
</head>
<body>
<pre id='log'></pre>
<!-- The smoothness of the block's moving indicates whether the main thread is too busy. -->
<div id='block'></div>
<script>
var testGroup;
window.addEventListener('error', function (err) {
var logElement = document.getElementById('log');
logElement.textContent += ' \n';
logElement.textContent += err.error.stack.replace(
new RegExp(window.location.href, 'g'), '/') + '\n';
});
function setupGLContextSerial(testRun) {
var infoElement = testRun.logElement;
testRun.gl = document.createElement('canvas').getContext('webgl2');
if (testRun.gl) {
infoElement.textContent += 'webgl2 context created.' + '\n\n';
return true;
} else {
infoElement.textContent += 'webgl2 context is not supported.' + '\n\n';
return false;
}
}
function setupGLContextParallel(testRun) {
var infoElement = testRun.logElement;
if (setupGLContextSerial(testRun)) {
// Enable KHR_parallel_shader_compile extension
testRun.ext = testRun.gl.getExtension('KHR_parallel_shader_compile');
if (testRun.ext) {
return true;
} else {
infoElement.textContent += 'KHR_parallel_shader_compile is unavailable, you' +
' may need to turn on the webgl draft extensions for your browser.'
}
}
return false;
}
function releasePrograms(testRun) {
var gl = testRun.gl;
var programs = testRun.programs;
for (var i = 0; i < programs.length; i++) {
var program = programs[i];
if (program.vShader) {
gl.deleteShader(program.vShader);
program.vShader = null;
}
if (program.fShader) {
gl.deleteShader(program.fShader);
program.fShader = null;
}
if (program.program) {
gl.deleteProgram(program.program);
program.program = null;
}
}
}
function showStatistics(testRun) {
var infoElement = testRun.logElement;
infoElement.textContent += ' ' + '\n';
infoElement.textContent += (Math.round(testRun.elapsedTotal * 100) / 100) +
'ms - ' + 'all shaders compiled, and linked.\n';
infoElement.textContent += ' ' + '\n';
infoElement.textContent += 'done.' + '\n';
releasePrograms(testRun);
}
function checkShader(gl, shader, infoElement) {
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var info = gl.getShaderInfoLog(shader);
infoElement.textContent += 'couldn\'t compile shader:\n';
infoElement.textContent += info.toString() + '\n';
return false;
}
return true;
}
function checkProgram(gl, program, infoElement) {
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var info = gl.getProgramInfoLog(program);
infoElement.textContent += ' ' + '\n';
infoElement.textContent += 'couldn\'t link program:\n';
infoElement.textContent += info.toString() + '\n';
return false;
}
return true;
}
function makeAllProgramsSerial(testRun) {
var gl = testRun.gl;
var infoElement = testRun.logElement;
var programs = testRun.programs;
for (var i = 0; i < programs.length; i++) {
var program = programs[i];
// vertex shader compilation
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, program.vSource);
gl.compileShader(vShader);
checkShader(gl, vShader, infoElement);
// fragment shader compilation
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, program.fSource);
gl.compileShader(fShader);
checkShader(gl, fShader, infoElement);
// program
var programHandle = gl.createProgram();
gl.attachShader(programHandle, vShader);
gl.attachShader(programHandle, fShader);
gl.linkProgram(programHandle);
checkProgram(gl, programHandle, infoElement);
}
testRun.elapsedTotal = performance.now() - testRun.start;
showStatistics(testRun);
};
function makeAllProgramsParallel(testRun) {
var gl = testRun.gl;
var infoElement = testRun.logElement;
var programs = testRun.programs;
for (var i = 0; i < programs.length; i++) {
var program = programs[i];
var vShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vShader, program.vSource);
gl.compileShader(vShader);
var fShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fShader, program.fSource);
gl.compileShader(fShader);
programHandle = gl.createProgram();
gl.attachShader(programHandle, vShader);
gl.attachShader(programHandle, fShader);
program.vShader = vShader;
program.fShader = fShader;
program.program = programHandle;
program.status = "Compiling";
}
function checkCompletion() {
var ext = testRun.ext;
var allProgramsLinked = true;
for (var i = 0; i < programs.length; i++) {
var program = programs[i];
switch (program.status) {
case "Compiling":
if (gl.getShaderParameter(program.vShader, ext.COMPLETION_STATUS_KHR) &&
gl.getShaderParameter(program.fShader, ext.COMPLETION_STATUS_KHR))
{
checkShader(gl, program.vShader, infoElement);
checkShader(gl, program.fShader, infoElement);
gl.linkProgram(program.program);
program.status = "Linking";
}
allProgramsLinked = false;
break;
case "Linking":
if (gl.getProgramParameter(program.program, ext.COMPLETION_STATUS_KHR))
{
checkProgram(gl, program.program, infoElement);
program.status = "Done";
}
else {
allProgramsLinked = false;
}
break;
case "Done":
break;
}
}
if (allProgramsLinked) {
testRun.elapsedTotal = performance.now() - testRun.start;
showStatistics(testRun);
}
else {
requestAnimationFrame(checkCompletion);
}
}
requestAnimationFrame(checkCompletion);
}
function parsePrograms(testRun) {
var gl = testRun.gl;
var infoElement = testRun.logElement;
// Parse programs from the cached text, formatted as:
// __BEGINPROGRAM__
// __VERTEXSHADER__
// shader source line
// ...
// __FRAGMENTSHADER__
// shader source line
// ...
// __ENDPROGRAM__
//
// __BEGINPROGRAM__
// ...
var arrayOfLines = testRun.test.shaderCache.match(/[^\r\n]+/g);
var programs = [];
var currentProgram = {};
var currentShader;
var shaderSourceLine = false;
for (var ii = 0; ii < arrayOfLines.length; ii++) {
var cur = arrayOfLines[ii];
// Use random numbers to fool the program cache mechanism.
if (cur.indexOf('PROGRAM_CACHE_BREAKER_RANDOM') != -1) {
cur = cur.replace('PROGRAM_CACHE_BREAKER_RANDOM', Math.random())
}
if (cur == '__VERTEXSHADER__') {
currentShader = [];
shaderSourceLine = true;
} else if (cur == '__FRAGMENTSHADER__') {
currentProgram.vSource = currentShader.join('\n');
currentShader = [];
shaderSourceLine = true;
} else if (cur == '__ENDPROGRAM__') {
currentProgram.fSource = currentShader.join('\n');
programs.push(currentProgram);
currentProgram = {};
currentShader = [];
shaderSourceLine = false;
} else if (shaderSourceLine) {
currentShader.push(cur);
}
}
infoElement.textContent += programs.length + ' programs found.' + '\n';
infoElement.textContent += 'starting compilations ...' + '\n';
testRun.start = performance.now();
testRun.programs = programs;
testRun.makeAllPrograms(testRun);
};
function runTest(index, isParallel) {
var testRun = {};
var test = testGroup[index];
testRun.test = test;
testRun.name = test.name + (isParallel ? "_parallel" : "_serial");
testRun.logElement = document.getElementById(testRun.name);
testRun.logElement.textContent = '';
testRun.setupGLContext =
(isParallel ? setupGLContextParallel : setupGLContextSerial);
testRun.makeAllPrograms =
(isParallel ? makeAllProgramsParallel : makeAllProgramsSerial);
if (!testRun.setupGLContext(testRun)) {
return;
}
if (test.shaderCache === undefined) {
// load shader cache
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function() {
test.shaderCache = xhr.responseText;
requestAnimationFrame(function() {
parsePrograms(testRun);
});
});
xhr.open('GET', test.location);
xhr.send();
} else {
parsePrograms(testRun);
}
}
function createElement(element, attribute, inner) {
if (element === undefined) {
return false;
}
if (inner === undefined) {
inner = [];
}
var el = document.createElement(element);
if (typeof(attribute) === 'object') {
for (var key in attribute) {
el.setAttribute(key, attribute[key]);
}
}
if (!Array.isArray(inner)) {
inner = [inner];
}
for (var k = 0; k < inner.length; k++) {
if (inner[k].tagName) {
el.appendChild(inner[k]);
} else {
el.appendChild(document.createTextNode(inner[k]));
}
}
return el;
}
var container = createElement("div", {"class": "container"});
document.body.appendChild(container);
testGroup = [{
'location': './shaders/aquarium/shader-cache.txt',
'name': 'aquarium'
},
];
testGroup.forEach((test, index) => {
function createTestView(test, index, isParallel) {
testName = test.name + (isParallel ? "_parallel" : "_serial");
var tButton = createElement(
'button',
{'class': 'button', 'onclick': 'runTest(' + index + ', ' + isParallel + ')'},
testName
);
var tPrex = createElement("pre");
var tPre = createElement("textarea", { "id": testName, "rows": 10, "cols": 30});
var tDivContainer = createElement(
"div",
{"id": " " + testName + "_container"},
[tButton, tPrex, tPre]
);
container.appendChild(tDivContainer);
}
createTestView(test, index, false);
createTestView(test, index, true);
});
</script>
</body>