[canvaskit] POC working on Node.js

Basically no hoops to jump through - the same binary
that works in the browser works in Node.

Tested locally with Node 8.9.3.

This aligns the GPU and CPU APIs (that is, makeSurface)
and breaks out the GPU/CPU js interface parts into
their own files. We only need one of them and we know
which at compile time.

Bug: skia:
Change-Id: I6d141387403a792d2374cf904872c6dbc999abfb
Reviewed-on: https://skia-review.googlesource.com/c/162746
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Joe Gregorio <jcgregorio@google.com>
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
index f63395d..cf7cf74 100644
--- a/experimental/canvaskit/Makefile
+++ b/experimental/canvaskit/Makefile
@@ -10,6 +10,13 @@
 	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
 	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
 
+release_cpu:
+	# Does an incremental build where possible.
+	./compile.sh cpu
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
+
 debug:
 	# Does an incremental build where possible.
 	./compile.sh debug
@@ -17,6 +24,13 @@
 	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
 
+debug_cpu:
+	# Does an incremental build where possible.
+	./compile.sh debug cpu
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+
 local-example:
 	rm -rf node_modules/canvaskit
 	mkdir -p node_modules
@@ -27,4 +41,7 @@
 test-continuous:
 	echo "Assuming npm install has been run by user"
 	echo "Also assuming make debug or release has also been run by a user (if needed)"
