Clean starting point for interop. VK will let us dodge this entirely.

Bug: skia:
Change-Id: I4bc961e7e1b9ef30c5bfc096fb3f81af9191e750
Reviewed-on: https://skia-review.googlesource.com/136860
Commit-Queue: Allan MacKinnon <allanmac@google.com>
Reviewed-by: Allan MacKinnon <allanmac@google.com>
diff --git a/src/compute/skc/context.c b/src/compute/skc/context.c
index 59c7956..8066dc2 100644
--- a/src/compute/skc/context.c
+++ b/src/compute/skc/context.c
@@ -28,7 +28,7 @@
 //
 
 skc_err
-skc_context_create_cl(skc_context_t * context,
+skc_context_create_cl(skc_context_t * context, 
                       cl_context      context_cl,
                       cl_device_id    device_id_cl)
 {
diff --git a/src/compute/skc/grid.c b/src/compute/skc/grid.c
index 934fa9f..fb5a073 100644
--- a/src/compute/skc/grid.c
+++ b/src/compute/skc/grid.c
@@ -63,11 +63,11 @@
 // For now and for simplicity, unify all grid ids in one set.
 //
 
-typedef skc_uchar            skc_grid_id_t;                   // 256 values
-#define SKC_GRID_ID_INVALID  SKC_UCHAR_MAX                    // 255
+typedef skc_uchar            skc_grid_id_t;  // 256 values
+#define SKC_GRID_ID_INVALID  SKC_UCHAR_MAX   // 255
 
-#define SKC_GRID_SIZE_WORDS  8                                // 256 bits
-#define SKC_GRID_SIZE_IDS    ((32 * SKC_GRID_SIZE_WORDS) - 1) // 255 ids
+#define SKC_GRID_SIZE_IDS    (SKC_GRID_ID_INVALID-1)
+#define SKC_GRID_SIZE_WORDS  ((SKC_GRID_SIZE_IDS+31)/32)
 
 //
 //
@@ -315,8 +315,13 @@
                      char    const * const execute_name,
                      char    const * const dispose_name)
 {
+  //
   // FIXME -- no more ids -- either fatal or flush & wait for grids to be released
-  assert(deps->count < SKC_GRID_SIZE_IDS);
+  //
+  // assert(deps->count < SKC_GRID_SIZE_IDS);
+  //
+  while (deps->count == SKC_GRID_SIZE_IDS)
+    skc_scheduler_wait_one(deps->scheduler);
 
   // otherwise, an id exists so decrement count
   deps->count += 1;
@@ -404,7 +409,7 @@
 skc_grid_detach(skc_grid_t const grid)
 {
   // for now make sure grid is complete
-  // assert(*grid->state >= SKC_GRID_STATE_COMPLETE);
+  // assert(grid->state == SKC_GRID_STATE_COMPLETE);
 
   // transition state
   grid->state = SKC_GRID_STATE_DETACHED;
@@ -413,6 +418,7 @@
   // FIXME -- save profiling info
   //
 
+  // cleanup
   if (skc_grid_words_set(grid->deps->active,grid->id)) // 1:inactive
     grid->deps->count -= 1;
 }
@@ -445,7 +451,7 @@
     {
       skc_grid_id_t grid_id = handle_map[SKC_TYPED_HANDLE_TO_HANDLE(handles[ii])];
 
-      if (grid_id != SKC_GRID_ID_INVALID)
+      if (grid_id < SKC_GRID_ID_INVALID)
         {
           skc_grid_t const grid = deps->grids + grid_id;
 
@@ -483,6 +489,7 @@
 skc_grid_happens_after_grid(skc_grid_t const after,
                             skc_grid_t const before)
 {
+  // declarations can't be made on non-ready grids
   assert(after->state == SKC_GRID_STATE_READY);
 
   if (before->state >= SKC_GRID_STATE_COMPLETE)
@@ -502,7 +509,7 @@
 
   skc_uint const id_before = after->deps->handle_map[before];
 
-  if (id_before == SKC_GRID_ID_INVALID)
+  if (id_before >= SKC_GRID_ID_INVALID)
     return;
 
   if (skc_grid_words_set(after->before.words,id_before))
@@ -668,7 +675,7 @@
           if (idx == 32)
             {
               active  = *after_words++;
-              after  += 1;
+              after  += 32;
               continue;
             }
           else // clear active
diff --git a/src/compute/skc/interop.h b/src/compute/skc/interop.h
new file mode 100644
index 0000000..555f3c4
--- /dev/null
+++ b/src/compute/skc/interop.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can
+ * be found in the LICENSE file.
+ *
+ */
+
+#pragma once
+
+//
+//
+//
+
+#include "skc.h"
+
+//
+//
+//
+
+struct skc_interop *
+skc_interop_create();
+
+void
+skc_interop_destroy(struct skc_interop * interop);
+
+cl_context_properties
+skc_interop_get_wgl_context();
+
+cl_context_properties
+skc_interop_get_wgl_dc();
+
+void
+skc_interop_set_cl_context(struct skc_interop * interop,
+                           cl_context           context_cl);
+
+bool
+skc_interop_poll(struct skc_interop * interop,
+                 int                *  key);
+
+void
+skc_interop_transform(struct skc_interop         * interop,
+                      struct skc_transform_stack * ts);
+
+bool
+skc_interop_should_exit(struct skc_interop * interop);
+
+skc_framebuffer_t
+skc_interop_get_framebuffer();
+
+void
+skc_interop_blit(struct skc_interop * interop);
+
+void
+skc_interop_get_size(struct skc_interop * interop, 
+                     uint32_t           * width,
+                     uint32_t           * height);
+//
+//
+//
diff --git a/src/compute/skc/main.c b/src/compute/skc/main.c
index e0d42b3..fe37324 100644
--- a/src/compute/skc/main.c
+++ b/src/compute/skc/main.c
@@ -10,19 +10,10 @@
 //
 //
 
-#include <glad/glad.h>
-#include <GLFW/glfw3.h>
-
-//
-//
-//
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <conio.h>
 
-#include "skc_create_cl.h"
-
 #include "common/cl/find_cl.h"
 #include "common/cl/assert_cl.h"
 
@@ -34,35 +25,44 @@
 //
 //
 
-#include <CL/opencl.h>
-#include "platforms/cl_12/gl/interop.h"
+#include "platforms/cl_12/skc_cl.h"
+#include "interop.h"
 
 //
 //
 //
 
-void
-skc_runtime_cl_12_debug(struct skc_context * const context);
+typedef enum skc_pipeline_start_at_e {
+  SKC_PIPELINE_START_AT_DEFINE_PATHS = '1',
+  SKC_PIPELINE_START_AT_RASTERIZE    = '2',
+  SKC_PIPELINE_START_AT_COMPOSITION  = '3',
+  SKC_PIPELINE_START_AT_RENDER       = '4'
+} skc_pipeline_start_at_e;
 
 //
-//
+// Callback for explicitly waiting for render completion
 //
 
-
-
-//
-//
-//
-
+#if 0
 static
 void
 is_render_complete(skc_surface_t     surface,
                    skc_styling_t     styling,
                    skc_composition_t composition,
-                   bool            * quit)
+                   skc_framebuffer_t fb,
+                   void            * data)
 {
-  *quit = true;
+  // exit while loop
+  *(bool*)data = true;
 }
+#endif
+
+//
+// FIXME - for debugging purposes declare this internal prototype
+//
+
+void
+skc_runtime_cl_12_debug(struct skc_context * const context);
 
 //
 //
@@ -83,8 +83,6 @@
   //
   // load test file
   //
-  // #include "test/lion.inl"
-
   struct svg_doc * svg_doc = svg_doc_parse(argv[1],false);
 
   fprintf(stderr,"p/r/l = %u / %u / %u\n",
@@ -95,9 +93,7 @@
   //
   // fire up GL
   //
-  GLFWwindow * window;
-
-  skc_interop_init(&window);
+  struct skc_interop * interop = skc_interop_create();
 
   //
   // find platform and device by name
@@ -112,21 +108,19 @@
                    true));
 
   //
-  // get GL and device contexts
+  // create the CL context with GL interop
   //
-  HGLRC hGLRC = wglGetCurrentContext();
-  HDC   hDC   = wglGetCurrentDC();
-
-  //
-  // create the CL context
-  //
+#ifdef _WIN32
   cl_context_properties context_properties_cl[] =
     {
       CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id_cl,
-      CL_GL_CONTEXT_KHR,   (cl_context_properties)hGLRC,
-      CL_WGL_HDC_KHR,      (cl_context_properties)hDC,
+      CL_GL_CONTEXT_KHR,   skc_interop_get_wgl_context(),
+      CL_WGL_HDC_KHR,      skc_interop_get_wgl_dc(),
       0
     };
+#else
+#error "Missing a system-compatible context!"
+#endif
 
   cl_int     cl_err;
   cl_context context_cl = clCreateContext(context_properties_cl,
@@ -136,6 +130,11 @@
                                           NULL,
                                           &cl_err); cl_ok(cl_err);
   //
+  // register cl_context with GL interop
+  //
+  skc_interop_set_cl_context(interop,context_cl);
+
+  //
   // create SKC context
   //
   skc_context_t context;
@@ -145,11 +144,6 @@
                                       device_id_cl);
 
   //
-  // associate
-  //
-  skc_interop_register(context);
-
-  //
   // create path builder
   //
   skc_path_builder_t path_builder;
@@ -199,76 +193,125 @@
   //
   // rasterize, render and reclaim svg until escape
   //
-  while (!glfwWindowShouldClose(window))
+  skc_pipeline_start_at_e pipeline_start_at_base = SKC_PIPELINE_START_AT_DEFINE_PATHS;
+  skc_pipeline_start_at_e pipeline_start_at_loop = SKC_PIPELINE_START_AT_DEFINE_PATHS;  
+  skc_path_t            * paths;
+  skc_raster_t          * rasters;
+
+  while (!skc_interop_should_exit(interop))
     {
-      // save stack
-      uint32_t const ts_save = skc_transform_stack_save(ts);
+      // redefine the paths?
+      if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_DEFINE_PATHS)
+        {
+          // decode paths
+          paths = svg_doc_paths_decode(svg_doc,path_builder);
+        }
 
-      // poll glfw
-      skc_interop_poll(window,ts);
+      // rasterize the paths?
+      if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_RASTERIZE)
+        {
+          // save stack
+          uint32_t const ts_save = skc_transform_stack_save(ts);
 
-      // decode paths
-      skc_path_t * paths = svg_doc_paths_decode(svg_doc,path_builder);
+          // update transform
+          skc_interop_transform(interop,ts);
 
-      // decode rasters
-      skc_raster_t * rasters = svg_doc_rasters_decode(svg_doc,ts,paths,raster_builder);
+          // decode rasters
+          rasters = svg_doc_rasters_decode(svg_doc,ts,paths,raster_builder);
 
-      // restore the transform stack
-      skc_transform_stack_restore(ts,ts_save);
+          // restore the transform stack
+          skc_transform_stack_restore(ts,ts_save);
+        }
 
-      // decode layers -- places rasters
-      svg_doc_layers_decode(svg_doc,rasters,composition,styling,true/*is_srgb*/);
+      // decode the styling and composition?
+      if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_COMPOSITION)
+        {
+          // reset styling
+          skc_styling_reset(styling);
 
-      // seal the composition
-      skc_composition_seal(composition);
+          // unseal and reset the composition
+          skc_composition_unseal(composition,true);
 
-      bool           quit   = false;
+          // decode layers -- places rasters
+          svg_doc_layers_decode(svg_doc,rasters,composition,styling,true/*is_srgb*/);
+
+          // seal the styling -- render will seal if not called
+          skc_styling_seal(styling);
+
+          // seal the composition -- render will seal if not called
+          skc_composition_seal(composition);
+        }
+
       uint32_t const clip[] = { 0, 0, 65535, 65535 }; // tile clip is <= 9 bits (512)
 
       // render the styled composition to the surface
