More progress exposing interface

Much of the surface area exists for rendering now.

WIP of course still
diff --git a/piet-gpu-hal/src/hub.rs b/piet-gpu-hal/src/hub.rs
index 7b93372..ec4d169 100644
--- a/piet-gpu-hal/src/hub.rs
+++ b/piet-gpu-hal/src/hub.rs
@@ -401,6 +401,33 @@
     pub fn backend_type(&self) -> BackendType {
         self.0.device.backend_type()
     }
+
+    #[cfg(target_os = "macos")]
+    pub unsafe fn cmd_buf_from_raw_mtl(&self, raw_cmd_buf: &::metal::CommandBufferRef) -> CmdBuf {
+        let cmd_buf = Some(self.0.device.cmd_buf_from_raw_mtl(raw_cmd_buf));
+        let resources = Vec::new();
+        // Expect client to do cleanup manually.
+        let session = Weak::new();
+        CmdBuf {
+            cmd_buf,
+            fence: None,
+            resources,
+            session,
+        }
+    }
+
+    #[cfg(target_os = "macos")]
+    pub unsafe fn image_from_raw_mtl(
+        &self,
+        raw_texture: &::metal::TextureRef,
+        width: u32,
+        height: u32,
+    ) -> Image {
+        let image = self.0.device.image_from_raw_mtl(raw_texture, width, height);
+        // Expect client to do cleanup manually.
+        let session = Weak::new();
+        Image(Arc::new(ImageInner { image, session }))
+    }
 }
 
 impl SessionInner {
diff --git a/piet-gpu-hal/src/lib.rs b/piet-gpu-hal/src/lib.rs
index be0c1d6..3ee72b2 100644
--- a/piet-gpu-hal/src/lib.rs
+++ b/piet-gpu-hal/src/lib.rs
@@ -16,8 +16,8 @@
 mod mux;
 
 pub use crate::mux::{
-    DescriptorSet, Device, Fence, Instance, Pipeline, QueryPool, Sampler, Semaphore, ShaderCode, Surface,
-    Swapchain,
+    DescriptorSet, Device, Fence, Instance, Pipeline, QueryPool, Sampler, Semaphore, ShaderCode,
+    Surface, Swapchain,
 };
 pub use bufwrite::BufWrite;
 pub use hub::{
diff --git a/piet-gpu-hal/src/metal.rs b/piet-gpu-hal/src/metal.rs
index dd622fc..00eef49 100644
--- a/piet-gpu-hal/src/metal.rs
+++ b/piet-gpu-hal/src/metal.rs
@@ -216,6 +216,20 @@
             helpers,
         }
     }