-	npx karma start ./karma.conf.js --no-single-run --watch-poll
\ No newline at end of file
+	npx karma start ./karma.conf.js --no-single-run --watch-poll
+
+node-example:
+	node ./canvaskit/node.example.js --expose-wasm
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/cpu_example.html b/experimental/canvaskit/canvaskit/cpu_example.html
deleted file mode 100644
index f4bf85b..0000000
--- a/experimental/canvaskit/canvaskit/cpu_example.html
+++ /dev/null
@@ -1,320 +0,0 @@
-<!DOCTYPE html>
-<title>CanvasKit (Skia via Web Assembly)</title>
-<meta charset="utf-8" />
-<meta http-equiv="X-UA-Compatible" content="IE=edge">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
-<style>
-  svg, canvas {
-    border: 1px dashed #AAA;
-  }
-
-  #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding {
-    width: 300px;
-    height: 300px;
-  }
-
-</style>
-
-<h2> CanvasKit draws Paths to Canvas using a CPU backend</h2>
-<canvas id=patheffect width=300 height=300></canvas>
-<img id=output>
-<canvas id=paths width=200 height=200></canvas>
-<canvas id=ink width=300 height=300></canvas>
-
-<h2> Skottie </h2>
-<canvas id=sk_legos width=300 height=300></canvas>
-<canvas id=sk_drinks width=500 height=500></canvas>
-<canvas id=sk_party width=500 height=500></canvas>
-<canvas id=sk_onboarding width=500 height=500></canvas>
-
-<script type="text/javascript" src="/node_modules/canvas-kit/bin/canvaskit.js"></script>
-
-<script type="text/javascript" charset="utf-8">
-
-  var CanvasKit = null;
-  var legoJSON = null;
-  var drinksJSON = null;
-  var confettiJSON = null;
-  var onboardingJSON = null;
-  var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
-  CanvasKitInit({
-    locateFile: (file) => '/node_modules/canvas-kit/bin/'+file,
-  }).then((CK) => {
-    CK.initFonts();
-    CanvasKit = CK;
-    DrawingExample(CanvasKit);
-    PathExample(CanvasKit);
-    InkExample(CanvasKit);
-    // Set bounds to fix the 4:3 resolution of the legos
-    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
-    // Re-size to fit
-    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
-    resp.text().then((str) => {
-      legoJSON = str;
-      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
-    resp.text().then((str) => {
-      drinksJSON = str;
-      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
-    resp.text().then((str) => {
-      confettiJSON = str;
-      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-    });
-  });
-
-  fetch('https://storage.googleapis.com/skia-cdn/misc/onboarding.json').then((resp) => {
-    resp.text().then((str) => {
-      onboardingJSON = str;
-      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
-    });
-  });
-
-  function DrawingExample(CanvasKit) {
-    const surface = CanvasKit.getRasterN32PremulSurface('patheffect');
-    if (!surface) {
-      console.log('Could not make surface');
-      return;
-    }
-
-    const canvas = surface.getCanvas();
-
-    const paint = new CanvasKit.SkPaint();
-
-    const textPaint = new CanvasKit.SkPaint();
-    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
-    textPaint.setTextSize(30);
-    textPaint.setAntiAlias(true);
-
-    let i = 0;
-
-    let X = 128;
-    let Y = 128;
-
-    function drawFrame() {
-      const path = starPath(CanvasKit, X, Y);
-      const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
-      i++;
-
-      paint.setPathEffect(dpe);
-      paint.setStyle(CanvasKit.PaintStyle.STROKE);
-      paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
-      paint.setAntiAlias(true);
-      paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
-
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-
-      canvas.drawPath(path, paint);
-      canvas.drawText('Try Clicking!', 10, 280, textPaint);
-
-      surface.flush();
-
-      dpe.delete();
-      path.delete();
-      window.requestAnimationFrame(drawFrame);
-    }
-    window.requestAnimationFrame(drawFrame);
-
-    // Make animation interactive
-    let interact = (e) => {
-      if (!e.pressure) {
-        return;
-      }
-      X = e.offsetX;
-      Y = e.offsetY;
-    };
-    document.getElementById('patheffect').addEventListener('pointermove', interact);
-    document.getElementById('patheffect').addEventListener('pointerdown', interact);
-    preventScrolling(document.getElementById('patheffect'));
-    // A client would need to delete this if it didn't go on for ever.
-    //paint.delete();
-  }
-
-  function PathExample(CanvasKit) {
-    const surface = CanvasKit.getRasterN32PremulSurface('paths');
-    if (!surface) {
-      console.log('Could not make surface');
-    }
-    const canvas = surface.getCanvas();
-
-    function drawFrame() {
-      const paint = new CanvasKit.SkPaint();
-      paint.setStrokeWidth(1.0);
-      paint.setAntiAlias(true);
-      paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-      paint.setStyle(CanvasKit.PaintStyle.STROKE);
-
-      const path = new CanvasKit.SkPath();
-      path.moveTo(20, 5);
-      path.lineTo(30, 20);
-      path.lineTo(40, 10);
-      path.lineTo(50, 20);
-      path.lineTo(60, 0);
-      path.lineTo(20, 5);
-
-      path.moveTo(20, 80);
-      path.cubicTo(90, 10, 160, 150, 190, 10);
-
-      path.moveTo(36, 148);
-      path.quadTo(66, 188, 120, 136);
-      path.lineTo(36, 148);
-
-      path.moveTo(150, 180);
-      path.arcTo(150, 100, 50, 200, 20);
-      path.lineTo(160, 160);
-
-      path.moveTo(20, 120);
-      path.lineTo(20, 120);
-
-      canvas.drawPath(path, paint);
-
-      surface.flush();
-
-      path.delete();
-      paint.delete();
-      // Intentionally just draw frame once
-    }
-    window.requestAnimationFrame(drawFrame);
-  }
-
-  function preventScrolling(canvas) {
-    canvas.addEventListener('touchmove', (e) => {
-      // Prevents touch events in the canvas from scrolling the canvas.
-      e.preventDefault();
-      e.stopPropagation();
-    });
-  }
-
-  function InkExample(CanvasKit) {
-    const surface = CanvasKit.getRasterN32PremulSurface('ink');
-    if (!surface) {
-      console.log('Could not make surface');
-    }
-    const canvas = surface.getCanvas();
-
-    let paint = new CanvasKit.SkPaint();
-    paint.setAntiAlias(true);
-    paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-    paint.setStyle(CanvasKit.PaintStyle.STROKE);
-    paint.setStrokeWidth(4.0);
-    paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
-
-    // Draw I N K
-    let path = new CanvasKit.SkPath();
-    path.moveTo(80, 30);
-    path.lineTo(80, 80);
-
-    path.moveTo(100, 80);
-    path.lineTo(100, 15);
-    path.lineTo(130, 95);
-    path.lineTo(130, 30);
-
-    path.moveTo(150, 30);
-    path.lineTo(150, 80);
-    path.moveTo(170, 30);
-    path.lineTo(150, 55);
-    path.lineTo(170, 80);
-
-    let paths = [path];
-    let paints = [paint];
-
-    function drawFrame() {
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-      for (let i = 0; i < paints.length && i < paths.length; i++) {
-        canvas.drawPath(paths[i], paints[i]);
-      }
-      surface.flush();
-
-      window.requestAnimationFrame(drawFrame);
-    }
-
-    let hold = false;
-    let interact = (e) => {
-      let type = e.type;
-      if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
-        hold = false;
-        return;
-      }
-      if (hold) {
-        path.lineTo(e.offsetX, e.offsetY);
-      } else {
-        paint = paint.copy();
-        paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
-        paints.push(paint);
-        path = new CanvasKit.SkPath();
-        paths.push(path);
-        path.moveTo(e.offsetX, e.offsetY);
-      }
-      hold = true;
-    };
-    document.getElementById('ink').addEventListener('pointermove', interact);
-    document.getElementById('ink').addEventListener('pointerdown', interact);
-    document.getElementById('ink').addEventListener('lostpointercapture', interact);
-    document.getElementById('ink').addEventListener('pointerup', interact);
-    preventScrolling(document.getElementById('ink'));
-    window.requestAnimationFrame(drawFrame);
-  }
-
-  function starPath(CanvasKit, X=128, Y=128, R=116) {
-    let p = new CanvasKit.SkPath();
-    p.moveTo(X + R, Y);
-    for (let i = 1; i < 8; i++) {
-      let a = 2.6927937 * i;
-      p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
-    }
-    return p;
-  }
-
-  function fps(frameTimes) {
-    let total = 0;
-    for (let ft of frameTimes) {
-      total += ft;
-    }
-    return frameTimes.length / total;
-  }
-
-  function SkottieExample(CanvasKit, id, jsonStr, bounds) {
-    if (!CanvasKit || !jsonStr) {
-      return;
-    }
-    const animation = CanvasKit.MakeAnimation(jsonStr);
-    const duration = animation.duration() * 1000;
-    const size = animation.size();
-    let c = document.getElementById(id);
-    bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
-
-    const surface = CanvasKit.getRasterN32PremulSurface(id);
-    if (!surface) {
-      console.log('Could not make surface');
-    }
-    const canvas = surface.getCanvas();
-
-    let firstFrame = new Date().getTime();
-
-    function drawFrame() {
-      let now = new Date().getTime();
-      let seek = ((now - firstFrame) / duration) % 1.0;
-      animation.seek(seek);
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
-      animation.render(canvas, bounds);
-      surface.flush();
-      window.requestAnimationFrame(drawFrame);
-    }
-    window.requestAnimationFrame(drawFrame);
-
-    //animation.delete();
-  }
-
-</script>
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 8b2fb51..2e41635 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -16,7 +16,7 @@
 
 </style>
 
-<h2> CanvasKit draws Paths to WebGL</h2>
+<h2> CanvasKit draws Paths to the browser</h2>
 <canvas id=patheffect width=300 height=300></canvas>
 <canvas id=paths width=200 height=200></canvas>
 <canvas id=ink width=300 height=300></canvas>
@@ -30,7 +30,7 @@
 <!-- Doesn't work yet. -->
 <button id=lego_btn>Take a picture of the legos</button>
 
-<script type="text/javascript" src="/node_modules/canvas-kit/bin/canvaskit.js"></script>
+<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
 
 <script type="text/javascript" charset="utf-8">
 
@@ -41,7 +41,7 @@
   var onboardingJSON = null;
   var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
   CanvasKitInit({
-    locateFile: (file) => '/node_modules/canvas-kit/bin/'+file,
+    locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
   }).then((CK) => {
     CK.initFonts();
     CanvasKit = CK;
@@ -49,7 +49,8 @@
     PathExample(CanvasKit);
     InkExample(CanvasKit);
     // Set bounds to fix the 4:3 resolution of the legos
-    SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                            {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
     // Re-size to fit
     SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
     SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
@@ -59,7 +60,8 @@
   fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
     resp.text().then((str) => {
       legoJSON = str;
-      SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+      addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                            {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
     });
   });
 
@@ -84,30 +86,35 @@
     });
   });
 
-  // crashes the lego drawing
-  const btn = document.getElementById('lego_btn');
-  btn.addEventListener('click', () => {
-    const surface = CanvasKit.getWebGLSurface('sk_legos');
+  function addScreenshotListener(surface) {
     if (!surface) {
-      console.log('Could not get lego surface');
+      return;
     }
-
-    const img = surface.makeImageSnapshot()
-    if (!img) { return }
-    const png = img.encodeToData()
-    if (!png) { return }
-    const pngBytes = CanvasKit.getSkDataBytes(png);
-    // See https://stackoverflow.com/a/12713326
-    let b64encoded = btoa(String.fromCharCode.apply(null, pngBytes));
-    console.log("base64 encoded image", b64encoded);
-  });
+    if (CanvasKit.gpu) {
+      // Doesn't work on GPU (yet)
+      document.getElementById('lego_btn').remove();
+      return;
+    }
+    const btn = document.getElementById('lego_btn');
+    btn.addEventListener('click', () => {
+      const img = surface.makeImageSnapshot()
+      if (!img) { return }
+      const png = img.encodeToData()
+      if (!png) { return }
+      const pngBytes = CanvasKit.getSkDataBytes(png);
+      // See https://stackoverflow.com/a/12713326
+      let b64encoded = btoa(String.fromCharCode.apply(null, pngBytes));
+      console.log("base64 encoded image", b64encoded);
+    });
+  }
 
   function DrawingExample(CanvasKit) {
-    const surface = CanvasKit.getWebGLSurface('patheffect');
+    const surface = CanvasKit.MakeCanvasSurface('patheffect');
     if (!surface) {
-      console.log('Could not make surface');
+      console.error('Could not make surface');
+      return;
     }
-    const context = CanvasKit.currentContext();
+    const context = CanvasKit.currentContext(); // only needed for WebGL; no-op for CPU
 
     const canvas = surface.getCanvas();
 
@@ -139,7 +146,9 @@
 
       canvas.drawPath(path, paint);
       canvas.drawText('Try Clicking!', 10, 280, textPaint);
-      canvas.flush();
+
+      surface.flush();
+
       dpe.delete();
       path.delete();
       window.requestAnimationFrame(drawFrame);
@@ -162,9 +171,10 @@
   }
 
   function PathExample(CanvasKit) {
-    const surface = CanvasKit.getWebGLSurface('paths');
+    const surface = CanvasKit.MakeCanvasSurface('paths');
     if (!surface) {
-      console.log('Could not make surface');
+      console.error('Could not make surface');
+      return;
     }
     const context = CanvasKit.currentContext();
 
@@ -202,7 +212,7 @@
 
       canvas.drawPath(path, paint);
 
-      canvas.flush();
+      surface.flush();
 
       path.delete();
       paint.delete();
@@ -220,9 +230,10 @@
   }
 
   function InkExample(CanvasKit) {
-    const surface = CanvasKit.getWebGLSurface('ink');
+    const surface = CanvasKit.MakeCanvasSurface('ink');
     if (!surface) {
-      console.log('Could not make surface');
+      console.error('Could not make surface');
+      return;
     }
     const context = CanvasKit.currentContext();
 
@@ -256,11 +267,12 @@
 
     function drawFrame() {
       CanvasKit.setCurrentContext(context);
+      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
 
       for (let i = 0; i < paints.length && i < paths.length; i++) {
         canvas.drawPath(paths[i], paints[i]);
       }
-      canvas.flush();
+      surface.flush();
 
       window.requestAnimationFrame(drawFrame);
     }
@@ -320,9 +332,10 @@
     let c = document.getElementById(id);
     bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
 
-    const surface = CanvasKit.getWebGLSurface(id);
+    const surface = CanvasKit.MakeCanvasSurface(id);
     if (!surface) {
-      console.log('Could not make surface');
+      console.error('Could not make surface');
+      return;
     }
     const context = CanvasKit.currentContext();
     const canvas = surface.getCanvas();
@@ -334,14 +347,15 @@
       let seek = ((now - firstFrame) / duration) % 1.0;
       CanvasKit.setCurrentContext(context);
       animation.seek(seek);
-
+      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
       animation.render(canvas, bounds);
-      canvas.flush();
+      surface.flush();
       window.requestAnimationFrame(drawFrame);
     }
     window.requestAnimationFrame(drawFrame);
 
     //animation.delete();
+    return surface;
   }
 
 </script>