-      skc_surface_render(surface,clip,styling,composition,
-                         is_render_complete,&quit,
-                         skc_interop_get_fb(window));
+      skc_surface_render(surface,
+                         styling,
+                         composition,
+                         skc_interop_get_framebuffer(interop),
+                         clip,
+                         NULL,
+                         NULL);
 
-      // release the paths
-      svg_doc_paths_release(svg_doc,paths,context);
+      //
+      // poll for events and maybe start from a different point in the
+      // pipeline
+      //
+      int key;
 
-      // rasters have been released
-      svg_doc_rasters_release(svg_doc,rasters,context);
+      // poll for window events
+      bool const transform_changed = skc_interop_poll(interop,&key);
 
-      // spin until framebuffer is rendered
+      // how many blocks are in use?
+      if (key == 'I')
+        skc_runtime_cl_12_debug(context);
+
+      // do we only want to run part of the pipeline?
+      if ((key >= SKC_PIPELINE_START_AT_DEFINE_PATHS) && (key <= SKC_PIPELINE_START_AT_RENDER))
+        pipeline_start_at_base = key;
+      
+      // valid for a loop
+      pipeline_start_at_loop = pipeline_start_at_base;
+
+      // if the transform changed then we must start at rasterize or before
+      if (transform_changed)
+        pipeline_start_at_loop = min(pipeline_start_at_loop,SKC_PIPELINE_START_AT_RASTERIZE);
+
+      if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_COMPOSITION)
+        {
+          // rewind the svg doc
+          svg_doc_rewind(svg_doc);
+
+          if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_DEFINE_PATHS)
+            {
+              // release the paths
+              svg_doc_paths_release(svg_doc,paths,context);
+            }
+
+          if (pipeline_start_at_loop <= SKC_PIPELINE_START_AT_RASTERIZE)
+            {
+              // release the rasters
+              svg_doc_rasters_release(svg_doc,rasters,context);
+            }
+        }
+
+#if 0
+      //
+      // Note that we don't need to explicitly wait for the render()
+      // to complete since SKC is fully concurrent and the styling and
+      // compsition unseal() operations will "clock" the render loop.
+      //
+
+      //
+      // explicitly spin until framebuffer is rendered
+      //
+      bool quit = false;
+
       while (!quit) {
         // fprintf(stderr,"WAITING ON: !quit\n");
         skc_context_wait(context);
       }
-
-      // blit and swap
-      skc_interop_blit(window);
-
-      // print out some useful debug info
-      skc_runtime_cl_12_debug(context);
-
-      // rewind the doc
-      svg_doc_rewind(svg_doc);
-
-      //
-      // I don't like this being here
-      //
-      float    const rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
-      uint32_t       rect[4] = { 0 };
-
-      skc_interop_get_dim(rect+2);
-
-      skc_surface_clear(surface,rgba,rect,
-                        skc_interop_get_fb(window));
-
-      // exit(EXIT_SUCCESS);
-
-      // reset styling
-      skc_styling_reset(styling);
-
-      // unseal the composition
-      skc_composition_unseal(composition,true);
+#endif
     }
 
   //
@@ -287,10 +330,9 @@
   err = skc_context_release(context);
 
   //
-  // GLFW CLEANUP
+  // dispose of GL interop
   //
-  glfwDestroyWindow(window);
-  glfwTerminate();
+  skc_interop_destroy(interop);
 
   //
   //
diff --git a/src/compute/skc/platforms/cl_12/composition_cl_12.h b/src/compute/skc/platforms/cl_12/composition_cl_12.h
index 4f52090..81a544f 100644
--- a/src/compute/skc/platforms/cl_12/composition_cl_12.h
+++ b/src/compute/skc/platforms/cl_12/composition_cl_12.h
@@ -47,8 +47,8 @@
   skc_int                         lock_count; // wip renders
 
   struct { 
-    skc_grid_t                    sort;
     skc_grid_t                    place;
+    skc_grid_t                    sort;
   } grids;
 
   cl_command_queue                cq;
diff --git a/src/compute/skc/platforms/cl_12/cq_pool_cl.c b/src/compute/skc/platforms/cl_12/cq_pool_cl.c
index 8d1537d..2e37937 100644
--- a/src/compute/skc/platforms/cl_12/cq_pool_cl.c
+++ b/src/compute/skc/platforms/cl_12/cq_pool_cl.c
@@ -46,7 +46,7 @@
 cl_command_queue
 skc_runtime_cl_12_create_cq(struct skc_runtime * const runtime,
                             struct skc_cq_pool * const pool)
-
+                      
 {
   cl_command_queue cq;
 
@@ -59,7 +59,7 @@
       cq = clCreateCommandQueue(runtime->cl.context,
                                 runtime->cl.device_id,
                                 pool->cq_props,
-                                &cl_err); cl_ok(cl_err);
+                                &cl_err); cl_ok(cl_err);  
 #else
   if (runtime_cl->version.major < 2)
     {
@@ -71,7 +71,7 @@
       cq = clCreateCommandQueue(runtime_cl->context,
                                 runtime_cl->device_id,
                                 (cl_command_queue_properties)type,
-                                &cl_err); cl_ok(cl_err);
+                                &cl_err); cl_ok(cl_err);  
     }
   else
     {
@@ -135,7 +135,7 @@
 //
 //
 
-static
+static 
 void
 skc_cq_pool_write(struct skc_cq_pool * const pool,
                   cl_command_queue           cq)
@@ -174,7 +174,7 @@
 //
 //
 
-static
+static 
 cl_command_queue
 skc_cq_pool_read(struct skc_runtime * const runtime,
                  struct skc_cq_pool * const pool)
@@ -199,7 +199,7 @@
 }
 
 void
