khr-parallel-shader-compile.html (9219B)
1 <!-- 2 Copyright (c) 2020 The Khronos Group Inc. 3 Use of this source code is governed by an MIT-style license that can be 4 found in the LICENSE.txt file. 5 --> 6 7 <!DOCTYPE html> 8 <html> 9 <head> 10 <meta charset="utf-8"> 11 <link rel="stylesheet" href="../../resources/js-test-style.css"/> 12 <script src="../../js/js-test-pre.js"></script> 13 <script src="../../js/webgl-test-utils.js"></script> 14 <style> 15 .spinner { 16 width: 100px; 17 height: 100px; 18 border: 20px solid transparent; 19 border-top: 20px solid black; 20 border-radius: 100%; 21 text-align: center; 22 padding: 10px; 23 } 24 @keyframes rotation { 25 from { transorm: rotate(0); } 26 to { transform: rotate(360deg); } 27 } 28 </style> 29 </head> 30 <body> 31 <div id="description"></div> 32 <button onclick='compileShaders()'>The spinners below should not stutter when you click this button.</button> 33 <div class="spinner" style="animation: rotation 2s infinite linear;">CSS</div> 34 <div class="spinner" id=spinner>JS</div> 35 <div id="console"></div> 36 <canvas id=canvas></canvas> 37 <script> 38 "use strict"; 39 description("Test KHR_parallel_shader_compile"); 40 41 function spinSpinner() { 42 let degrees = (performance.now() / 1000 / 2 % 1.) * 360; 43 spinner.style.transform = `rotate(${degrees}deg)`; 44 requestAnimationFrame(spinSpinner); 45 } 46 spinSpinner(); 47 48 const wtu = WebGLTestUtils; 49 50 const gl = wtu.create3DContext(); 51 const loseContext = wtu.getExtensionWithKnownPrefixes(gl, "WEBGL_lose_context"); 52 53 let counter = 0; 54 const vertexSource = (extra) => ` 55 void main() { 56 vec4 result = vec4(0.${counter++}); 57 ${extra || ''} 58 gl_Position = result; 59 }`; 60 const fragmentSource = (extra) => ` 61 precision highp float; 62 void main() { 63 vec4 result = vec4(0.${counter++}); 64 ${extra || ''} 65 gl_FragColor = result; 66 }`; 67 68 let vs = gl.createShader(gl.VERTEX_SHADER); 69 let fs = gl.createShader(gl.FRAGMENT_SHADER); 70 let program = gl.createProgram(); 71 gl.attachShader(program, vs); 72 gl.attachShader(program, fs); 73 74 const COMPLETION_STATUS_KHR = 0x91B1; 75 76 gl.shaderSource(vs, vertexSource()); 77 gl.compileShader(vs); 78 let status = gl.getShaderParameter(vs, COMPLETION_STATUS_KHR); 79 if (status !== null) testFailed('Extension disabled, status should be null'); 80 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "extension disabled"); 81 82 gl.shaderSource(fs, fragmentSource()); 83 gl.compileShader(fs); 84 85 gl.linkProgram(program); 86 status = gl.getProgramParameter(program, COMPLETION_STATUS_KHR); 87 if (status !== null) testFailed('Extension disabled, status should be null'); 88 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "extension disabled"); 89 90 const ext = wtu.getExtensionWithKnownPrefixes(gl, "KHR_parallel_shader_compile"); 91 92 let successfullyParsed = false; 93 94 let extraCode = ''; 95 96 (async () => { 97 98 if (!ext) { 99 testPassed("No KHR_parallel_shader_compile support -- this is legal"); 100 } else { 101 testPassed("Successfully enabled KHR_parallel_shader_compile extension"); 102 103 shouldBe("ext.COMPLETION_STATUS_KHR", "0x91B1"); 104 105 debug("Checking that status is a boolean."); 106 gl.shaderSource(vs, vertexSource()); 107 gl.compileShader(vs); 108 let status = gl.getShaderParameter(vs, COMPLETION_STATUS_KHR); 109 if (status !== true && status !== false) testFailed("status should be a boolean"); 110 111 gl.linkProgram(program); 112 status = gl.getProgramParameter(program, COMPLETION_STATUS_KHR); 113 if (status !== true && status !== false) testFailed("status should be a boolean"); 114 115 const minimumShaderCompileDurationMs = 500; 116 debug(`Constructing shader that takes > ${minimumShaderCompileDurationMs} ms to compile.`); 117 let measuredCompileDuration = 0; 118 extraCode = '\n if (true) { result += vec4(0.0000001); }'; 119 for (let i = 0; measuredCompileDuration < minimumShaderCompileDurationMs; i++) { 120 extraCode += extraCode; 121 extraCode += extraCode; 122 if (i < 4) continue; 123 gl.shaderSource(vs, vertexSource(extraCode)); 124 gl.shaderSource(fs, fragmentSource(extraCode)); 125 gl.compileShader(vs); 126 gl.compileShader(fs); 127 gl.linkProgram(program); 128 const start = performance.now(); 129 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { 130 testFailed(`Shaders failed to compile. 131 program: ${gl.getProgramInfoLog(program)} 132 vs: ${gl.getShaderInfoLog(vs)} 133 fs: ${gl.getShaderInfoLog(fs)}`); 134 break; 135 } 136 measuredCompileDuration = performance.now() - start; 137 } 138 139 debug(''); 140 gl.shaderSource(vs, vertexSource(extraCode)); 141 gl.shaderSource(fs, fragmentSource(extraCode)); 142 gl.compileShader(vs); 143 gl.compileShader(fs); 144 gl.linkProgram(program); 145 146 let start = performance.now(); 147 gl.getShaderParameter(fs, COMPLETION_STATUS_KHR); 148 gl.getShaderParameter(vs, COMPLETION_STATUS_KHR); 149 let duration = performance.now() - start; 150 if (duration > 100) 151 testFailed(`Querying shader status should not wait for compilation. Took ${duration} ms`); 152 153 let frames = 0; 154 const maximumTimeToWait = measuredCompileDuration * 4; 155 while (!gl.getProgramParameter(program, COMPLETION_STATUS_KHR) 156 && performance.now() - start < maximumTimeToWait) { 157 frames++; 158 await new Promise(requestAnimationFrame); 159 } 160 duration = performance.now() - start; 161 if (!gl.getProgramParameter(program, COMPLETION_STATUS_KHR)) { 162 testFailed(`Program took longer than ${maximumTimeToWait} ms to compile. Expected: ${measuredCompileDuration} ms, actual: ${duration} ms`); 163 } else if (!gl.getShaderParameter(vs, COMPLETION_STATUS_KHR) || !gl.getShaderParameter(fs, COMPLETION_STATUS_KHR)) { 164 testFailed('Program linked before shaders finished compiling.'); 165 } else if (frames <= 6) { 166 testFailed(`Program should have taken many more than 6 frames to compile. Actual value: ${frames} frames, duration ${performance.now() - start} ms.`); 167 } else { 168 console.log(`COMPLETION_STATUS_KHR sucessfully transitioned from false to true in ${frames} frames and ${duration} ms.`); 169 testPassed(`COMPLETION_STATUS_KHR sucessfully transitioned from false to true`); 170 } 171 172 debug("Checking that compiling lots of programs in parallel eventually completes."); 173 let programs = []; 174 for (let i = 0; i < 256; ++i) { 175 gl.shaderSource(vs, vertexSource()); 176 gl.shaderSource(fs, fragmentSource()); 177 gl.compileShader(vs); 178 gl.compileShader(fs); 179 let program = gl.createProgram(); 180 gl.attachShader(program, vs); 181 gl.attachShader(program, fs); 182 gl.linkProgram(program); 183 programs.push(program); 184 } 185 let allDone = false; 186 while (!allDone) { 187 allDone = true; 188 for (let i = 0; i < programs.length; ++i) { 189 if (!gl.getProgramParameter(programs[i], COMPLETION_STATUS_KHR)) { 190 allDone = false; 191 break; 192 } 193 } 194 if (!allDone) { 195 await new Promise(requestAnimationFrame); 196 } 197 } 198 199 debug("Checking that status is true when context is lost."); 200 if (loseContext) { 201 gl.shaderSource(vs, vertexSource(extraCode)); 202 gl.shaderSource(fs, fragmentSource(extraCode)); 203 gl.compileShader(vs); 204 gl.compileShader(fs); 205 gl.linkProgram(program); 206 loseContext.loseContext(); 207 status = gl.getShaderParameter(vs, COMPLETION_STATUS_KHR); 208 if (status !== true) testFailed("shader status should be true when context is lost"); 209 status = gl.getProgramParameter(program, COMPLETION_STATUS_KHR); 210 if (status !== true) testFailed("program status should be true when context is lost"); 211 loseContext.restoreContext(); 212 vs = gl.createShader(gl.VERTEX_SHADER); 213 fs = gl.createShader(gl.FRAGMENT_SHADER); 214 program = gl.createProgram(); 215 } 216 } 217 finishTest(); 218 })(); 219 220 async function compileShaders() { 221 console.log('Compiling shaders'); 222 const gl = canvas.getContext('webgl'); 223 const vs = gl.createShader(gl.VERTEX_SHADER); 224 const fs = gl.createShader(gl.FRAGMENT_SHADER); 225 const program = gl.createProgram(); 226 gl.getExtension(wtu.getExtensionWithKnownPrefixes(gl, "KHR_parallel_shader_compile")); 227 gl.attachShader(program, vs); 228 gl.attachShader(program, fs); 229 gl.shaderSource(vs, vertexSource(extraCode)); 230 gl.shaderSource(fs, fragmentSource(extraCode)); 231 gl.compileShader(vs); 232 gl.compileShader(fs); 233 gl.linkProgram(program); 234 while (!gl.getProgramParameter(program, COMPLETION_STATUS_KHR)) { 235 gl.getShaderParameter(vs, COMPLETION_STATUS_KHR); 236 gl.getShaderParameter(fs, COMPLETION_STATUS_KHR); 237 await new Promise(requestAnimationFrame); 238 } 239 gl.getProgramParameter(program, gl.LINK_STATUS); 240 console.log('Compilation finished.'); 241 } 242 243 </script> 244 245 </body> 246 </html>