diff --git a/experimental/canvaskit/canvaskit/node.example.js b/experimental/canvaskit/canvaskit/node.example.js
new file mode 100644
index 0000000..5149003
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/node.example.js
@@ -0,0 +1,70 @@
+console.log('hello world');
+
+const CanvasKitInit = require('./bin/canvaskit.js');
+
+CanvasKitInit({
+  locateFile: (file) => __dirname + '/bin/'+file,
+}).then((CK) => {
+  CanvasKit = CK;
+  CanvasKit.initFonts();
+  console.log('loaded');
+
+  let surface = CanvasKit.MakeSurface(300, 300);
+  const canvas = surface.getCanvas();
+
+  const paint = new CanvasKit.SkPaint();
+
+  const textPaint = new CanvasKit.SkPaint();
+  textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+  textPaint.setTextSize(30);
+  textPaint.setAntiAlias(true);
+
+  const path = starPath(CanvasKit);
+  const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
+
+  paint.setPathEffect(dpe);
+  paint.setStyle(CanvasKit.PaintStyle.STROKE);
+  paint.setStrokeWidth(5.0);
+  paint.setAntiAlias(true);
+  paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+  canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+  canvas.drawPath(path, paint);
+  canvas.drawText('Try Clicking!', 10, 280, textPaint);
+
+  surface.flush();
+
+  const img = surface.makeImageSnapshot()
+  if (!img) {
+    console.error('no snapshot');
+    return;
+  }
+  const png = img.encodeToData()
+  if (!png) {
+    console.error('encoding failure');
+    return
+  }
+  const pngBytes = CanvasKit.getSkDataBytes(png);
+  // See https://stackoverflow.com/a/12713326
+  let b64encoded = Buffer.from(pngBytes).toString('base64');
+  console.log(`<img src="data:image/png;base64,${b64encoded}" />`);
+
+  dpe.delete();
+  path.delete();
+  canvas.delete();
+  textPaint.delete();
+  paint.delete();
+
+  surface.dispose();
+});
+
+function starPath(CanvasKit, X=128, Y=128, R=116) {
+  let p = new CanvasKit.SkPath();
+  p.moveTo(X + R, Y);
+  for (let i = 1; i < 8; i++) {
+    let a = 2.6927937 * i;
+    p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+  }
+  return p;
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index 7cfdd04..9f665c1 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -35,12 +35,12 @@
 mkdir -p $BUILD_DIR
 
 GN_GPU="skia_enable_gpu=true"
-WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1"
+WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1 --pre-js $BASE_DIR/gpu.js"
 if [[ $@ == *cpu* ]]; then
   echo "Using the CPU backend instead of the GPU backend"
   GN_GPU="skia_enable_gpu=false"
   GN_GPU_FLAGS=""
-  WASM_GPU="-DSK_SUPPORT_GPU=0"
+  WASM_GPU="-DSK_SUPPORT_GPU=0 --pre-js $BASE_DIR/cpu.js"
 fi
 
 WASM_SKOTTIE="-DSK_INCLUDE_SKOTTIE=1 \
diff --git a/experimental/canvaskit/cpu.js b/experimental/canvaskit/cpu.js
new file mode 100644
index 0000000..d144664
--- /dev/null
+++ b/experimental/canvaskit/cpu.js
@@ -0,0 +1,71 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, anything that should only be on the CPU version of canvaskit.
+(function(CanvasKit){
+  CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
+  CanvasKit._extraInitializations.push(function() {
+    CanvasKit.MakeCanvasSurface = function(htmlID) {
+      var canvas = document.getElementById(htmlID);
+      if (!canvas) {
+        throw 'Canvas with id ' + htmlID + ' was not found';
+      }
+      // Maybe better to use clientWidth/height.  See:
+      // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+      var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height);
+      if (surface) {
+        surface._canvas = canvas;
+        surface._width = canvas.width;
+        surface._height = canvas.height;
+        surface._pixelLen = surface._width * surface._height * 4; // it's 8888
+        // Allocate the buffer of pixels to be used to draw back and forth.
+        surface._pixelPtr = CanvasKit._malloc(surface._pixelLen);
+      }
+      return surface;
+    };
+
+    CanvasKit.MakeSurface = function(width, height) {
+      var surface = this._getRasterN32PremulSurface(width, height);
+      if (surface) {
+        surface._canvas = null;
+        surface._width = width;
+        surface._height = height;
+        surface._pixelLen = width * height * 4; // it's 8888
+        // Allocate the buffer of pixels to be used to draw back and forth.
+        surface._pixelPtr = CanvasKit._malloc(surface._pixelLen);
+      }
+      return surface;
+    };
+
+    CanvasKit.SkSurface.prototype.flush = function() {
+      this._flush();
+      // Do we have an HTML canvas to write the pixels to?
+      if (this._canvas) {
+        var success = this._readPixels(this._width, this._height, this._pixelPtr);
+        if (!success) {
+          console.err('could not read pixels');
+          return;
+        }
+
+        var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen);
+        var imageData = new ImageData(pixels, this._width, this._height);
+
+        this._canvas.getContext('2d').putImageData(imageData, 0, 0);
+      }
+    };
+
+    // Call dispose() instead of delete to clean up the underlying memory
+    CanvasKit.SkSurface.prototype.dispose = function() {
+      if (this._pixelPtr) {
+        CanvasKit._free(this._pixelPtr);
+      }
+      this.delete();
+    }
+
+    CanvasKit.currentContext = function() {
+      // no op. aka return undefined.
+    };
+
+    CanvasKit.setCurrentContext = function() {
+      // no op. aka return undefined.
+    };
+  });
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index 801bad6..808d478 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -26,10 +26,10 @@
 	// public API (i.e. things we declare in the pre-js file)
 	Color: function(r, g, b, a) {},
 	currentContext: function() {},
-	getWebGLSurface: function(htmlID) {},
-	getRasterN32PremulSurface: function(htmlID) {},
+	MakeCanvasSurface: function(htmlID) {},
+	MakeSurface: function(w, h) {},
 	MakeSkDashPathEffect: function(intervals, phase) {},
-	setCurrentContext: function() {},
+	setCurrentContext: function(ctx) {},
 	LTRBRect: function(l, t, r, b) {},
 	gpu: {},
 	skottie: {},
@@ -39,6 +39,7 @@
 	_getWebGLSurface: function(htmlID, w, h) {},
 	_getRasterN32PremulSurface: function(w, h) {},
 	_malloc: function(size) {},
+	_free: function(ptr) {},
 	onRuntimeInitialized: function() {},
 	_MakeSkDashPathEffect: function(ptr, len, phase) {},
 
@@ -75,16 +76,17 @@
 		_rect: function(x, y, w, h) {},
 		_simplify: function() {},
 		_transform: function(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2) {},
+		delete: function() {},
 	},
 
 	SkSurface: {
 		// public API should go below because closure still will
 		// remove things declared here and not on the prototype.
 		flush: function() {},
-
 		// private API
 		_readPixels: function(w, h, ptr) {},
 		_flush: function() {},
+		delete: function() {},
 	}
 }
 
