| <!doctype html> |
| <html lang="en-us"> |
| <head> |
| <meta charset="utf-8"> |
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
| <title>Skia and WASM</title> |
| <style> |
| .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } |
| textarea.emscripten { font-family: monospace; width: 80%; } |
| div.emscripten { text-align: center; } |
| div.emscripten_border { border: 1px solid black; } |
| /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ |
| canvas.emscripten { border: 0px none; background-color: black; } |
| |
| .spinner { |
| height: 50px; |
| width: 50px; |
| margin: 0px auto; |
| -webkit-animation: rotation .8s linear infinite; |
| -moz-animation: rotation .8s linear infinite; |
| -o-animation: rotation .8s linear infinite; |
| animation: rotation 0.8s linear infinite; |
| border-left: 10px solid rgb(0,150,240); |
| border-right: 10px solid rgb(0,150,240); |
| border-bottom: 10px solid rgb(0,150,240); |
| border-top: 10px solid rgb(100,0,200); |
| border-radius: 100%; |
| background-color: rgb(200,100,250); |
| } |
| @-webkit-keyframes rotation { |
| from {-webkit-transform: rotate(0deg);} |
| to {-webkit-transform: rotate(360deg);} |
| } |
| @-moz-keyframes rotation { |
| from {-moz-transform: rotate(0deg);} |
| to {-moz-transform: rotate(360deg);} |
| } |
| @-o-keyframes rotation { |
| from {-o-transform: rotate(0deg);} |
| to {-o-transform: rotate(360deg);} |
| } |
| @keyframes rotation { |
| from {transform: rotate(0deg);} |
| to {transform: rotate(360deg);} |
| } |
| |
| </style> |
| </head> |
| <body> |
| <hr/> |
| <figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure> |
| <div class="emscripten" id="status">Downloading...</div> |
| <div class="emscripten"> |
| <progress value="0" max="100" id="progress" hidden=1></progress> |
| </div> |
| <div class="emscripten_border"> |
| <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> |
| </div> |
| <svg id=svg xmlns='http://www.w3.org/2000/svg' width=50 height=50 viewPort='0 0 50 50'> |
| </svg> |
| <hr/> |
| <div class="emscripten"> |
| <input type="checkbox" id="resize">Resize canvas |
| <input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer |
| |
| <input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked, |
| document.getElementById('resize').checked)"> |
| </div> |
| |
| <hr/> |
| <textarea class="emscripten" id="output" rows="8"></textarea> |
| <hr> |
| <script type='text/javascript'> |
| function entryPoint() { |
| // See https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#call-compiled-c-c-code-directly-from-javascript |
| const FAST_TIMES = 100 + 11 /*warmups*/; |
| const SLOW_TIMES = 10 + 11 /*warmups*/; |
| |
| var path; |
| for (let i = 0; i< FAST_TIMES; i++){ |
| if (i === 10) { |
| // let it "warm up" |
| console.time('Path_with_calls'); // about 3.9s for 1M iterations |
| } |
| path = new Module.SkPath(); |
| path.moveTo(0,0); |
| path.lineTo(40,0); |
| path.lineTo(20,20); |
| path.close(); |
| path.moveTo(20,0); |
| path.lineTo(60,0); |
| path.lineTo(40,20); |
| path.close(); |
| if (i < FAST_TIMES - 1) { |
| path.delete(); |
| } |
| } |
| console.timeEnd('Path_with_calls'); |
| |
| var result; |
| for(let i = 0; i < SLOW_TIMES; i++) { |
| if (i === 10) { |
| console.time('simplify'); // about 5.4s for 100k iterations |
| } |
| result = Module.SimplifyPath(path); |
| if (i < SLOW_TIMES - 1) { |
| result.delete(); |
| } |
| } |
| console.timeEnd('simplify'); |
| |
| var p2d; |
| for(let i = 0; i < FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('ToPath2D'); // about 6.3s for 1M iterations |
| } |
| p2d = Module.ToPath2D(result, new Path2D()); |
| } |
| console.timeEnd('ToPath2D'); |
| |
| let ctx = Module.canvas.getContext('2d') |
| ctx.strokeStyle = 'red'; |
| ctx.stroke(p2d); |
| |
| var svgPtr; |
| for(let i=0; i < FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('ToSVGString (simple)'); // about 7.1s for 1M iterations |
| } |
| svgPtr = Module.ToSVGString(result); |
| } |
| console.timeEnd('ToSVGString (simple)'); |
| Module.print('Got SVG Data: ' + svgPtr); |
| |
| var mountains; |
| for(let i=0; i < FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('FromSVGString (simple)'); |
| } |
| mountains = Module.FromSVGString(svgPtr); |
| if (i < FAST_TIMES - 1) { |
| mountains.delete(); |
| } |
| } |
| console.timeEnd('FromSVGString (simple)'); |
| |
| path.delete(); |
| result.delete(); |
| mountains.delete(); |
| |
| //============================================================================== |
| |
| function uint8TypedArray(arr) { |
| const ta = new Uint8ClampedArray(arr.length); |
| for (let i = 0; i < arr.length; i++){ |
| ta[i] = arr[i]; |
| } |
| |
| retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT); |
| Module.HEAPU8.set(ta, retVal / ta.BYTES_PER_ELEMENT); |
| return retVal; |
| } |
| |
| function floatTypedArray(arr) { |
| const ta = new Float32Array(arr.length); |
| for (let i = 0; i < arr.length; i++){ |
| ta[i] = arr[i]; |
| } |
| |
| retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT); |
| Module.HEAPF32.set(ta, retVal / ta.BYTES_PER_ELEMENT); |
| return retVal; |
| } |
| var path4; |
| for (let i = 0; i< FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('path_with_typed_array'); // about 3.6s for 1M iterations |
| } |
| let verbs = []; |
| let args = []; |
| |
| verbs.push(Module.MOVE_VERB); |
| args.push(100); args.push(0); |
| verbs.push(Module.LINE_VERB); |
| args.push(140); args.push(0); |
| verbs.push(Module.LINE_VERB); |
| args.push(120); args.push(20); |
| verbs.push(Module.CLOSE_VERB); |
| |
| verbs.push(Module.MOVE_VERB); |
| args.push(120); args.push(0); |
| verbs.push(Module.LINE_VERB); |
| args.push(160); args.push(0); |
| verbs.push(Module.LINE_VERB); |
| args.push(140); args.push(20); |
| verbs.push(Module.CLOSE_VERB); |
| |
| let tVerbs = uint8TypedArray(verbs); |
| let tArgs = floatTypedArray(args); |
| |
| path4 = Module.SkPathFromVerbsArgsTyped(tVerbs, verbs.length, |
| tArgs, args.length); |
| if (i < FAST_TIMES - 1) { |
| path4.delete(); |
| } |
| Module._free(tVerbs); |
| Module._free(tArgs); |
| } |
| console.timeEnd('path_with_typed_array'); |
| //path4.dump(); |
| |
| var v4, a4; |
| for (let i = 0; i< FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('to_verbs_args_array'); // about 5.4s for 1M iterations |
| } |
| v4 = []; |
| a4 = []; |
| Module.SkPathToVerbsArgsArray(path4, v4, a4); |
| } |
| console.timeEnd('to_verbs_args_array'); |
| |
| Module.print(`Got path with ${v4.length} verbs and ${a4.length} args`); |
| Module.print(v4[0] + ' (0 for value is MoveTo, 1 for LineTo, etc)'); |
| Module.print(a4[0], a4[1] + ' (coordinates)'); |
| |
| path4.delete(); |
| //============================================================================== |
| |
| let p1 = new Module.SkPath(); |
| p1.moveTo(0,60); |
| p1.lineTo(40,60); |
| p1.lineTo(20,80); |
| p1.close(); |
| let p2 = new Module.SkPath(); |
| p2.moveTo(20,60); |
| p2.lineTo(60,60); |
| p2.lineTo(40,80); |
| p2.close(); |
| |
| var p3; |
| for (let i = 0; i< SLOW_TIMES; i++) { |
| if (i === 10) { |
| console.time('ApplyPathOp'); // about 5.2s for 100k iterations |
| } |
| p3 = Module.ApplyPathOp(p1, p2, Module.PathOp.INTERSECT); |
| if (i < SLOW_TIMES - 1) { |
| p3.delete(); |
| } |
| } |
| console.timeEnd('ApplyPathOp'); |
| |
| ctx = Module.canvas.getContext('2d'); |
| ctx.strokeStyle = 'green'; |
| // Alternative way to use Path2D (potentially for browsers that |
| // don't support the Path2D?) |
| ctx.beginPath(); |
| Module.ToPath2D(p3, ctx); |
| ctx.stroke(); |
| |
| var builder, p4; |
| for (let i = 0; i< SLOW_TIMES; i++) { |
| if (i === 10) { |
| console.time('PathOpBuilder'); // about 12.3s for 100k iterations |
| } |
| builder = new Module.SkOpBuilder(); |
| builder.add(p1, Module.PathOp.UNION); |
| builder.add(p2, Module.PathOp.UNION); |
| builder.add(p3, Module.PathOp.DIFFERENCE); |
| p4 = Module.ResolveBuilder(builder); |
| if (i < SLOW_TIMES - 1) { |
| p4.delete(); |
| } |
| builder.delete(); |
| } |
| console.timeEnd('PathOpBuilder'); |
| |
| |
| p2d = Module.ToPath2D(p4, new Path2D()); |
| |
| ctx = Module.canvas.getContext('2d'); |
| ctx.fillStyle = 'purple'; |
| ctx.strokeStyle = 'white'; |
| ctx.fill(p2d); |
| ctx.stroke(p2d); |
| |
| p1.delete(); |
| p2.delete(); |
| p3.delete(); |
| p4.delete(); |
| |
| //===================================================================================== |
| |
| function floatTypedArrayFrom2D(arr) { |
| // expects 2d array where index 0 is verb and index 1-n are args |
| let len = 0; |
| for (cmd of arr) { |
| len += cmd.length; |
| } |
| |
| const ta = new Float32Array(len); |
| let i = 0; |
| for (cmd of arr) { |
| for (c of cmd) { |
| ta[i] = c; |
| i++; |
| } |
| } |
| |
| retVal = Module._malloc(ta.length * ta.BYTES_PER_ELEMENT); |
| Module.HEAPF32.set(ta, retVal / ta.BYTES_PER_ELEMENT); |
| return [retVal, len]; |
| } |
| |
| function SkPathFromCmdTyped(cmdArr) { |
| let [cmd, len] = floatTypedArrayFrom2D(cmdArr); |
| let path = Module.SkPathFromCmdTyped(cmd, len); |
| Module._free(cmd); |
| return path; |
| } |
| |
| var path5; |
| for (let i = 0; i< FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('path_cmd_typed (simple)'); // about 4.6s for 1M iterations |
| } |
| let commands = []; |
| |
| commands.push([Module.MOVE_VERB, 100, 60]); |
| commands.push([Module.LINE_VERB, 140, 60]); |
| commands.push([Module.LINE_VERB, 120, 80]); |
| commands.push([Module.CLOSE_VERB]); |
| |
| commands.push([Module.MOVE_VERB, 120, 60]); |
| commands.push([Module.LINE_VERB, 160, 60]); |
| commands.push([Module.LINE_VERB, 140, 80]); |
| commands.push([Module.CLOSE_VERB]); |
| |
| path5 = SkPathFromCmdTyped(commands); |
| |
| if (i < FAST_TIMES - 1) { |
| path5.delete(); |
| } |
| } |
| console.timeEnd('path_cmd_typed (simple)'); |
| |
| p2d = Module.ToPath2D(path5, new Path2D()); |
| |
| ctx = Module.canvas.getContext('2d'); |
| ctx.fillStyle = 'white'; |
| ctx.fill(p2d); |
| |
| var cmd5; |
| for (let i = 0; i< FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('to_cmd_array'); // about 9.1s for 1M iterations |
| } |
| cmd5 = Module.SkPathToCmdArray(path5); |
| } |
| console.timeEnd('to_cmd_array'); |
| |
| |
| Module.print(`Got path with ${cmd5.length} commands`); |
| Module.print(cmd5[0][0] + ' (0 for value is MoveTo, 1 for LineTo, etc)'); |
| Module.print(cmd5[0][1], cmd5[0][2] + ' (coordinates)'); |
| //console.log(cmd5); |
| |
| path5.delete(); |
| //========================================================== |
| const svgPath = 'M6 18c0 .55.45 1 1 1h1v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h2v3.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5V19h1c.55 0 1-.45 1-1V8H6v10zM3.5 8C2.67 8 2 8.67 2 9.5v7c0 .83.67 1.5 1.5 1.5S5 17.33 5 16.5v-7C5 8.67 4.33 8 3.5 8zm17 0c-.83 0-1.5.67-1.5 1.5v7c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-7c0-.83-.67-1.5-1.5-1.5zm-4.97-5.84l1.3-1.3c.2-.2.2-.51 0-.71-.2-.2-.51-.2-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.15c-.2-.2-.51-.2-.71 0-.2.2-.2.51 0 .71l1.31 1.31C6.97 3.26 6 5.01 6 7h12c0-1.99-.97-3.75-2.47-4.84zM10 5H9V4h1v1zm5 0h-1V4h1v1z'; |
| |
| var android; |
| for(let i=0; i < SLOW_TIMES; i++) { |
| if (i === 10) { |
| console.time('FromSVGString (moderate)'); // about 6.8s for 100k iterations |
| } |
| android = Module.FromSVGString(svgPath); |
| if (i < SLOW_TIMES - 1) { |
| android.delete(); |
| } |
| } |
| console.timeEnd('FromSVGString (moderate)'); |
| |
| result = Module.SimplifyPath(android); |
| //result.dump(); |
| let androidSimple = Module.ToSVGString(result); |
| |
| let newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| newPath.setAttribute('fill', 'rgb(0,200,0)'); |
| newPath.setAttribute('d', androidSimple); |
| document.getElementById('svg').appendChild(newPath); |
| |
| androidCMDs = Module.SkPathToCmdArray(android); |
| |
| android.delete(); |
| result.delete(); |
| |
| var android2; |
| for (let i = 0; i< FAST_TIMES; i++) { |
| if (i === 10) { |
| console.time('path_cmd_typed (moderate)'); // about 2.2s for 100k |
| } |
| |
| android2 = SkPathFromCmdTyped(androidCMDs); |
| |
| if (i < FAST_TIMES - 1) { |
| android2.delete(); |
| } |
| } |
| console.timeEnd('path_cmd_typed (moderate)'); |
| var android2Str; |
| for(let i=0; i < SLOW_TIMES; i++) { |
| if (i === 10) { |
| console.time('ToSVGString (moderate)'); |
| } |
| android2Str = Module.ToSVGString(android2); |
| } |
| console.timeEnd('ToSVGString (moderate)'); |
| |
| newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| newPath.setAttribute('fill', 'rgb(0,0,200)'); |
| newPath.setAttribute('d', android2Str); |
| newPath.setAttribute('transform', 'translate(25 0)'); |
| document.getElementById('svg').appendChild(newPath); |
| |
| android2.delete(); |
| } |
| </script> |
| <script type='text/javascript'> |
| var statusElement = document.getElementById('status'); |
| var progressElement = document.getElementById('progress'); |
| var spinnerElement = document.getElementById('spinner'); |
| |
| var Module = { |
| preRun: [], |
| postRun: [entryPoint], |
| print: (function() { |
| var element = document.getElementById('output'); |
| if (element) element.value = ''; // clear browser cache |
| return function(text) { |
| if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); |
| // These replacements are necessary if you render to raw HTML |
| //text = text.replace(/&/g, "&"); |
| //text = text.replace(/</g, "<"); |
| //text = text.replace(/>/g, ">"); |
| //text = text.replace('\n', '<br>', 'g'); |
| console.log(text); |
| if (element) { |
| element.value += text + "\n"; |
| element.scrollTop = element.scrollHeight; // focus on bottom |
| } |
| }; |
| })(), |
| printErr: function(text) { |
| if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); |
| if (0) { // XXX disabled for safety typeof dump == 'function') { |
| dump(text + '\n'); // fast, straight to the real console |
| } else { |
| console.error(text); |
| } |
| }, |
| canvas: (function() { |
| var canvas = document.getElementById('canvas'); |
| |
| // As a default initial behavior, pop up an alert when webgl context is lost. To make your |
| // application robust, you may want to override this behavior before shipping! |
| // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 |
| canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); |
| |
| return canvas; |
| })(), |
| setStatus: function(text) { |
| if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; |
| if (text === Module.setStatus.last.text) return; |
| var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); |
| var now = Date.now(); |
| if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon |
| Module.setStatus.last.time = now; |
| Module.setStatus.last.text = text; |
| if (m) { |
| text = m[1]; |
| progressElement.value = parseInt(m[2])*100; |
| progressElement.max = parseInt(m[4])*100; |
| progressElement.hidden = false; |
| spinnerElement.hidden = false; |
| } else { |
| progressElement.value = null; |
| progressElement.max = null; |
| progressElement.hidden = true; |
| if (!text) spinnerElement.hidden = true; |
| } |
| statusElement.innerHTML = text; |
| }, |
| totalDependencies: 0, |
| monitorRunDependencies: function(left) { |
| this.totalDependencies = Math.max(this.totalDependencies, left); |
| Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); |
| } |
| }; |
| Module.setStatus('Downloading...'); |
| window.onerror = function() { |
| Module.setStatus('Exception thrown, see JavaScript console'); |
| spinnerElement.style.display = 'none'; |
| Module.setStatus = function(text) { |
| if (text) Module.printErr('[post-exception status] ' + text); |
| }; |
| }; |
| </script> |
| {{{ SCRIPT }}} |
| </body> |
| </html> |