-skc_runtime_release_cq_in_order(struct skc_runtime * const runtime,
+skc_runtime_release_cq_in_order(struct skc_runtime * const runtime, 
                                 cl_command_queue           cq)
 {
   skc_cq_pool_write(&runtime->cq_pool,cq);
diff --git a/src/compute/skc/platforms/cl_12/gl/interop.c b/src/compute/skc/platforms/cl_12/gl/interop.c
deleted file mode 100644
index 6697bb7..0000000
--- a/src/compute/skc/platforms/cl_12/gl/interop.c
+++ /dev/null
@@ -1,629 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can
- * be found in the LICENSE file.
- *
- */
-
-//
-//
-//
-
-#include <glad/glad.h>
-#include <glfw/glfw3.h>
-
-//
-//
-//
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdbool.h>
-#include <math.h>
-
-//
-//
-//
-
-#include "common/cl/assert_cl.h"
-#include "types.h"
-
-//
-//
-//
-
-#include "interop.h"
-#include "context.h"
-#include "runtime_cl_12.h"
-
-//
-//
-//
-
-#include "svg2skc/transform_stack.h"
-
-//
-//
-//
-
-#if 1
-#define SKC_IMAGE_FORMAT GL_RGBA8
-#else
-#define SKC_IMAGE_FORMAT GL_RGBA16F
-#endif
-
-//
-//
-//
-
-#ifndef M_PI
-#define M_PI 3.14159265358979323846
-#endif
-
-//
-//
-//
-
-struct skc_interop_fb
-{
-  cl_context context;
-
-  GLuint     fbo;
-  GLuint     rbo;
-
-  cl_mem     mem;
-
-  int        width;
-  int        height;
-
-  bool       is_srgb;
-  bool       is_vsync_on;
-  bool       is_fullscreen;
-  bool       is_iconified;
-  bool       is_resized;
-  bool       is_spinning;
-  bool       is_info;
-
-  skc_float  scale;
-  skc_float2 translate;
-  float      rotate_theta;
-};
-
-static struct skc_interop_fb fb =
-  {
-    .mem           = NULL,
-
-    .is_srgb       = true,
-    .is_vsync_on   = false,
-    .is_fullscreen = false,
-    .is_iconified  = false,
-    .is_resized    = true,
-    .is_spinning   = false,
-    .is_info       = false,
-
-    .scale         = 1.0f,
-    .translate     = { 0.0f, 0.0f },
-    .rotate_theta  = 0.0f
-  };
-
-//
-// FPS COUNTER FROM HERE:
-//
-// http://antongerdelan.net/opengl/glcontext2.html
-//
-
-static
-void
-skc_interop_fps(GLFWwindow * window)
-{
-  if (fb.is_fullscreen)
-    return;
-
-  // static fps counters
-  static double stamp_prev  = 0.0;
-  static int    frame_count = 0;
-
-  // locals
-  double const  stamp_curr  = glfwGetTime();
-  double const  elapsed     = stamp_curr - stamp_prev;
-
-  if (elapsed >= 0.5)
-    {
-      stamp_prev = stamp_curr;
-
-      double const fps = (double)frame_count / elapsed;
-
-      char tmp[64];
-
-      sprintf_s(tmp,64,"(%d x %d) - VSync %s - sRGB %s - FPS: %.2f",
-                fb.width,fb.height,
-                fb.is_vsync_on ? "ON"      : "OFF",
-                fb.is_srgb     ? "ENABLED" : "DISABLED",
-                fps);
-
-      glfwSetWindowTitle(window,tmp);
-
-      frame_count = 0;
-    }
-
-  frame_count++;
-}
-
-//
-// INITIALIZE GLFW/GLAD
-//
-
-static
-void
-skc_interop_error_callback(int error, char const * description)
-{
-  fputs(description,stderr);
-}
-
-//
-//
-//
-
-static
-void
-skc_interop_iconify_callback(GLFWwindow * window, int iconified)
-{
-  fb.is_iconified = iconified;
-}
-
-//
-//
-//
-
-static
-void
-skc_interop_key_callback(GLFWwindow * window, int key, int scancode, int action, int mods)
-{
-  if (action == GLFW_RELEASE)
-    return;
-
-  switch (key)
-    {
-    case GLFW_KEY_EQUAL:
-      fb.rotate_theta = 0.0f;
-      break;
-
-    case GLFW_KEY_I:
-      fb.is_info = true;
-      break;
-
-    case GLFW_KEY_R:
-      fb.is_spinning ^= true;
-      break;
-
-    case GLFW_KEY_S:
-      fb.is_srgb ^= true;
-      if (fb.is_srgb)
-        glEnable(GL_FRAMEBUFFER_SRGB);
-      else
-        glDisable(GL_FRAMEBUFFER_SRGB);
-      break;
-
-    case GLFW_KEY_V:
-      fb.is_vsync_on ^= true;
-      glfwSwapInterval(fb.is_vsync_on ? 1 : 0);
-      break;
-
-    case GLFW_KEY_W:
-      glfwSetWindowSize(window,1024,1024);
-      break;
-
-    case GLFW_KEY_ESCAPE:
-      glfwSetWindowShouldClose(window,GL_TRUE);
-      break;
-    }
-}
-
-static
-void
-skc_interop_window_size_callback(GLFWwindow * window, int width, int height)
-{
-  fb.width      = width;
-  fb.height     = height;
-  fb.is_resized = true;
-
-#if 0
-  skc_render_kernel_set_clip(0,0,width,height);
-#endif
-}
-
-static
-void
-skc_interop_scale(double const scale_offset)
-{
-#define SKC_SCALE_FACTOR 1.05
-
-  static double scale_exp = 0.0;
-
-  scale_exp += scale_offset;
-  fb.scale   = (float)pow(SKC_SCALE_FACTOR,scale_exp);
-}
-
-static
-void
-skc_interop_scroll_callback(GLFWwindow * window, double xoffset, double yoffset)
-{
-  bool const ctrl =
-    (glfwGetKey(window,GLFW_KEY_LEFT_CONTROL)  == GLFW_PRESS) ||
-    (glfwGetKey(window,GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS);
-
-  if (!ctrl)
-    return;
-
-  skc_interop_scale(yoffset);
-}
-
-static
-void
-skc_interop_translate(float const dx, float const dy)
-{
-  float const dx_scaled = dx / fb.scale;
-  float const dy_scaled = dy / fb.scale;
-
-  float const cos_theta = cosf(fb.rotate_theta); // replace with cospi if available
-  float const sin_theta = sinf(fb.rotate_theta); // replace with sinpi if available
-
-  fb.translate.x += dx_scaled*cos_theta + dy_scaled*sin_theta;
-  fb.translate.y += dy_scaled*cos_theta - dx_scaled*sin_theta;
-}
-
-static
-void
-skc_interop_cursor_position_callback(GLFWwindow * window, double x, double y)
-{
-  int const state = glfwGetMouseButton(window,GLFW_MOUSE_BUTTON_LEFT);
-
-  static bool  is_mouse_dragging = false;
-  static float x_prev=0.0, y_prev=0.0;
-
-  float const mx = (float)x;
-  float const my = (float)y;
-
-  if (state == GLFW_PRESS)
-    {
-      if (is_mouse_dragging)
-        {
-          const bool ctrl =
-            (glfwGetKey(window,GLFW_KEY_LEFT_CONTROL)  == GLFW_PRESS) ||
-            (glfwGetKey(window,GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS);
-
-          if (ctrl)
-            {
-              float const cx  = 0.5f * fb.width;
-              float const cy  = 0.5f * fb.height;
-
-              // find angle between mouse and center
-              float const vx  = x_prev - cx;
-              float const vy  = y_prev - cy;
-
-              float const wx  = mx - cx;
-              float const wy  = my - cy;
-
-              float const len = sqrtf((vx*vx + vy*vy) * (wx*wx + wy*wy));
-
-              if (len > 0.0f)
-                {
-                  float const dot = vx*wx + vy*wy;
-                  float const da  = acosf(dot / len);
-
-                  if (vx*wy - vy*wx >= 0.0f)
-                    fb.rotate_theta += da;
-                  else
-                    fb.rotate_theta -= da;
-
-                  fb.rotate_theta = fmodf(fb.rotate_theta,(float)(M_PI*2.0));
-                }
-            }
-          else
-            {
-              skc_interop_translate(mx - x_prev,
-                                    my - y_prev);
-            }
-        }
-      else
-        {
-          is_mouse_dragging = true;
-        }
-
-      x_prev = mx;
-      y_prev = my;
-    }
-  else
-    {
-      is_mouse_dragging = false;
-    }
-}
-
-//
-//
-//
-
-static
-void
-skc_interop_resize()
-{
-  fb.is_resized = false;
-
-  // release the image2d
-  if (fb.mem != NULL)
-    cl(ReleaseMemObject(fb.mem));
-
-  // resize rbo
-  glNamedRenderbufferStorage(fb.rbo,
-                             SKC_IMAGE_FORMAT,
-                             fb.width,
-                             fb.height);
-
-  // attach rbo to fbo
-  glNamedFramebufferRenderbuffer(fb.fbo,
-                                 GL_COLOR_ATTACHMENT0,
-                                 GL_RENDERBUFFER,
-                                 fb.rbo);
-  //
-  //
-  //
-  cl_int cl_err;
-
-  fb.mem = clCreateFromGLRenderbuffer(fb.context,
-                                      CL_MEM_WRITE_ONLY,
-                                      fb.rbo,
-                                      &cl_err); cl_ok(cl_err);
-  //
-  // for debugging porpoises!
-  //
-  cl_image_format format;
-
-  cl(GetImageInfo(fb.mem,
-                  CL_IMAGE_FORMAT,
-                  sizeof(format),
-                  &format,
-                  NULL));
-}
-
-//
-//
-//
-
-static
-void
-skc_interop_acquire()
-{
-  // frame buffer object
-  glCreateFramebuffers(1,&fb.fbo);
-
-  // render buffer object w/a color buffer
-  glCreateRenderbuffers(1,&fb.rbo);
-
-  // size rbo
-  glNamedRenderbufferStorage(fb.rbo,
-                             SKC_IMAGE_FORMAT,
-                             fb.width,
-                             fb.height);
-
-  // attach rbo to fbo
-  glNamedFramebufferRenderbuffer(fb.fbo,
-                                 GL_COLOR_ATTACHMENT0,
-                                 GL_RENDERBUFFER,
-                                 fb.rbo);
-}
-
-void
-skc_interop_register(skc_context_t context)
-{
-  fb.context = context->runtime->cl.context;
-}
-
-//
-//
-//
-
-void
-skc_interop_init(GLFWwindow * * window)
-{
-  //
-  // INITIALIZE GLFW/GLAD
-  //
-  glfwSetErrorCallback(skc_interop_error_callback);
-
-  if (!glfwInit())
-    exit(EXIT_FAILURE);
-
-  GLFWmonitor       * const primary = glfwGetPrimaryMonitor();
-  GLFWvidmode const * const mode    = glfwGetVideoMode(primary);
-
-  if (fb.is_fullscreen)
-    {
-      fb.width  = mode->width;
-      fb.height = mode->height;
-    }
-  else
-    {
-      fb.width  = 1600;
-      fb.height = 1024;
-    }
-
-  glfwWindowHint(GLFW_ALPHA_BITS,            0);
-  glfwWindowHint(GLFW_DEPTH_BITS,            0);
-  glfwWindowHint(GLFW_STENCIL_BITS,          0);
-
-  glfwWindowHint(GLFW_SRGB_CAPABLE,          GL_TRUE);
-
-  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
-  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
-
-  glfwWindowHint(GLFW_OPENGL_PROFILE,        GLFW_OPENGL_CORE_PROFILE);
-
-  *window = glfwCreateWindow(fb.width,fb.height,
-                             "Skia Compute",
-                             fb.is_fullscreen ? primary : NULL,
-                             NULL);
-
-  if (*window == NULL)
-    {
-      glfwTerminate();
-      exit(EXIT_FAILURE);
-    }
-
-  glfwMakeContextCurrent(*window);
-
-  // set up GLAD
-  gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
-
-  // ignore vsync for now
-  glfwSwapInterval(fb.is_vsync_on ? 1 : 0);
-
-  // only copy r/g/b
-  glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_FALSE);
-
-  // enable SRGB, disable scissor
-  glEnable(GL_FRAMEBUFFER_SRGB);
-  glDisable(GL_SCISSOR_TEST);
-
-  //
-  // SET USER POINTER AND CALLBACKS
-  //
-  glfwSetKeyCallback            (*window,skc_interop_key_callback);
-  glfwSetFramebufferSizeCallback(*window,skc_interop_window_size_callback);
-  glfwSetScrollCallback         (*window,skc_interop_scroll_callback);
-  glfwSetCursorPosCallback      (*window,skc_interop_cursor_position_callback);
-  glfwSetWindowIconifyCallback  (*window,skc_interop_iconify_callback);
-
-  //
-  //
-  //
-  fprintf(stderr,
-          "GL_VENDOR   : %s\n"
-          "GL_RENDERER : %s\n",
-          glGetString(GL_VENDOR),
-          glGetString(GL_RENDERER));
-
-  //
-  // acquire an FBO/RBO
-  //
-  skc_interop_acquire();
-}
-
-//
-//
-//
-
-#define SKC_ROTATE_STEP ((float)(M_PI / 180.0))
-
-static
-void
-skc_interop_transform(struct skc_transform_stack * ts)
-{
-  // OpenGL'ism
-  skc_transform_stack_push_affine(ts,
-                                  1.0f, 0.0f,0.0f,
-                                  0.0f,-1.0f,(float)fb.height);
-  // multiply
-  skc_transform_stack_concat(ts);
-
-  // spinner...
-  if (fb.is_spinning)
-    fb.rotate_theta = fmodf(fb.rotate_theta + SKC_ROTATE_STEP,(float)(M_PI*2.0));
-  
-  // always rotate and scale around surface center point
-  skc_transform_stack_push_rotate_scale_xy(ts,
-                                           fb.rotate_theta,
-                                           fb.scale,fb.scale,
-                                           0.5f*fb.width,0.5f*fb.height);
-  skc_transform_stack_concat(ts);
-
-  // where did the mouse take us?
-  skc_transform_stack_push_translate(ts,
-                                     fb.translate.x,fb.translate.y);
-  skc_transform_stack_concat(ts);
-}
-
-
-void
-skc_interop_poll(GLFWwindow                 * window,
-                 struct skc_transform_stack * ts)
-{
-  // wait until uniconified
-  while (fb.is_iconified)
-    {
-      glfwWaitEvents();
-      continue;
-    }
-
-  // what's happended?
-  glfwPollEvents();
-
-  // resize?
-  if (fb.is_resized)
-    skc_interop_resize();
-
-  // monitor fps
-  skc_interop_fps(window);
-
-  skc_interop_transform(ts);
-}
-
-//
-//
-//
-
-void
-skc_interop_blit(GLFWwindow * window)
-{
-  // blit skc rbo
-  glBlitNamedFramebuffer(fb.fbo,0,
-                         0,0,fb.width,fb.height,
-                         0,0,fb.width,fb.height,
-                         GL_COLOR_BUFFER_BIT,
-                         GL_NEAREST);
-
-#if 0
-  //
-  // FIXME -- this clear does nothing!
-  //
-  // As a hack we're clearing the interop'd RBO with a
-  // clEnqueueFillImage().
-  //
-  float const rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
-  // GLenum const attachments[] = { GL_COLOR_ATTACHMENT0 };
-  // glInvalidateNamedFramebufferData(fb.fbo,1,attachments);
-  glClearNamedFramebufferfv(fb.fbo,GL_COLOR,0,rgba);
-#endif
-
-  // swap buffers
-  glfwSwapBuffers(window);
-}
-
-//
-//
-//
-
-void *
-skc_interop_get_fb(GLFWwindow * window)
-{
-  glFlush();
-
-  return fb.mem;
-}
-
-//
-//
-//
-
-void
-skc_interop_get_dim(uint32_t dim[2])
-{
-  dim[0] = fb.width;
-  dim[1] = fb.height;
-}
-
-//
-//
-//
-
-
diff --git a/src/compute/skc/platforms/cl_12/gl/interop.h b/src/compute/skc/platforms/cl_12/gl/interop.h
deleted file mode 100644
index 112d365..0000000
--- a/src/compute/skc/platforms/cl_12/gl/interop.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can
- * be found in the LICENSE file.
- *
- */
-
-#pragma once
-
-//
-//
-//
-
-#include "skc.h"
-
-//
-//
-//
-
-void
-skc_interop_init(GLFWwindow * * window);
-
-void
-skc_interop_register(skc_context_t context);
-
-void
-skc_interop_poll(GLFWwindow                 * window,
-                 struct skc_transform_stack * ts);
-
-void *
-skc_interop_get_fb(GLFWwindow * window);
-
-void
-skc_interop_get_dim(uint32_t dim[2]);
-
-void
-skc_interop_blit(GLFWwindow * window);
-
-//
-//
-//
diff --git a/src/compute/skc/platforms/cl_12/interop/interop_glfw.c b/src/compute/skc/platforms/cl_12/interop/interop_glfw.c
new file mode 100644
index 0000000..a5c0bfc
--- /dev/null
+++ b/src/compute/skc/platforms/cl_12/interop/interop_glfw.c
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can
+ * be found in the LICENSE file.
+ *
+ */
+
+//
+//
+//
+
+#include <glad/glad.h>
+#include <glfw/glfw3.h>
+
+//
+//
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+
+//
+//
+//
+
+#include "common/cl/assert_cl.h"
+
+//
+//
+//
+
+#include "interop.h"
+
+//
+//
+//
+
+#include "skc_cl.h"
+#include "runtime_cl_12.h"
+
+//
+//
+//
+
+#include "svg2skc/transform_stack.h"
+
+//
+//
+//
+
+#if 1
+#define SKC_IMAGE_FORMAT GL_RGBA8
+#else
+#define SKC_IMAGE_FORMAT GL_RGBA16F
+#endif
+
+//
+//
+//
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//
+//
+//
+
+struct skc_interop
+{
+  GLFWwindow              * window;
+
+  cl_context                context_cl;
+
+  GLuint                    fbo;
+  GLuint                    rbo;
+
+  struct skc_framebuffer_cl fb;
+
+  int                       width;
+  int                       height;
+
+  bool                      is_msecs;
+  bool                      is_srgb;
+  bool                      is_vsync_on;
+  bool                      is_fullscreen;
+  bool                      is_iconified;
+  bool                      is_resized;
+  bool                      is_spinning;
+  bool                      is_transform;
+
+  skc_float                 scale;
+  skc_float2                translate;
+  float                     rotate_theta;
+
+  int                       key;
+};
+
+//
+// INITIALIZE GLFW/GLAD
+//
+
+static
+void
+skc_interop_error_callback(int error, char const * description)
+{
+  fputs(description,stderr);
+}
+
+//
+//
+//
+
+static
+void
+skc_interop_iconify_callback(GLFWwindow * window, int iconified)
+{
+  struct skc_interop * interop = glfwGetWindowUserPointer(window);
+
+  interop->is_iconified = iconified;
+}
+
+//
+//
+//
+
+static
+void
+skc_interop_key_callback(GLFWwindow * window, int key, int scancode, int action, int mods)
+{
+  struct skc_interop * interop = glfwGetWindowUserPointer(window);
+  
+  if (action == GLFW_RELEASE)
+    return;
+
+  switch (key)
+    {
+    case GLFW_KEY_EQUAL:
+      interop->rotate_theta = 0.0f;
+      interop->is_transform = true;
+      break;
+
+    case GLFW_KEY_M:
+      interop->is_msecs ^= true;
+      break;
+
+    case GLFW_KEY_R:
+      interop->is_spinning ^= true;
+      break;
+
+    case GLFW_KEY_S:
+      interop->is_srgb ^= true;
+      if (interop->is_srgb)
+        glEnable(GL_FRAMEBUFFER_SRGB);
+      else
+        glDisable(GL_FRAMEBUFFER_SRGB);
+      break;
+
+    case GLFW_KEY_V:
+      interop->is_vsync_on ^= true;
+      glfwSwapInterval(interop->is_vsync_on ? 1 : 0);
+      break;
+
+    case GLFW_KEY_W:
+      glfwSetWindowSize(window,1024,1024);
+      break;
+
+    case GLFW_KEY_ESCAPE:
+      glfwSetWindowShouldClose(window,GL_TRUE);
+      break;
+
+    default:
+      interop->key = key;
+    }
+}
+
+static
+void
+skc_interop_window_size_callback(GLFWwindow * window, int width, int height)
+{
+  struct skc_interop * interop = glfwGetWindowUserPointer(window);
+  
+  interop->width        = width;
+  interop->height       = height;
+  interop->is_resized   = true;
+  interop->is_transform = true;
+
+#if 0
+  skc_render_kernel_set_clip(0,0,width,height);
+#endif
+}
+
+static
+void
+skc_interop_scale(struct skc_interop * interop, double const scale_offset)
+{
+#define SKC_SCALE_FACTOR 1.05
+
+  static double scale_exp = 0.0;
+
+  scale_exp += scale_offset;
+
+  interop->scale = (float)pow(SKC_SCALE_FACTOR,scale_exp);
+}
+
+static
+void
+skc_interop_scroll_callback(GLFWwindow * window, double xoffset, double yoffset)
+{
+  bool const ctrl =
+    (glfwGetKey(window,GLFW_KEY_LEFT_CONTROL)  == GLFW_PRESS) ||
+    (glfwGetKey(window,GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS);
+
+  if (!ctrl)
+    return;
+
+  struct skc_interop * interop = glfwGetWindowUserPointer(window);  
+
+  skc_interop_scale(interop,yoffset);
+
+  interop->is_transform = true;
+}
+
+static
+void
+skc_interop_translate(struct skc_interop * interop, float const dx, float const dy)
+{
+  float const dx_scaled = dx / interop->scale;
+  float const dy_scaled = dy / interop->scale;
+
+  float const cos_theta = cosf(interop->rotate_theta); // replace with cospi if available
+  float const sin_theta = sinf(interop->rotate_theta); // replace with sinpi if available
+
+  interop->translate.x += dx_scaled*cos_theta + dy_scaled*sin_theta;
+  interop->translate.y += dy_scaled*cos_theta - dx_scaled*sin_theta;
+}
+
+static
+void
+skc_interop_cursor_position_callback(GLFWwindow * window, double x, double y)
+{
+  
+  int const state = glfwGetMouseButton(window,GLFW_MOUSE_BUTTON_LEFT);
+
+  static bool  is_mouse_dragging = false;
+  static float x_prev=0.0, y_prev=0.0;
+
+  float const mx = (float)x;
+  float const my = (float)y;
+
+  if (state == GLFW_PRESS)
+    {
+      struct skc_interop * interop = glfwGetWindowUserPointer(window);  
+      
+      if (is_mouse_dragging)
+        {
+          const bool ctrl =
+            (glfwGetKey(window,GLFW_KEY_LEFT_CONTROL)  == GLFW_PRESS) ||
+            (glfwGetKey(window,GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS);
+
+          if (ctrl)
+            {
+              float const cx  = 0.5f * interop->width;
+              float const cy  = 0.5f * interop->height;
+
+              // find angle between mouse and center
+              float const vx  = x_prev - cx;
+              float const vy  = y_prev - cy;
+
+              float const wx  = mx - cx;
+              float const wy  = my - cy;
+
+              float const len = sqrtf((vx*vx + vy*vy) * (wx*wx + wy*wy));
+
+              if (len > 0.0f)
+                {
+                  float const dot = vx*wx + vy*wy;
+                  float const da  = acosf(dot / len);
+
+                  if (vx*wy - vy*wx >= 0.0f)
+                    interop->rotate_theta += da;
+                  else
+                    interop->rotate_theta -= da;
+
+                  interop->rotate_theta = fmodf(interop->rotate_theta,(float)(M_PI*2.0));
+                }
+            }
+          else
+            {
+              skc_interop_translate(interop,
+                                    mx - x_prev,
+                                    my - y_prev);
+            }
+          
+          interop->is_transform = true;
+        }
+      else
+        {
+          is_mouse_dragging = true;
+        }
+
+      x_prev = mx;
+      y_prev = my;
+    }
+  else
+    {
+      is_mouse_dragging = false;
+    }
+}
+
+//
+//
+//
+
+static
+void
+skc_interop_acquire(struct skc_interop * interop)
+{
+  // frame buffer object
+  glCreateFramebuffers(1,&interop->fbo);
+
+  // render buffer object w/a color buffer
+  glCreateRenderbuffers(1,&interop->rbo);
+
+  // size rbo
+  glNamedRenderbufferStorage(interop->rbo,
+                             SKC_IMAGE_FORMAT,
+                             interop->width,
+                             interop->height);
+
+  // attach rbo to fbo
+  glNamedFramebufferRenderbuffer(interop->fbo,
+                                 GL_COLOR_ATTACHMENT0,
+                                 GL_RENDERBUFFER,
+                                 interop->rbo);
+}
+
+//
+//
+//
+
+struct skc_interop *
+skc_interop_create()
+{
+  struct skc_interop * interop = malloc(sizeof(*interop));
+
+  *interop = (struct skc_interop)
+    {
+     .fb            = { .type        = SKC_FRAMEBUFFER_CL_GL_RENDERBUFFER,
+                        .mem         = NULL,
+                        .interop     = interop,
+                        .post_render = skc_interop_blit },
+
+     .is_msecs      = true,
+     .is_srgb       = true,
+     .is_vsync_on   = false,
+     .is_fullscreen = false,
+     .is_iconified  = false,
+     .is_resized    = true,
+     .is_spinning   = false,
+     .is_transform  = true,
+
+     .scale         = 1.0f,
+     .translate     = { 0.0f, 0.0f },
+     .rotate_theta  = 0.0f,
+
+     .key           = 0
+    };
+
+  //
+  // INITIALIZE GLFW/GLAD
+  //
+  glfwSetErrorCallback(skc_interop_error_callback);
+
+  if (!glfwInit())
+    exit(EXIT_FAILURE);
+
+  GLFWmonitor       * const primary = glfwGetPrimaryMonitor();
+  GLFWvidmode const * const mode    = glfwGetVideoMode(primary);
+
+  if (interop->is_fullscreen)
+    {
+      interop->width  = mode->width;
+      interop->height = mode->height;
+    }
+  else
+    {
+      interop->width  = 1600;
+      interop->height = 1600;
+    }
+
+  glfwWindowHint(GLFW_ALPHA_BITS,            0);
+  glfwWindowHint(GLFW_DEPTH_BITS,            0);
+  glfwWindowHint(GLFW_STENCIL_BITS,          0);
+
+  glfwWindowHint(GLFW_SRGB_CAPABLE,          GL_TRUE);
+
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
+
+  glfwWindowHint(GLFW_OPENGL_PROFILE,        GLFW_OPENGL_CORE_PROFILE);
+
+  interop->window = glfwCreateWindow(interop->width,
+                                     interop->height,
+                                     "Skia Compute",
+                                     interop->is_fullscreen ? primary : NULL,
+                                     NULL);
+
+  if (interop->window == NULL)
+    {
+      glfwTerminate();
+      exit(EXIT_FAILURE);
+    }
+
+  // save back pointer
+  glfwSetWindowUserPointer(interop->window,interop);
+  
+  glfwMakeContextCurrent(interop->window);
+
+  // set up GLAD
+  gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
+
+  // ignore vsync for now
+  glfwSwapInterval(interop->is_vsync_on ? 1 : 0);
+
+  // only copy r/g/b
+  glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_FALSE);
+
+  // enable SRGB, disable scissor
+  glEnable(GL_FRAMEBUFFER_SRGB);
+  glDisable(GL_SCISSOR_TEST);
+
+  //
+  // SET USER POINTER AND CALLBACKS
+  //
+  glfwSetKeyCallback            (interop->window,skc_interop_key_callback);
+  glfwSetFramebufferSizeCallback(interop->window,skc_interop_window_size_callback);
+  glfwSetScrollCallback         (interop->window,skc_interop_scroll_callback);
+  glfwSetCursorPosCallback      (interop->window,skc_interop_cursor_position_callback);
+  glfwSetWindowIconifyCallback  (interop->window,skc_interop_iconify_callback);
+
+  //
+  //
+  //
+  fprintf(stderr,
+          "GL_VENDOR   : %s\n"
+          "GL_RENDERER : %s\n",
+          glGetString(GL_VENDOR),
+          glGetString(GL_RENDERER));
+
+  //
+  // acquire an FBO/RBO
+  //
+  skc_interop_acquire(interop);
+
+  return interop;
+}
+
+//
+//
+//
+
+void
+skc_interop_destroy(struct skc_interop * interop)
+{
+  glfwDestroyWindow(interop->window);
+  glfwTerminate();
+
+  free(interop);
+}
+
+//
+//
+//
+
+void
+skc_interop_set_cl_context(struct skc_interop * interop,
+                           cl_context           context_cl)
+{
+  interop->context_cl = context_cl;
+}
+
+//
+//
+//
+
+cl_context_properties
+skc_interop_get_wgl_context()
+{
+  return (cl_context_properties)wglGetCurrentContext();
+}
+
+cl_context_properties
+skc_interop_get_wgl_dc()
+{
+  return (cl_context_properties)wglGetCurrentDC();
+}
+
+//
+//
+//
+
+#define SKC_ROTATE_STEP ((float)(M_PI / 180.0))
+
+void
+skc_interop_transform(struct skc_interop         * interop,
+                      struct skc_transform_stack * ts)
+{
+  // OpenGL'ism
+  skc_transform_stack_push_affine(ts,
+                                  1.0f, 0.0f,0.0f,
+                                  0.0f,-1.0f,(float)interop->height);
+  // multiply
+  skc_transform_stack_concat(ts);
+
+  // spinner...
+  if (interop->is_spinning)
+    interop->rotate_theta = fmodf(interop->rotate_theta + SKC_ROTATE_STEP,(float)(M_PI*2.0));
+  
+  // always rotate and scale around surface center point
+  skc_transform_stack_push_rotate_scale_xy(ts,
+                                           interop->rotate_theta,
+                                           interop->scale,
+                                           interop->scale,
+                                           0.5f*interop->width,
+                                           0.5f*interop->height);
+  skc_transform_stack_concat(ts);
+
+  // where did the mouse take us?
+  skc_transform_stack_push_translate(ts,
+                                     interop->translate.x,
+                                     interop->translate.y);
+  skc_transform_stack_concat(ts);
+}
+
+//
+//
+//
+
+static
+void
+skc_interop_resize(struct skc_interop * interop)
+{
+  interop->is_resized = false;
+
+  // release the image2d
+  if (interop->fb.mem != NULL)
+    cl(ReleaseMemObject(interop->fb.mem));
+
+  // resize rbo
+  glNamedRenderbufferStorage(interop->rbo,
+                             SKC_IMAGE_FORMAT,
+                             interop->width,
+                             interop->height);
+
+  // attach rbo to fbo
+  glNamedFramebufferRenderbuffer(interop->fbo,
+                                 GL_COLOR_ATTACHMENT0,
+                                 GL_RENDERBUFFER,
+                                 interop->rbo);
+  //
+  //
+  //
+  cl_int cl_err;
+
+  interop->fb.mem = clCreateFromGLRenderbuffer(interop->context_cl,
+                                               CL_MEM_WRITE_ONLY,
+                                               interop->rbo,
+                                               &cl_err); cl_ok(cl_err);
+  //
+  // for debugging porpoises!
+  //
+#if 0
+  cl_image_format format;
+
+  cl(GetImageInfo(interop->fb.mem,
+                  CL_IMAGE_FORMAT,
+                  sizeof(format),
+                  &format,
+                  NULL));
+#endif
+}
+
+//
+// FPS COUNTER FROM HERE:
+//
+// http://antongerdelan.net/opengl/glcontext2.html
+//
+
+static
+void
+skc_interop_fps(struct skc_interop * interop)
+{
+  if (interop->is_fullscreen)
+    return;
+
+  // static fps counters
+  static double stamp_prev  = 0.0;
+  static int    frame_count = 0;
+
+  // locals
+  double const  stamp_curr  = glfwGetTime();
+  double const  elapsed     = stamp_curr - stamp_prev;
+
+  if (elapsed >= 0.5)
+    {
+      stamp_prev = stamp_curr;
+
+      char tmp[64];
+
+      if (interop->is_msecs)
+        {
+          double const msecs = min(elapsed * 1000 / frame_count,9999.9);
+
+          sprintf_s(tmp,64,"%5.1f MSECS - (%d x %d) - VSync %s - sRGB %s",
+                    msecs,
+                    interop->width,interop->height,
+                    interop->is_vsync_on ? "ON"      : "OFF",
+                    interop->is_srgb     ? "ENABLED" : "DISABLED");
+        }
+      else
+        {
+          double const fps = min((double)frame_count / elapsed,9999.9);
+
+          sprintf_s(tmp,64,"%5.1f FPS - (%d x %d) - VSync %s - sRGB %s",
+                    fps,
+                    interop->width,interop->height,
+                    interop->is_vsync_on ? "ON"      : "OFF",
+                    interop->is_srgb     ? "ENABLED" : "DISABLED");
+        }
+
+      glfwSetWindowTitle(interop->window,tmp);
+
+      frame_count = 0;
+    }
+
+  frame_count++;
+}
+
+//
+//
+//
+
+bool
+skc_interop_poll(struct skc_interop * interop, int * key)
+{
+  // wait until uniconified
+  while (interop->is_iconified)
+    {
+      glfwWaitEvents();
+      continue;
+    }
+
+  // what's happended?
+  glfwPollEvents();
+
+  // resize?
+  if (interop->is_resized)
+    skc_interop_resize(interop);
+
+  // monitor fps
+  skc_interop_fps(interop);
+
+  if (key != NULL)
+    {
+      *key         = interop->key;
+      interop->key = 0;
+    }
+
+  bool const is_transform = interop->is_transform || interop->is_spinning;
+
+  interop->is_transform = false;
+  
+  return is_transform;
+}
+
+//
+//
+//
+
+void
+skc_interop_blit(struct skc_interop * interop)
+{
+  // blit skc rbo
+  glBlitNamedFramebuffer(interop->fbo,0,
+                         0,0,interop->width,interop->height,
+                         0,0,interop->width,interop->height,
+                         GL_COLOR_BUFFER_BIT,
+                         GL_NEAREST);
+
+  // swap buffers
+  glfwSwapBuffers(interop->window);
+
+#if 0
+  //
+  // FIXME -- this clear does nothing!
+  //
+  // As a hack we're clearing the interop'd RBO with a
+  // clEnqueueFillImage().
+  //
+  GLenum const attachments[] = { GL_COLOR_ATTACHMENT0 };
+  glInvalidateNamedFramebufferData(interop->fbo,1,attachments);
+  float  const rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+  glClearNamedFramebufferfv(interop->fbo,GL_COLOR,0,rgba);
+#endif
+}
+
+//
+//
+//
+
+skc_framebuffer_t
+skc_interop_get_framebuffer(struct skc_interop * interop)
+{
+  // glFlush();
+  glFinish();
+
+  return &interop->fb;
+}
+
+//
+//
+//
+
+bool
+skc_interop_should_exit(struct skc_interop * interop)
+{
+  return glfwWindowShouldClose(interop->window);
+}
+
+//
+//
+//
+
+void
+skc_interop_get_size(struct skc_interop * interop, 
+                     uint32_t           * width,
+                     uint32_t           * height)
+{
+  *width  = interop->width;
+  *height = interop->height;
+}
+
+//
+//
+//
+
+
diff --git a/src/compute/skc/platforms/cl_12/kernels/devices/gen9/device_cl_12.c b/src/compute/skc/platforms/cl_12/kernels/devices/gen9/device_cl_12.c
index f7e06a1..d7f10e3 100644
--- a/src/compute/skc/platforms/cl_12/kernels/devices/gen9/device_cl_12.c
+++ b/src/compute/skc/platforms/cl_12/kernels/devices/gen9/device_cl_12.c
@@ -87,6 +87,8 @@
 //
 // FIXME -- THE CONFIG INITIALIZATION IS ONLY HERE TEMPORARILY
 //
+// FIXME -- move these to log2 values where appropriate
+//
 
 static 
 struct skc_config const config =
@@ -103,7 +105,7 @@
     }, 
 
     .scheduler = {
-      .size         = 4096 // 128 // fixme -- this is just for testing -- too big
+      .size         = 4096 // 128 // FIXME -- this is just for testing -- way too big -- schedulees should bring their own state
     },
 
     .subblock = {
diff --git a/src/compute/skc/platforms/cl_12/runtime_cl_12.c b/src/compute/skc/platforms/cl_12/runtime_cl_12.c
index a4a578f..81e1e85 100644
--- a/src/compute/skc/platforms/cl_12/runtime_cl_12.c
+++ b/src/compute/skc/platforms/cl_12/runtime_cl_12.c
@@ -31,7 +31,7 @@
 //
 //
 
-static
+static 
 void
 skc_block_pool_create(struct skc_runtime * const runtime, cl_command_queue cq)
 {
@@ -41,7 +41,7 @@
   // create block extent
   skc_extent_pdrw_alloc(runtime,
                         &runtime->block_pool.blocks,
-                        runtime->block_pool.size->pool_size *
+                        runtime->block_pool.size->pool_size * 
                         runtime->config->block.bytes);
 
   // allocate block pool ids
@@ -84,7 +84,7 @@
   cl(ReleaseKernel(k1));
 }
 
-static
+static 
 void
 skc_block_pool_dispose(struct skc_runtime * const runtime)
 {
@@ -105,7 +105,7 @@
 }
 
 static
-void
+void 
 skc_runtime_wait(struct skc_runtime * const runtime)
 {
   skc_scheduler_wait(runtime->scheduler);
@@ -122,7 +122,7 @@
 {
   // allocate the runtime
   struct skc_runtime * const runtime = malloc(sizeof(*runtime));
-
+  
   // save off CL objects
   runtime->cl.context   = context_cl;
   runtime->cl.device_id = device_id_cl;
@@ -135,7 +135,7 @@
                    sizeof(align_bits),
                    &align_bits,
                    NULL));
-
+          
   runtime->cl.align_bytes = align_bits / 8;
 
   // create device
@@ -183,7 +183,7 @@
 
   context->yield          = skc_runtime_yield;
   context->wait           = skc_runtime_wait;
-
+  
   context->path_builder   = skc_path_builder_cl_12_create;
   context->path_retain    = skc_runtime_path_host_retain;
   context->path_release   = skc_runtime_path_host_release;
@@ -196,7 +196,7 @@
 
   context->composition    = skc_composition_cl_12_create;
   context->styling        = skc_styling_cl_12_create;
-
+  
   context->surface        = skc_surface_cl_12_create;
 
   // block on pool creation
@@ -234,48 +234,19 @@
   skc_block_pool_dispose(context->runtime);
 
   // skc_handle_pool_dispose(context->runtime);
-
+  
   return SKC_ERR_SUCCESS;
 }
 
 //
-// TEMPORARY BENCHMARK
+// REPORT BLOCK POOL ALLOCATION
 //
 
-#if 1
-
-#include <windows.h>
-
-#define SKC_FRAMES_MASK 0x7F
-#define SKC_FRAMES      (SKC_FRAMES_MASK + 1)
-
 void
 skc_runtime_cl_12_debug(struct skc_context * const context)
 {
-#ifdef NDEBUG
-  static skc_uint      frames=0;
-  static LARGE_INTEGER StartingTime={0}, EndingTime;
-
-  if ((frames++ & SKC_FRAMES_MASK) != SKC_FRAMES_MASK)
-    return;
-
-  QueryPerformanceCounter(&EndingTime);
-
-  LARGE_INTEGER ElapsedMicroseconds, Frequency;
-
-  ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
-
-  QueryPerformanceFrequency(&Frequency);
-
-  double const msecs_total  = 1000.0 * ElapsedMicroseconds.QuadPart / Frequency.QuadPart;
-  double const msecs_frame  = msecs_total / SKC_FRAMES;
-
-  printf("Frames / Total / Per : %u / %.3f / %.3f\n",
-         SKC_FRAMES,msecs_total,msecs_frame);
-#endif
-
   struct skc_runtime * const runtime = context->runtime;
-
+  
   // acquire out-of-order cq
   cl_command_queue cq = skc_runtime_acquire_cq_in_order(runtime);
 
@@ -293,28 +264,17 @@
   skc_uint const available = bp_atomic->writes - bp_atomic->reads;
   skc_uint const inuse     = runtime->config->block_pool.pool_size - available;
 
-  fprintf(stderr,"w/r/f/a: %9u - %9u = %9u : %6.2f MB\n",
+  fprintf(stderr,
+          "writes/reads/avail/alloc: %9u / %9u / %9u = %6.2f MB / %9u = %6.2f MB\n",
           bp_atomic->writes,
           bp_atomic->reads,
           available,
-          (inuse * runtime->config->block.bytes) / (1024.0*1024.0));
-
-  if (available >= (1<<27))
-    {
-      fprintf(stderr,"block pool corrupted!\n");
-      exit(-1);
-    }
-
-  //
-  //
-  //
-#ifdef NDEBUG
-  QueryPerformanceCounter(&StartingTime);
-#endif
+          (available * runtime->config->block.bytes) / (1024.0*1024.0),
+          inuse,
+          (inuse     * runtime->config->block.bytes) / (1024.0*1024.0));
 }
 
-#endif
+//
+//
+//
 
-//
-//
-//
diff --git a/src/compute/skc/platforms/cl_12/runtime_cl_12.h b/src/compute/skc/platforms/cl_12/runtime_cl_12.h
index ff820e6..beb924f 100644
--- a/src/compute/skc/platforms/cl_12/runtime_cl_12.h
+++ b/src/compute/skc/platforms/cl_12/runtime_cl_12.h
@@ -178,3 +178,11 @@
 //
 //
 //
+
+void
+skc_runtime_cl_12_debug(struct skc_context * const context);
+
+//
+//
+//
+
diff --git a/src/compute/skc/platforms/cl_12/skc_cl.h b/src/compute/skc/platforms/cl_12/skc_cl.h
new file mode 100644
index 0000000..41ca402
--- /dev/null
+++ b/src/compute/skc/platforms/cl_12/skc_cl.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can
+ * be found in the LICENSE file.
+ *
+ */
+
+#ifndef SKC_ONCE_SKC_CREATE_CL
+#define SKC_ONCE_SKC_CREATE_CL
+
+//
+//
+//
+
+#ifdef __APPLE__
+#include "OpenCL/opencl.h"
+#else
+#include "CL/opencl.h"
+#endif
+
+//
+//
+//
+
+#include "skc.h"
+
+//
+// CONTEXT CREATION
+//
+
+skc_err
+skc_context_create_cl(skc_context_t * context,
+                      cl_context      context_cl,
+                      cl_device_id    device_id_cl);
+
+//
+// SURFACE RENDER FRAMEBUFFER TYPES
+//
+
+typedef enum skc_framebuffer_cl_mem_type {
+  SKC_FRAMEBUFFER_CL_IMAGE2D,
+  SKC_FRAMEBUFFER_CL_GL_RENDERBUFFER,
+  SKC_FRAMEBUFFER_CL_GL_TEXTURE
+} skc_framebuffer_cl_mem_type;
+
+struct skc_framebuffer_cl
+{
+  skc_framebuffer_cl_mem_type type;
+  cl_mem                      mem;
+  struct skc_interop        * interop;
+  void                     (* post_render)(struct skc_interop * interop);
+};
+
+//
+//
+//
+
+#endif
+
+//
+//
+//
diff --git a/src/compute/skc/platforms/cl_12/styling_cl_12.c b/src/compute/skc/platforms/cl_12/styling_cl_12.c
index 6c84fe6..8d8d905 100644
--- a/src/compute/skc/platforms/cl_12/styling_cl_12.c
+++ b/src/compute/skc/platforms/cl_12/styling_cl_12.c
@@ -212,7 +212,12 @@
     return;
 
   //
-  // otherwise, unmap all resources by sealing and delete
+  // otherwise, unmap all resources
+  //
+
+  //
+  // FIXME -- is it pointless unmap before freeing?  The seal
+  // accomplishes the unmapping.
   //
   skc_styling_pfn_seal(impl);
 
diff --git a/src/compute/skc/platforms/cl_12/surface_cl_12_buffer.c b/src/compute/skc/platforms/cl_12/surface_cl_12.c
similarity index 79%
rename from src/compute/skc/platforms/cl_12/surface_cl_12_buffer.c
rename to src/compute/skc/platforms/cl_12/surface_cl_12.c
index cc7cba5..24a0c45 100644
--- a/src/compute/skc/platforms/cl_12/surface_cl_12_buffer.c
+++ b/src/compute/skc/platforms/cl_12/surface_cl_12.c
@@ -12,6 +12,8 @@
 
 #include "common/cl/assert_cl.h"
 
+#include "skc_cl.h"
+#include "interop.h"
 #include "extent_cl_12.h"
 #include "runtime_cl_12.h"
 #include "styling_cl_12.h"
@@ -32,32 +34,23 @@
 
 struct skc_surface_impl
 {
-  struct skc_surface        * surface;
-  struct skc_runtime        * runtime;
+  struct skc_surface * surface;
+  struct skc_runtime * runtime;
 
   // framebuffer
   // struct skc_extent_pdrw      fb;
   // struct skc_extent_phrN_pdwN fb;
 
   // for now, a single in-order command queue
-  cl_command_queue            cq;
+  cl_command_queue     cq;
 
   struct {
-    cl_kernel                 render;
+    cl_kernel          render;
   } kernels;
 };
 
 //
-// we might want concurrent access to the same surface as long as
-// the clips don't overlap.
 //
-// this would require acquiring a cq on demand when it is determined
-// that the clipped render won't overlap
-//
-// { tile clip , cq } pair
-//
-// skc_uint4                clip;
-// cl_command_queue         cq
 //
 
 struct skc_surface_render
@@ -68,10 +61,10 @@
   struct skc_styling          * styling;
   struct skc_composition      * composition;
 
-  skc_surface_render_pfn_notify notify;
-  void                        * data;
+  struct skc_framebuffer_cl   * fb;
 
-  cl_mem                        fb;
+  skc_surface_render_notify     notify;
+  void                        * data;
 
   skc_grid_t                    grid;
 
@@ -79,21 +72,22 @@
 };
 
 //
-//
+// FIXME -- we only need this because (I think) RBO<>CL interop
+// results in glClear() on the FBO not working...
 //
 
 static
 void
-skc_surface_pfn_clear(struct skc_surface_impl * const impl,
-                      float                     const rgba[4],
-                      skc_uint                  const rect[4],
-                      void                     *      fb)
+skc_surface_debug_clear(struct skc_surface_impl * const impl,
+                        skc_framebuffer_t               fb,
+                        float                     const rgba[4], 
+                        uint32_t                  const rect[4])
 {
   size_t const origin[3] = { rect[0], rect[1], 0 };
   size_t const region[3] = { rect[2], rect[3], 1 };
 
   cl(EnqueueFillImage(impl->cq,
-                      (cl_mem)fb,
+                      ((struct skc_framebuffer_cl *)fb)->mem,
                       rgba,
                       origin,
                       region,
@@ -104,27 +98,7 @@
 //
 //
 
-static
-void
-skc_surface_pfn_blit(struct skc_surface_impl * const impl,
-                     skc_uint                  const rect[4],
-                     skc_int                   const txty[2])
-{
-  ;
-}
-
-//
-//
-//
-
 #if 0 // #ifndef NDEBUG
-#define SKC_SURFACE_DEBUG
-#endif
-
-#ifdef SKC_SURFACE_DEBUG
-
-#define SKC_SURFACE_WIDTH  4096
-#define SKC_SURFACE_HEIGHT 4096
 
 static
 void
@@ -166,123 +140,6 @@
 //
 //
 
-void
-skc_surface_render_complete(struct skc_surface_render * const render)
-{
-#ifdef SKC_SURFACE_DEBUG
-  // write fb out
-  skc_surface_debug(render->impl);
-#endif
-
-  // notify
-  if (render->notify != NULL) {
-    render->notify(render->impl->surface,
-                   render->styling,
-                   render->composition,
-                   render->data);
-  }
-
-  // unlock and release the styling and composition
-  skc_styling_unlock_and_release(render->styling);
-  skc_composition_unlock_and_release(render->composition);
-
-  // grid is now complete
-  skc_grid_complete(render->grid);
-}
-
-static
-void
-skc_surface_render_cb(cl_event event, cl_int status, struct skc_surface_render * const render)
-{
-  SKC_CL_CB(status);
-
-  // as quickly as possible, enqueue next stage in pipeline to context command scheduler
-  SKC_SCHEDULER_SCHEDULE(render->impl->runtime->scheduler,
-                         skc_surface_render_complete,
-                         render);
-}
-
-//
-//
-//
-
-static
-void
-skc_surface_grid_pfn_execute(skc_grid_t const grid)
-{
-  struct skc_surface_render   * const render  = skc_grid_get_data(grid);
-  struct skc_surface_impl     * const impl    = render->impl;
-  struct skc_runtime          * const runtime = impl->runtime;
-
-  // get the composition args
-  struct skc_composition_impl * const ci      = render->composition->impl;
-  struct skc_place_atomics    * const atomics = ci->atomics.hr;
-
-  if (atomics->offsets > 0)
-    {
-      // acquire the rbo
-      cl(EnqueueAcquireGLObjects(impl->cq,1,&render->fb,0,NULL,NULL));
-
-      // get the styling args
-      struct skc_styling_impl * const si = render->styling->impl;
-
-      cl(SetKernelArg(impl->kernels.render,0,SKC_CL_ARG(si->layers.drN)));
-      cl(SetKernelArg(impl->kernels.render,1,SKC_CL_ARG(si->groups.drN)));
-      cl(SetKernelArg(impl->kernels.render,2,SKC_CL_ARG(si->extras.drN)));
-
-      cl(SetKernelArg(impl->kernels.render,3,SKC_CL_ARG(ci->keys.drw)));
-      cl(SetKernelArg(impl->kernels.render,4,SKC_CL_ARG(atomics->keys)));
-      cl(SetKernelArg(impl->kernels.render,5,SKC_CL_ARG(ci->offsets.drw)));
-      cl(SetKernelArg(impl->kernels.render,6,SKC_CL_ARG(atomics->offsets)));
-
-      // block pool
-      cl(SetKernelArg(impl->kernels.render,7,SKC_CL_ARG(impl->runtime->block_pool.blocks.drw)));
-
-      // surface
-      cl(SetKernelArg(impl->kernels.render,8,SKC_CL_ARG(render->fb)));
-
-#if 1
-      // tile clip
-      cl(SetKernelArg(impl->kernels.render,9,sizeof(skc_uint4),render->clip));
-#else
-      // surface pitch (height)
-      skc_uint const surface_pitch = SKC_SURFACE_HEIGHT;
-      cl(SetKernelArg(impl->kernels.render,9,SKC_CL_ARG(surface_pitch)));
-      // tile clip
-      cl(SetKernelArg(impl->kernels.render,10,sizeof(skc_uint4),render->clip));
-#endif
-
-      // launch render kernel
-      skc_device_enqueue_kernel(runtime->device,
-                                SKC_DEVICE_KERNEL_ID_RENDER,
-                                impl->cq,
-                                impl->kernels.render,
-                                atomics->offsets,
-                                0,NULL,NULL);
-
-
-      cl_event complete;
-
-      // give the rbo back
-      cl(EnqueueReleaseGLObjects(impl->cq,1,&render->fb,0,NULL,&complete));
-
-      // notify anyone listening...
-      cl(SetEventCallback(complete,CL_COMPLETE,skc_surface_render_cb,render));
-      cl(ReleaseEvent(complete));
-
-      // flush it
-      cl(Flush(impl->cq));
-    }
-  else
-    {
-      skc_surface_render_complete(render);
-    }
-}
-
-//
-//
-//
-
 static
 void
 skc_surface_pfn_release(struct skc_surface_impl * const impl)
@@ -319,19 +176,146 @@
 //
 //
 
-static
 void
-skc_surface_grid_pfn_dispose(skc_grid_t const grid)
+skc_surface_render_complete(struct skc_surface_render * const render)
 {
-  struct skc_surface_render * const render  = skc_grid_get_data(grid);
-  struct skc_surface_impl   * const impl    = render->impl;
-  struct skc_runtime        * const runtime = impl->runtime;
+#ifdef SKC_SURFACE_DEBUG
+  // write fb out
+  skc_surface_debug(render->impl);
+#endif
 
-  // free the render object
-  skc_runtime_host_temp_free(runtime,render,render->id);
+  // notify
+  if (render->notify != NULL) {
+    render->notify(render->impl->surface,
+                   render->styling,
+                   render->composition,
+                   render->fb,
+                   render->data);
+  }
+
+  // unlock and release the styling and composition
+  skc_styling_unlock_and_release(render->styling);
+  skc_composition_unlock_and_release(render->composition);
+
+  // grid is now complete
+  skc_grid_complete(render->grid);
+
+  struct skc_surface_impl * const impl    = render->impl;  
+  struct skc_runtime      * const runtime = impl->runtime;
 
   // release the surface
   skc_surface_pfn_release(impl);
+
+  // free the render object
+  skc_runtime_host_temp_free(runtime,render,render->id);
+}
+
+static
+void
+skc_surface_render_cb(cl_event event, cl_int status, struct skc_surface_render * const render)
+{
+  SKC_CL_CB(status);
+
+  // as quickly as possible, enqueue next stage in pipeline to context command scheduler
+  SKC_SCHEDULER_SCHEDULE(render->impl->runtime->scheduler,
+                         skc_surface_render_complete,
+                         render);
+}
+
+//
+//
+//
+
+static
+void
+skc_surface_grid_pfn_execute(skc_grid_t const grid)
+{
+  struct skc_surface_render   * const render  = skc_grid_get_data(grid);
+  struct skc_surface_impl     * const impl    = render->impl;
+  struct skc_runtime          * const runtime = impl->runtime;
+
+  // get the composition args
+  struct skc_composition_impl * const ci      = render->composition->impl;
+  struct skc_place_atomics    * const atomics = ci->atomics.hr;
+
+  if (atomics->offsets > 0)
+    {
+      // acquire the rbo/tex
+      if (render->fb->type != SKC_FRAMEBUFFER_CL_IMAGE2D)
+        cl(EnqueueAcquireGLObjects(impl->cq,1,&render->fb->mem,0,NULL,NULL));
+
+      // get the styling args
+      struct skc_styling_impl * const si = render->styling->impl;
+
+      cl(SetKernelArg(impl->kernels.render,0,SKC_CL_ARG(si->layers.drN)));
+      cl(SetKernelArg(impl->kernels.render,1,SKC_CL_ARG(si->groups.drN)));
+      cl(SetKernelArg(impl->kernels.render,2,SKC_CL_ARG(si->extras.drN)));
+
+      cl(SetKernelArg(impl->kernels.render,3,SKC_CL_ARG(ci->keys.drw)));
+      cl(SetKernelArg(impl->kernels.render,4,SKC_CL_ARG(atomics->keys)));
+      cl(SetKernelArg(impl->kernels.render,5,SKC_CL_ARG(ci->offsets.drw)));
+      cl(SetKernelArg(impl->kernels.render,6,SKC_CL_ARG(atomics->offsets)));
+
+      // block pool
+      cl(SetKernelArg(impl->kernels.render,7,SKC_CL_ARG(impl->runtime->block_pool.blocks.drw)));
+
+      // surface
+      cl(SetKernelArg(impl->kernels.render,8,SKC_CL_ARG(render->fb->mem)));
+
+#if 1
+      // tile clip
+      cl(SetKernelArg(impl->kernels.render,9,sizeof(skc_uint4),render->clip));
+#else
+      // surface pitch (height)
+      skc_uint const surface_pitch = SKC_SURFACE_HEIGHT;
+      cl(SetKernelArg(impl->kernels.render,9,SKC_CL_ARG(surface_pitch)));
+      // tile clip
+      cl(SetKernelArg(impl->kernels.render,10,sizeof(skc_uint4),render->clip));
+#endif
+
+      // launch render kernel
+      skc_device_enqueue_kernel(runtime->device,
+                                SKC_DEVICE_KERNEL_ID_RENDER,
+                                impl->cq,
+                                impl->kernels.render,
+                                atomics->offsets,
+                                0,NULL,NULL);
+
+
+      cl_event complete;
+
+      // give the rbo back
+      if (render->fb->type != SKC_FRAMEBUFFER_CL_IMAGE2D)
+        {
+          cl(EnqueueReleaseGLObjects(impl->cq,1,&render->fb->mem,0,NULL,&complete));
+
+          //
+          // blit the rbo to fbo0
+          //
+          render->fb->post_render(render->fb->interop);
+
+          //
+          // clear the rbo -- FIXME -- we shouldn't have to do this here
+          //
+          float    const rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+          uint32_t       rect[4] = { 0 };
+
+          skc_interop_get_size(render->fb->interop,rect+2,rect+3);
+
+          skc_surface_debug_clear(impl,render->fb,rgba,rect);
+        }
+
+      // notify anyone listening...
+      cl(SetEventCallback(complete,CL_COMPLETE,skc_surface_render_cb,render));
+      cl(ReleaseEvent(complete));
+
+      // flush it
+      cl(Flush(impl->cq));
+    }
+  else
+    {
+      skc_surface_render_complete(render);
+    }
 }
 
 //
@@ -341,12 +325,12 @@
 static
 void
 skc_surface_pfn_render(struct skc_surface_impl * const impl,
-                       uint32_t                  const clip[4],
                        skc_styling_t                   styling,
                        skc_composition_t               composition,
-                       skc_surface_render_pfn_notify   notify,
-                       void                          * data,
-                       void                          * fb)
+                       skc_framebuffer_t               fb,
+                       uint32_t                  const clip[4],
+                       skc_surface_render_notify       notify,
+                       void                          * data)
 {
   // retain surface
   skc_surface_retain(impl->surface);
@@ -387,15 +371,21 @@
   render->grid        = SKC_GRID_DEPS_ATTACH(impl->runtime->deps,
                                              NULL, // invalidation not necessary
                                              render,
-                                             NULL, // no waiting
+                                             NULL,  // no waiting
                                              skc_surface_grid_pfn_execute,
-                                             skc_surface_grid_pfn_dispose);
-
+                                             NULL); // no disposal
+  //
   // declare happens-after relationships
-  skc_grid_happens_after_grid(render->grid,styling->impl->grid);
-  skc_grid_happens_after_grid(render->grid,composition->impl->grids.sort);
+  //
+  if (styling->impl->state != SKC_STYLING_STATE_SEALED)
+    skc_grid_happens_after_grid(render->grid,styling->impl->grid);
 
-  // wait for styling and composition
+  if (composition->impl->state != SKC_COMPOSITION_STATE_SEALED)
+    skc_grid_happens_after_grid(render->grid,composition->impl->grids.sort);
+
+  //
+  // start render but possibly wait for styling and composition
+  //
   skc_grid_start(render->grid);
 }
 
@@ -423,8 +413,6 @@
   (*surface)->ref_count  = 1;
 
   (*surface)->release    = skc_surface_pfn_release;
-  (*surface)->clear      = skc_surface_pfn_clear;
-  (*surface)->blit       = skc_surface_pfn_blit;
   (*surface)->render     = skc_surface_pfn_render;
 
   // intialize impl
diff --git a/src/compute/skc/skc.h b/src/compute/skc/skc.h
index a81a534..a5e81fb 100644
--- a/src/compute/skc/skc.h
+++ b/src/compute/skc/skc.h
@@ -248,7 +248,7 @@
 skc_styling_group_leave(skc_styling_t             styling,
                         skc_group_id              group_id,
                         uint32_t                  n,
-                        skc_styling_cmd_t const * cmds);                        
+                        skc_styling_cmd_t const * cmds);
 
 skc_err
 skc_styling_group_parents(skc_styling_t        styling,
@@ -271,7 +271,7 @@
                         skc_group_id              group_id,
                         skc_layer_id              layer_id,
                         uint32_t                  n,
-                        skc_styling_cmd_t const * cmds);                        
+                        skc_styling_cmd_t const * cmds);
 
 //
 // STYLING ENCODERS -- FIXME -- WILL EVENTUALLY BE OPAQUE
@@ -298,13 +298,6 @@
 // SURFACE
 //
 
-//
-// FIXME - surface create needs to be able to specify different
-// surface targets here that are a function of the surface type and
-// rendering model: CL/global, GL/buffer, simple SRCOVER model,
-// complex group-based PDF rendering model, etc.
-//
-
 skc_err
 skc_surface_create(skc_context_t context, skc_surface_t * surface);
 
@@ -314,44 +307,24 @@
 skc_err
 skc_surface_release(skc_surface_t surface);
 
-// skc_interop_surface_t
-// skc_surface_interop_surface_get(skc_surface_t surface);
-
-//
-// NO NO NO -- SKC will always be a client of some other platform so
-// handle things like blits and clears there unless it's something
-// unique like an SKC tile-based clear/blit.
-//
-// (temporarily implement these for testing porpoises)
-//
-
-skc_err
-skc_surface_clear(skc_surface_t  surface, 
-                  float    const rgba[4], 
-                  uint32_t const rect[4],
-                  void         * fb);
-
-skc_err
-skc_surface_blit(skc_surface_t  surface, 
-                 uint32_t const rect[4], 
-                 int32_t  const txty[2]);
-
 //
 // SURFACE RENDER
 //
 
-typedef void (*skc_surface_render_pfn_notify)(skc_surface_t     surface,
-                                              skc_styling_t     styling,
-                                              skc_composition_t composition,
-                                              void            * data);
+typedef void (*skc_surface_render_notify)(skc_surface_t     surface,
+                                          skc_styling_t     styling,
+                                          skc_composition_t composition,
+                                          skc_framebuffer_t fb,
+                                          void            * data);
+
 skc_err
-skc_surface_render(skc_surface_t                 surface,
-                   uint32_t                const clip[4],
-                   skc_styling_t                 styling,
-                   skc_composition_t             composition,
-                   skc_surface_render_pfn_notify notify,
-                   void                        * data,
-                   void                        * fb); // FIXME FIXME
+skc_surface_render(skc_surface_t             surface,
+                   skc_styling_t             styling,
+                   skc_composition_t         composition,
+                   skc_framebuffer_t         fb,
+                   uint32_t            const clip[4],
+                   skc_surface_render_notify notify,
+                   void                    * data);
 
 //
 // COORDINATED EXTERNAL OPERATIONS
@@ -387,4 +360,3 @@
 //
 //
 //
-
diff --git a/src/compute/skc/skc_create_cl.h b/src/compute/skc/skc_create_cl.h
deleted file mode 100644
index 0ab0fe0..0000000
--- a/src/compute/skc/skc_create_cl.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can
- * be found in the LICENSE file.
- *
- */
-
-#ifndef SKC_ONCE_SKC_CREATE_CL
-#define SKC_ONCE_SKC_CREATE_CL
-
-//
-//
-//
-
-#ifdef __APPLE__
-#include "OpenCL/opencl.h"
-#else
-#include "CL/opencl.h"
-#endif
-
-//
-//
-//
-
-#include "skc.h"
-
-//
-// CONTEXT CREATION
-//
-
-skc_err
-skc_context_create_cl(skc_context_t * context,
-                      cl_context      context_cl,
-                      cl_device_id    device_id_cl);
-
-//
-// FIXME -- SPECIALIZE SURFACE RENDER
-//
-
-#if 0
-
-//
-// SURFACE RENDER
-//
-
-typedef void (*skc_surface_render_pfn_notify)(skc_surface_t     surface,
-                                              skc_styling_t     styling,
-                                              skc_composition_t composition,
-                                              void            * data);
-skc_err
-skc_surface_render(skc_surface_t                 surface,
-                   uint32_t                const clip[4],
-                   skc_styling_t                 styling,
-                   skc_composition_t             composition,
-                   skc_surface_render_pfn_notify notify,
-                   void                        * data,
-                   void                        * fb); // FIXME FIXME
-
-#endif
-
-//
-//
-//
-
-#endif
-
-//
-//
-//
diff --git a/src/compute/skc/skc_types.h b/src/compute/skc/skc_types.h
index 0dbcf18..c0b1dc9 100644
--- a/src/compute/skc/skc_types.h
+++ b/src/compute/skc/skc_types.h
@@ -20,31 +20,28 @@
 //
 //
 
-typedef struct skc_context          * skc_context_t;
-typedef struct skc_path_builder     * skc_path_builder_t;
-typedef struct skc_raster_builder   * skc_raster_builder_t;
+typedef struct skc_context        * skc_context_t;
+typedef struct skc_path_builder   * skc_path_builder_t;
+typedef struct skc_raster_builder * skc_raster_builder_t;
 
-typedef struct skc_composition      * skc_composition_t;
-typedef struct skc_styling          * skc_styling_t;
+typedef struct skc_composition    * skc_composition_t;
+typedef struct skc_styling        * skc_styling_t;
 
-typedef struct skc_surface          * skc_surface_t;
+typedef struct skc_surface        * skc_surface_t;
 
-typedef        uint32_t               skc_path_t;
-typedef        uint32_t               skc_raster_t;
+typedef        uint32_t             skc_path_t;
+typedef        uint32_t             skc_raster_t;
 
-typedef        uint32_t               skc_layer_id;
-typedef        uint32_t               skc_group_id;
+typedef        uint32_t             skc_layer_id;
+typedef        uint32_t             skc_group_id;
 
-typedef        uint32_t               skc_styling_cmd_t;
+typedef        uint32_t             skc_styling_cmd_t;
 
-typedef        uint64_t               skc_weakref_t;
-typedef        skc_weakref_t          skc_transform_weakref_t;
-typedef        skc_weakref_t          skc_raster_clip_weakref_t;
+typedef        uint64_t             skc_weakref_t;
+typedef        skc_weakref_t        skc_transform_weakref_t;
+typedef        skc_weakref_t        skc_raster_clip_weakref_t;
 
-#if 0
-typedef struct skc_interop          * skc_interop_t;
-typedef        uint32_t               skc_interop_surface_t;
-#endif
+typedef        void               * skc_framebuffer_t;
 
 //
 //
@@ -62,10 +59,6 @@
 // RASTER CLIP LAYOUT: { x0, y0, x1, y1 }
 //
 
-//
-//
-//
-
 #endif
 
 //
diff --git a/src/compute/skc/surface.c b/src/compute/skc/surface.c
index 61bfac3..3d96bb6 100644
--- a/src/compute/skc/surface.c
+++ b/src/compute/skc/surface.c
@@ -34,39 +34,18 @@
   return SKC_ERR_SUCCESS;
 }
 