@@ -103,6 +105,7 @@
 CanvasKit.SkPath.prototype.transform = function() {};
 
 CanvasKit.SkSurface.prototype.flush = function() {};
+CanvasKit.SkSurface.prototype.dispose = function() {};
 
 // Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
 function loadWebAssemblyModule() {}
diff --git a/experimental/canvaskit/gpu.js b/experimental/canvaskit/gpu.js
new file mode 100644
index 0000000..7472fcc
--- /dev/null
+++ b/experimental/canvaskit/gpu.js
@@ -0,0 +1,24 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, anything that should only be on the GPU version of canvaskit.
+(function(CanvasKit){
+    CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
+    CanvasKit._extraInitializations.push(function() {
+        CanvasKit.MakeCanvasSurface = function(htmlID) {
+        var canvas = document.getElementById(htmlID);
+        if (!canvas) {
+          throw 'Canvas with id ' + htmlID + ' was not found';
+        }
+        // Maybe better to use clientWidth/height.  See:
+        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+        return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
+      };
+
+      CanvasKit.SkSurface.prototype.flush = function() {
+        this._flush();
+      }
+
+      CanvasKit.SkSurface.prototype.dispose = function() {
+        this.delete();
+      }
+    });
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index 938ef3e..885d7e2 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -107,53 +107,11 @@
       return this;
     };
 
-    if (CanvasKit.gpu) {
-      CanvasKit.getWebGLSurface = function(htmlID) {
-        var canvas = document.getElementById(htmlID);
-        if (!canvas) {
-          throw 'Canvas with id ' + htmlID + ' was not found';
-        }
-        // Maybe better to use clientWidth/height.  See:
-        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-        return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
-      };
-
-      CanvasKit.SkSurface.prototype.flush = function() {
-        this._flush();
-      }
-    } else {
-      CanvasKit.getRasterN32PremulSurface = function(htmlID) {
-        var canvas = document.getElementById(htmlID);
-        if (!canvas) {
-          throw 'Canvas with id ' + htmlID + ' was not found';
-        }
-        // Maybe better to use clientWidth/height.  See:
-        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-        var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height);
-        if (surface) {
-          surface.canvas = canvas;
-          surface._width = canvas.width;
-          surface._height = canvas.height;
-          surface._pixelLen = surface._width * surface._height * 4; // it's 8888
-          // Allocate the buffer of pixels to be used to draw back and forth.
-          surface._pixelPtr = CanvasKit._malloc(surface._pixelLen);
-        }
-        return surface;
-      };
-
-      CanvasKit.SkSurface.prototype.flush = function() {
-        this._flush();
-        var success = this._readPixels(this._width, this._height, this._pixelPtr);
-        if (!success) {
-          console.err('could not read pixels');
-          return;
-        }
-
-        var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen);
-        var imageData = new ImageData(pixels, this._width, this._height);
-
-        this.canvas.getContext('2d').putImageData(imageData, 0, 0);
-      };
+    // Run through the JS files that are added at compile time.
+    if (CanvasKit._extraInitializations) {
+      CanvasKit._extraInitializations.forEach(function(init) {
+        init();
+      });
     }
   } // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
 
