diff options
author | Kevin Lubick <kjlubick@google.com> | 2018-07-06 14:31:23 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-07-11 15:38:08 +0000 |
commit | 22647d0e84ec63b76b9d26153c59d9338b761107 (patch) | |
tree | 40fcf32a49630a6c4f901a2c11b02b7a4e0287a7 /experimental/wasm/shell.html | |
parent | 1857ddbe218afd8ef32f672619ab13c0f853436c (diff) |
Adventures with Skia, WASM and a JS API for Pathkit
See shell.html::entrypoint() for the JS side of things.
See wasm_main.cpp for the C++ side of things
(EMSCRIPTEN_BINDINGS at the bottom is what glues the two parts
together - in general the strings are for JS and the not strings
are the C++)
To build this yourself, follow the getting started instructions:
https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
and download this patch. Then, update compile.sh to point at your
sdk and run it (e.g. $SKIA_ROOT/experimental/wasm/compile.sh)
Then navigate a browser (e.g. Chrome) to
http://localhost:8000/out/wasm/pathkit.html
So far, can compile with compile.sh, but not really with
GN/ninja (the compilation into many object files and a link
at the end seems to mess emscripten up)
Bug: skia:
Change-Id: If6b300e2b102469e17841265c7866f1a81094d70
Reviewed-on: https://skia-review.googlesource.com/137422
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'experimental/wasm/shell.html')
-rw-r--r-- | experimental/wasm/shell.html | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/experimental/wasm/shell.html b/experimental/wasm/shell.html new file mode 100644 index 0000000000..8557241096 --- /dev/null +++ b/experimental/wasm/shell.html @@ -0,0 +1,511 @@ +<!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> |