-skc_err
-skc_surface_clear(skc_surface_t  surface, 
-                  float    const rgba[4], 
-                  uint32_t const rect[4],
-                  void         * fb)
-{
-  surface->clear(surface->impl,rgba,rect,fb);
-
-  return SKC_ERR_SUCCESS;
-}
-
-skc_err
-skc_surface_blit(skc_surface_t  surface, 
-                 uint32_t const rect[4], 
-                 int32_t  const txty[2])
-{
-  surface->blit(surface->impl,rect,txty);
-
-  return SKC_ERR_SUCCESS;
-}
-
 //
 //
 //
 
 skc_err
-skc_surface_render(skc_surface_t                 surface,
-                   uint32_t                const clip[4],
-                   skc_styling_t                 styling,
-                   skc_composition_t             composition,
-                   skc_surface_render_pfn_notify notify,
-                   void                        * data,
-                   void                        * fb)
+skc_surface_render(skc_surface_t             surface,
+                   skc_styling_t             styling,
+                   skc_composition_t         composition,
+                   skc_framebuffer_t         fb,
+                   uint32_t            const clip[4],
+                   skc_surface_render_notify notify,
+                   void                    * data)
 {
   skc_err err;
 
@@ -74,17 +53,24 @@
   if ((err = skc_styling_seal(styling)) != SKC_ERR_SUCCESS)
     return err;
 
-  // seal composition -- force started
+  // seal composition -- force starts any dependent paths or rasters
   if ((err = skc_composition_seal(composition)) != SKC_ERR_SUCCESS)
     return err;
 
   //
-  // FIXME -- at some point, we will want non-overlapping clips to be
-  // rendered simultaneously. There is plenty of compute for nominal
-  // size render tasks so it might not make much a performance
-  // improvement.
+  // NOTE: there is purposefully no guard against any of the following
+  // use cases:
   //
-  surface->render(surface->impl,clip,styling,composition,notify,data,fb);
+  //   - Simultaneous renders to different frambuffers.
+  //
+  //   - Simultaneous renders with potentially overlapping clips to
+  //     the same framebuffer.
+  //
+  // NOTE: we may want to support concurrent rendering of
+  // non-overlapping clips.  This is fairly easy but at this point
+  // doesn't seem like a common use case.
+  //
+  surface->render(surface->impl,styling,composition,fb,clip,notify,data);
 
   return SKC_ERR_SUCCESS;
 }