diff --git a/experimental/canvaskit/tests/path.spec.js b/experimental/canvaskit/tests/path.spec.js
index 0be3032..8d94348 100644
--- a/experimental/canvaskit/tests/path.spec.js
+++ b/experimental/canvaskit/tests/path.spec.js
@@ -1,3 +1,6 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
 
 describe('CanvasKit\'s Path Behavior', function() {
     // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
@@ -31,14 +34,6 @@
         container.innerHTML = '';
     });
 
-    function getSurface() {
-        if (CanvasKit.gpu) {
-            return CanvasKit.getWebGLSurface('test');
-        }
-        return CanvasKit.getRasterN32PremulSurface('test');
-    }
-
-
     function reportSurface(surface, testname, done) {
         // In docker, the webgl canvas is blank, but the surface has the pixel
         // data. So, we copy it out and draw it to a normal canvas to take a picture.
@@ -66,7 +61,7 @@
     it('can draw a path', function(done) {
         LoadCanvasKit.then(() => {
             // This is taken from example.html
-            const surface = getSurface();
+            const surface = CanvasKit.MakeCanvasSurface('test');
             expect(surface).toBeTruthy('Could not make surface')
             if (!surface) {
                 done();
@@ -128,7 +123,7 @@
 
     it('can apply an effect and draw text', function(done) {
         LoadCanvasKit.then(() => {
-            const surface = getSurface();
+            const surface = CanvasKit.MakeCanvasSurface('test');
             expect(surface).toBeTruthy('Could not make surface')
             if (!surface) {
                 done();