+
+    pub fn cmd_buf_from_raw_mtl(&self, raw_cmd_buf: metal::CommandBuffer) -> CmdBuf {
+        let cmd_buf = raw_cmd_buf;
+        let helpers = self.helpers.clone();
+        CmdBuf { cmd_buf, helpers }
+    }
+
+    pub fn image_from_raw_mtl(&self, texture: metal::Texture, width: u32, height: u32) -> Image {
+        Image {
+            texture,
+            width,
+            height,
+        }
+    }
 }
 
 impl crate::backend::Device for MtlDevice {
diff --git a/piet-gpu-hal/src/mux.rs b/piet-gpu-hal/src/mux.rs
index 654a77b..e4d7937 100644
--- a/piet-gpu-hal/src/mux.rs
+++ b/piet-gpu-hal/src/mux.rs
@@ -209,8 +209,35 @@
 // missing functionality).
 impl Device {
     #[cfg(target_os = "macos")]
-    pub fn new_from_raw_mtl(device: &::metal::DeviceRef, queue: &::metal::CommandQueueRef) -> Device {
-        Device::Mtl(metal::MtlDevice::new_from_raw_mtl(device.to_owned(), queue.to_owned()))
+    pub fn new_from_raw_mtl(
+        device: &::metal::DeviceRef,
+        queue: &::metal::CommandQueueRef,
+    ) -> Device {
+        Device::Mtl(metal::MtlDevice::new_from_raw_mtl(
+            device.to_owned(),
+            queue.to_owned(),
+        ))
+    }
+
+    #[cfg(target_os = "macos")]
+    pub fn cmd_buf_from_raw_mtl(&self, raw_cmd_buf: &::metal::CommandBufferRef) -> CmdBuf {
+        // Note: this will cause problems if we support multiple back-ends on mac. But it will
+        // be a compile error;
+        let Device::Mtl(d) = self;
+        CmdBuf::Mtl(d.cmd_buf_from_raw_mtl(raw_cmd_buf.to_owned()))
+    }
+
+    #[cfg(target_os = "macos")]
+    pub fn image_from_raw_mtl(
+        &self,
+        raw_texture: &::metal::TextureRef,
+        width: u32,
+        height: u32,
+    ) -> Image {
+        // Note: this will cause problems if we support multiple back-ends on mac. But it will
+        // be a compile error;
+        let Device::Mtl(d) = self;
+        Image::Mtl(d.image_from_raw_mtl(raw_texture.to_owned(), width, height))
     }
 
     pub fn query_gpu_info(&self) -> GpuInfo {
diff --git a/piet-gpu/src/glyph_render.rs b/piet-gpu/src/glyph_render.rs
new file mode 100644
index 0000000..900938d
--- /dev/null
+++ b/piet-gpu/src/glyph_render.rs
@@ -0,0 +1,63 @@
+// Copyright 2022 The piet-gpu authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Also licensed under MIT license, at your choice.
+
+//! An experimental API for glyph rendering.
+
+use swash::{scale::ScaleContext, CacheKey, FontRef};
+
+use crate::{encoder::GlyphEncoder, PietGpuRenderContext};
+
+pub struct GlyphRenderer {
+    pub render_ctx: PietGpuRenderContext,
+    scale_context: ScaleContext,
+}
+
+#[repr(transparent)]
+pub struct FontId(CacheKey);
+
+impl GlyphRenderer {
+    pub fn new() -> GlyphRenderer {
+        GlyphRenderer {
+            render_ctx: PietGpuRenderContext::new(),
+            scale_context: ScaleContext::new(),
+        }
+    }
+
+    pub unsafe fn add_glyph(&mut self, font_data: &[u8], font_id: u64, glyph_id: u16) {
+        // This transmute is dodgy because the definition in swash isn't repr(transparent).
+        // I think the best solution is to have a from_u64 method, but we'll work that out
+        // later.
+        let font_id = FontId(std::mem::transmute(font_id));
+        let encoder = self.make_glyph(font_data, font_id, glyph_id);
+        self.render_ctx.encode_glyph(&encoder);
+        // TODO: don't fill glyph if RGBA
+        self.render_ctx.fill_glyph(0xff_ff_ff_ff);
+    }
+
+    fn make_glyph(&mut self, font_data: &[u8], font_id: FontId, glyph_id: u16) -> GlyphEncoder {
+        let mut encoder = GlyphEncoder::default();
+        let font_ref = FontRef {
+            data: font_data,
+            offset: 0,
+            key: font_id.0,
+        };
+        let mut scaler = self.scale_context.builder(font_ref).size(2048.).build();
+        if let Some(outline) = scaler.scale_outline(glyph_id) {
+            crate::text::append_outline(&mut encoder, outline.verbs(), outline.points());
+        }
+        encoder
+    }
+}
diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs
index f045d65..47de115 100644
--- a/piet-gpu/src/lib.rs
+++ b/piet-gpu/src/lib.rs
@@ -1,4 +1,5 @@
 mod encoder;
+pub mod glyph_render;
 mod gradient;
 mod pico_svg;
 mod render_ctx;
diff --git a/piet-gpu/src/text.rs b/piet-gpu/src/text.rs
index dec3ffa..0fb508b 100644
--- a/piet-gpu/src/text.rs
+++ b/piet-gpu/src/text.rs
@@ -260,7 +260,7 @@
     }
 }
 
-fn append_outline(encoder: &mut GlyphEncoder, verbs: &[Verb], points: &[Vector]) {
+pub(crate) fn append_outline(encoder: &mut GlyphEncoder, verbs: &[Verb], points: &[Vector]) {
     let mut path_encoder = encoder.path_encoder();
     let mut i = 0;
     for verb in verbs {