diff --git a/src/compute/skc/surface.h b/src/compute/skc/surface.h
index 7f9dda8..94f9128 100644
--- a/src/compute/skc/surface.h
+++ b/src/compute/skc/surface.h
@@ -26,29 +26,15 @@
 
   skc_int                   ref_count;
 
-  //
-  // FIXME -- this list of pfn's isn't complete
-  //
-  void                   (* release)(struct skc_surface_impl * const impl);
-  void                   (* render )(struct skc_surface_impl * const impl,
-                                     uint32_t                  const clip[4],
-                                     skc_styling_t                   styling,
-                                     skc_composition_t               composition,
-                                     skc_surface_render_pfn_notify   notify,
-                                     void                          * data,
-                                     void                          * fb);
-  //
-  // FIXME -- these will probably be removed
-  //
-  void                   (* clear  )(struct skc_surface_impl * const impl,
-                                     float                     const rgba[4], 
-                                     skc_uint                  const rect[4],
-                                     void                    *       fb);
+  void (* release)(struct skc_surface_impl * const impl);
 
-  void                   (* blit   )(struct skc_surface_impl * const impl,
-                                     skc_uint                  const rect[4], 
-                                     skc_int                   const txty[2]);
-
+  void (* render )(struct skc_surface_impl * const impl,
+                   skc_styling_t                   styling,
+                   skc_composition_t               composition,
+                   skc_framebuffer_t               fb,
+                   uint32_t                  const clip[4],
+                   skc_surface_render_notify       notify,
+                   void                          * data);
 };
 
 //