Roll external/skia b07b06e14..cbcb0a12a (1 commits)

https://skia.googlesource.com/skia.git/+log/b07b06e14..cbcb0a12a

2017-11-19 bsalomon@google.com Revert "Revert "Add Atlas Text interface for rendering SDF glyphs.""

The AutoRoll server is located here: https://android-roll.skia.org

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md

If the roll is causing failures, please contact the current sheriff, who should
be CC'd on the roll, and stop the roller if necessary.

Test: Presubmit checks will test this change.
Change-Id: If01694f893601ea76dfd32c337683a1fd2032b0f
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
diff --git a/Android.bp b/Android.bp
index d335079..055e1cb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,6 +72,7 @@
 
     export_include_dirs: [
         "include/android/",
+        "include/atlastext/",
         "include/c/",
         "include/codec/",
         "include/config/",
@@ -90,6 +91,7 @@
 
     local_include_dirs: [
         "include/android/",
+        "include/atlastext/",
         "include/c/",
         "include/codec/",
         "include/config/",
@@ -949,6 +951,7 @@
         "experimental/svg/model/",
         "gm/",
         "include/android/",
+        "include/atlastext/",
         "include/c/",
         "include/codec/",
         "include/config/",
@@ -1039,6 +1042,7 @@
         "gm/arcofzorro.cpp",
         "gm/arcto.cpp",
         "gm/arithmode.cpp",
+        "gm/atlastext.cpp",
         "gm/badpaint.cpp",
         "gm/beziereffects.cpp",
         "gm/beziers.cpp",
@@ -1682,6 +1686,7 @@
         "tools/gpu/GrContextFactory.cpp",
         "tools/gpu/GrTest.cpp",
         "tools/gpu/TestContext.cpp",
+        "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp",
         "tools/gpu/gl/GLTestContext.cpp",
         "tools/gpu/gl/command_buffer/GLTestContext_command_buffer.cpp",
         "tools/gpu/gl/debug/DebugGLTestContext.cpp",
@@ -1719,6 +1724,7 @@
         "experimental/svg/model/",
         "gm/",
         "include/android/",
+        "include/atlastext/",
         "include/c/",
         "include/codec/",
         "include/config/",
@@ -1922,6 +1928,7 @@
         "gm/arcofzorro.cpp",
         "gm/arcto.cpp",
         "gm/arithmode.cpp",
+        "gm/atlastext.cpp",
         "gm/badpaint.cpp",
         "gm/beziereffects.cpp",
         "gm/beziers.cpp",
@@ -2245,6 +2252,7 @@
         "tools/gpu/GrContextFactory.cpp",
         "tools/gpu/GrTest.cpp",
         "tools/gpu/TestContext.cpp",
+        "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp",
         "tools/gpu/gl/GLTestContext.cpp",
         "tools/gpu/gl/command_buffer/GLTestContext_command_buffer.cpp",
         "tools/gpu/gl/debug/DebugGLTestContext.cpp",
diff --git a/BUILD.gn b/BUILD.gn
index 6e24d3d..73a9c91 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -52,6 +52,7 @@
 declare_args() {
   skia_use_dng_sdk = !is_fuchsia && skia_use_libjpeg_turbo && skia_use_zlib
   skia_use_sfntly = skia_use_icu
+  skia_enable_atlas_text = is_skia_dev_build && skia_enable_gpu
 
   if (is_android) {
     skia_use_vulkan = defined(ndk_api) && ndk_api >= 24
@@ -92,6 +93,7 @@
   "include/encode",
   "include/gpu",
   "include/gpu/gl",
+  "include/atlastext",
   "include/pathops",
   "include/ports",
   "include/svg",
@@ -102,6 +104,9 @@
 if (skia_use_vulkan) {
   skia_public_includes += [ "include/gpu/vk" ]
 }
+if (skia_enable_atlas_text) {
+  skia_public_includes += [ "include/atlastext" ]
+}
 if (skia_use_metal) {
   skia_public_includes += [ "include/gpu/mtl" ]
 }
@@ -125,6 +130,9 @@
   if (!skia_enable_gpu) {
     defines += [ "SK_SUPPORT_GPU=0" ]
   }
+  if (skia_enable_atlas_text) {
+    defines += [ "SK_SUPPORT_ATLAS_TEXT=1" ]
+  }
 }
 
 # Skia internal APIs, used by Skia itself and a few test tools.
@@ -586,6 +594,10 @@
     libs += [ "Metal.framework" ]
     cflags_objcc += [ "-fobjc-arc" ]
   }
+
+  if (skia_enable_atlas_text) {
+    sources += skia_atlas_text_sources
+  }
 }
 
 optional("heif") {
@@ -992,6 +1004,7 @@
         "tools/gpu/GrContextFactory.cpp",
         "tools/gpu/GrTest.cpp",
         "tools/gpu/TestContext.cpp",
+        "tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp",
         "tools/gpu/gl/GLTestContext.cpp",
         "tools/gpu/gl/command_buffer/GLTestContext_command_buffer.cpp",
         "tools/gpu/gl/debug/DebugGLTestContext.cpp",
diff --git a/gm/atlastext.cpp b/gm/atlastext.cpp
new file mode 100644
index 0000000..b8acb0f
--- /dev/null
+++ b/gm/atlastext.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#if SK_SUPPORT_ATLAS_TEXT
+
+#include "SkAtlasTextContext.h"
+#include "SkAtlasTextFont.h"
+#include "SkAtlasTextTarget.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkTypeface.h"
+#include "gpu/TestContext.h"
+#include "gpu/atlastext/GLTestAtlasTextRenderer.h"
+#include "gpu/atlastext/TestAtlasTextRenderer.h"
+#include "sk_tool_utils.h"
+
+// GM that draws text using the Atlas Text interface offscreen and then blits that to the canvas.
+
+static SkScalar draw_string(SkAtlasTextTarget* target, const SkString& text, SkScalar x, SkScalar y,
+                            uint32_t color, sk_sp<SkTypeface> typeface, float size) {
+    auto font = SkAtlasTextFont::Make(std::move(typeface), size);
+    target->drawText(text.c_str(), text.size(), x, y, color, *font);
+    SkPaint paint;
+    paint.setTextSize(size);
+    return x + paint.measureText(text.c_str(), text.size());
+}
+
+class AtlasTextGM : public skiagm::GM {
+public:
+    AtlasTextGM() = default;
+
+protected:
+    SkString onShortName() override { return SkString("atlastext"); }
+
+    SkISize onISize() override { return SkISize::Make(kSize, kSize); }
+
+    void onOnceBeforeDraw() override {
+        fRenderer = sk_gpu_test::MakeGLTestAtlasTextRenderer();
+        if (!fRenderer) {
+            return;
+        }
+        fContext = SkAtlasTextContext::Make(fRenderer);
+        auto targetHandle = fRenderer->makeTargetHandle(kSize, kSize);
+        fTarget = SkAtlasTextTarget::Make(fContext, kSize, kSize, targetHandle);
+
+        fTypefaces[0] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic());
+        fTypefaces[1] =
+                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Italic());
+        fTypefaces[2] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Normal());
+        fTypefaces[3] =
+                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+        fTypefaces[4] = sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold());
+        fTypefaces[5] = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        if (!fRenderer) {
+            canvas->clear(SK_ColorRED);
+            return;
+        }
+        auto bmp = this->drawText();
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        canvas->drawBitmap(bmp, 0, 0);
+    }
+
+private:
+    SkBitmap drawText() {
+        static const int kSizes[] = {8, 13, 18, 23, 30};
+
+        static const SkString kTexts[] = {SkString("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+                                          SkString("abcdefghijklmnopqrstuvwxyz"),
+                                          SkString("0123456789"),
+                                          SkString("!@#$%^&*()<>[]{}")};
+        SkScalar x = 0;
+        SkScalar y = 10;
+
+        SkRandom random;
+        do {
+            for (auto s : kSizes) {
+                auto size = 2 * s;
+                for (const auto& typeface : fTypefaces) {
+                    for (const auto& text : kTexts) {
+                        uint32_t color = random.nextU();
+                        x = size + draw_string(fTarget.get(), text, x, y, color, typeface, size);
+                        x = SkScalarCeilToScalar(x);
+                        if (x + 100 > kSize) {
+                            x = 0;
+                            y += SkScalarCeilToScalar(size + 3);
+                            if (y > kSize) {
+                                fTarget->flush();
+                                return fRenderer->readTargetHandle(fTarget->handle());
+                            }
+                        }
+                    }
+                }
+            }
+        } while (true);
+    }
+
+    static constexpr int kSize = 1280;
+
+    sk_sp<SkTypeface> fTypefaces[6];
+    sk_sp<sk_gpu_test::TestAtlasTextRenderer> fRenderer;
+    std::unique_ptr<SkAtlasTextTarget> fTarget;
+    sk_sp<SkAtlasTextContext> fContext;
+
+    typedef GM INHERITED;
+};
+
+constexpr int AtlasTextGM::kSize;
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new AtlasTextGM;)
+
+#endif
diff --git a/gn/core.gni b/gn/core.gni
index 186e5af..259cd19 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -134,6 +134,7 @@
   "$_src/core/SkFindAndPlaceGlyph.h",
   "$_src/core/SkArenaAlloc.cpp",
   "$_src/core/SkArenaAlloc.h",
+  "$_src/core/SkArenaAllocList.h",
   "$_src/core/SkGaussFilter.cpp",
   "$_src/core/SkGaussFilter.h",
   "$_src/core/SkFlattenable.cpp",
diff --git a/gn/gm.gni b/gn/gm.gni
index 441c041..22d562f 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -22,6 +22,7 @@
   "$_gm/arcofzorro.cpp",
   "$_gm/arcto.cpp",
   "$_gm/arithmode.cpp",
+  "$_gm/atlastext.cpp",
   "$_gm/badpaint.cpp",
   "$_gm/beziereffects.cpp",
   "$_gm/beziers.cpp",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index e584bf5..a26111d 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -628,3 +628,14 @@
   "$_src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp",
   "$_src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp",
 ]
+
+skia_atlas_text_sources = [
+  "$_include/atlastext/SkAtlasTextContext.h",
+  "$_include/atlastext/SkAtlasTextFont.h",
+  "$_include/atlastext/SkAtlasTextRenderer.h",
+  "$_include/atlastext/SkAtlasTextTarget.h",
+
+  "$_src/atlastext/SkAtlasTextContext.cpp",
+  "$_src/atlastext/SkAtlasTextTarget.cpp",
+  "$_src/atlastext/SkInternalAtlasTextContext.cpp",
+]
diff --git a/include/atlastext/SkAtlasTextContext.h b/include/atlastext/SkAtlasTextContext.h
new file mode 100644
index 0000000..bb5de52
--- /dev/null
+++ b/include/atlastext/SkAtlasTextContext.h
@@ -0,0 +1,42 @@
+/*
+ * 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 SkAtlasTextContext_DEFINED
+#define SkAtlasTextContext_DEFINED
+
+#include "SkRefCnt.h"
+
+class SkAtlasTextRenderer;
+class SkInternalAtlasTextContext;
+
+SkAtlasTextRenderer* SkGetAtlasTextRendererFromInternalContext(class SkInternalAtlasTextContext&);
+
+/**
+ * Class that Atlas Text client uses to register their SkAtlasTextRenderer implementation and
+ * to create one or more SkAtlasTextTargets (destination surfaces for text rendering).
+ */
+class SK_API SkAtlasTextContext : public SkRefCnt {
+public:
+    static sk_sp<SkAtlasTextContext> Make(sk_sp<SkAtlasTextRenderer>);
+
+    SkAtlasTextRenderer* renderer() const {
+        return SkGetAtlasTextRendererFromInternalContext(*fInternalContext);
+    }
+
+    SkInternalAtlasTextContext& internal() { return *fInternalContext; }
+
+private:
+    SkAtlasTextContext() = delete;
+    SkAtlasTextContext(const SkAtlasTextContext&) = delete;
+    SkAtlasTextContext& operator=(const SkAtlasTextContext&) = delete;
+
+    SkAtlasTextContext(sk_sp<SkAtlasTextRenderer>);
+
+    std::unique_ptr<SkInternalAtlasTextContext> fInternalContext;
+};
+
+#endif
diff --git a/include/atlastext/SkAtlasTextFont.h b/include/atlastext/SkAtlasTextFont.h
new file mode 100644
index 0000000..a9e641f
--- /dev/null
+++ b/include/atlastext/SkAtlasTextFont.h
@@ -0,0 +1,35 @@
+/*
+ * 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 SkAtlasTextFont_DEFINED
+#define SkAtlasTextFont_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkTypeface.h"
+
+/** Represents a font at a size. TODO: What else do we need here (skewX, scaleX, vertical, ...)? */
+class SK_API SkAtlasTextFont : public SkRefCnt {
+public:
+    static sk_sp<SkAtlasTextFont> Make(sk_sp<SkTypeface> typeface, SkScalar size) {
+        return sk_sp<SkAtlasTextFont>(new SkAtlasTextFont(std::move(typeface), size));
+    }
+
+    SkTypeface* typeface() const { return fTypeface.get(); }
+
+    sk_sp<SkTypeface> refTypeface() const { return fTypeface; }
+
+    SkScalar size() const { return fSize; }
+
+private:
+    SkAtlasTextFont(sk_sp<SkTypeface> typeface, SkScalar size)
+            : fTypeface(std::move(typeface)), fSize(size) {}
+
+    sk_sp<SkTypeface> fTypeface;
+    SkScalar fSize;
+};
+
+#endif
diff --git a/include/atlastext/SkAtlasTextRenderer.h b/include/atlastext/SkAtlasTextRenderer.h
new file mode 100644
index 0000000..a78e270
--- /dev/null
+++ b/include/atlastext/SkAtlasTextRenderer.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPoint.h"
+#include "SkRefCnt.h"
+
+#ifndef SkAtlasTextRenderer_DEFINED
+#define SkAtlasTextRenderer_DEFINED
+
+/**
+ * This is the base class for a renderer implemented by the SkAtlasText client. The
+ * SkAtlasTextContext issues texture creations, deletions, uploads, and vertex draws to the
+ * renderer. The renderer must perform those actions in the order called to correctly render
+ * the text drawn to SkAtlasTextTargets.
+ */
+class SK_API SkAtlasTextRenderer : public SkRefCnt {
+public:
+    enum class AtlasFormat {
+        /** Unsigned normalized 8 bit single channel format. */
+        kA8
+    };
+
+    struct SDFVertex {
+        /** Position in device space (not normalized). */
+        SkPoint fPosition;
+        /** Color, same value for all four corners of a glyph quad. */
+        uint32_t fColor;
+        /** Texture coordinate (in texel units, not normalized). */
+        SkIPoint16 fTextureCoord;
+    };
+
+    virtual ~SkAtlasTextRenderer() = default;
+
+    /**
+     * Create a texture of the provided format with dimensions 'width' x 'height'
+     * and return a unique handle.
+     */
+    virtual void* createTexture(AtlasFormat, int width, int height) = 0;
+
+    /**
+     * Delete the texture with the passed handle.
+     */
+    virtual void deleteTexture(void* textureHandle) = 0;
+
+    /**
+     * Place the pixel data specified by 'data' in the texture with handle
+     * 'textureHandle' in the rectangle ['x', 'x' + 'width') x ['y', 'y' + 'height').
+     * 'rowBytes' specifies the byte offset between successive rows in 'data' and will always be
+     * a multiple of the number of bytes per pixel.
+     * The pixel format of data is the same as that of 'textureHandle'.
+     */
+    virtual void setTextureData(void* textureHandle, const void* data, int x, int y, int width,
+                                int height, size_t rowBytes) = 0;
+
+    /**
+     * Draws glyphs using SDFs. The SDF data resides in 'textureHandle'. The array
+     * 'vertices' provides interleaved device-space positions, colors, and
+     * texture coordinates. There are are 4 * 'quadCnt' entries in 'vertices'.
+     */
+    virtual void drawSDFGlyphs(void* targetHandle, void* textureHandle, const SDFVertex vertices[],
+                               int quadCnt) = 0;
+
+    /** Called when a SkAtlasTextureTarget is destroyed. */
+    virtual void targetDeleted(void* targetHandle) = 0;
+};
+
+#endif
diff --git a/include/atlastext/SkAtlasTextTarget.h b/include/atlastext/SkAtlasTextTarget.h
new file mode 100644
index 0000000..0859afd
--- /dev/null
+++ b/include/atlastext/SkAtlasTextTarget.h
@@ -0,0 +1,61 @@
+/*
+ * 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 SkAtlasTextTarget_DEFINED
+#define SkAtlasTextTarget_DEFINED
+
+#include <memory>
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+
+class SkAtlasTextContext;
+class SkAtlasTextFont;
+
+/** Represents a client-created renderable surface and is used to draw text into the surface. */
+class SK_API SkAtlasTextTarget {
+public:
+    virtual ~SkAtlasTextTarget();
+
+    /**
+     * Creates a text drawing target. ‘handle’ is used to identify this rendering surface when
+     * draws are flushed to the SkAtlasTextContext's SkAtlasTextRenderer.
+     */
+    static std::unique_ptr<SkAtlasTextTarget> Make(sk_sp<SkAtlasTextContext>, int width, int height,
+                                                   void* handle);
+
+    /**
+     * Enqueues a text draw in the target. The meaning of 'color' here is interpreted by the
+     * client's SkAtlasTextRenderer when it actually renders the text.
+     */
+    virtual void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
+                          uint32_t color, const SkAtlasTextFont& font) = 0;
+
+    /** Issues all queued text draws to SkAtlasTextRenderer. */
+    virtual void flush() = 0;
+
+    int width() const { return fWidth; }
+    int height() const { return fHeight; }
+
+    void* handle() const { return fHandle; }
+
+    SkAtlasTextContext* context() const { return fContext.get(); }
+
+protected:
+    SkAtlasTextTarget(sk_sp<SkAtlasTextContext>, int width, int height, void* handle);
+
+    void* const fHandle;
+    const sk_sp<SkAtlasTextContext> fContext;
+    const int fWidth;
+    const int fHeight;
+
+private:
+    SkAtlasTextTarget() = delete;
+    SkAtlasTextTarget(const SkAtlasTextContext&) = delete;
+    SkAtlasTextTarget& operator=(const SkAtlasTextContext&) = delete;
+};
+
+#endif
diff --git a/include/core/SkPostConfig.h b/include/core/SkPostConfig.h
index 3a39f66..ce81904 100644
--- a/include/core/SkPostConfig.h
+++ b/include/core/SkPostConfig.h
@@ -89,6 +89,12 @@
 #  define SK_SUPPORT_GPU 1
 #endif
 
+#if !defined(SK_SUPPORT_ATLAS_TEXT)
+#  define SK_SUPPORT_ATLAS_TEXT 0
+#elif SK_SUPPORT_ATLAS_TEXT && !SK_SUPPORT_GPU
+#  error "SK_SUPPORT_ATLAS_TEXT requires SK_SUPPORT_GPU"
+#endif
+
 /**
  * The clang static analyzer likes to know that when the program is not
  * expected to continue (crash, assertion failure, etc). It will notice that
diff --git a/public.bzl b/public.bzl
index f455981..dfc163b 100644
--- a/public.bzl
+++ b/public.bzl
@@ -255,6 +255,9 @@
 
         # Only used to regenerate the lexer
         "src/sksl/lex/*",
+
+        # Atlas text
+        "src/atlastext/*",
     ],
 )
 
diff --git a/src/atlastext/SkAtlasTextContext.cpp b/src/atlastext/SkAtlasTextContext.cpp
new file mode 100644
index 0000000..85e7911
--- /dev/null
+++ b/src/atlastext/SkAtlasTextContext.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAtlasTextContext.h"
+#include "SkAtlasTextRenderer.h"
+#include "SkInternalAtlasTextContext.h"
+
+sk_sp<SkAtlasTextContext> SkAtlasTextContext::Make(sk_sp<SkAtlasTextRenderer> renderer) {
+    return sk_sp<SkAtlasTextContext>(new SkAtlasTextContext(std::move(renderer)));
+}
+
+SkAtlasTextContext::SkAtlasTextContext(sk_sp<SkAtlasTextRenderer> renderer)
+        : fInternalContext(SkInternalAtlasTextContext::Make(std::move(renderer))) {}
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp
new file mode 100644
index 0000000..57ece37
--- /dev/null
+++ b/src/atlastext/SkAtlasTextTarget.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAtlasTextTarget.h"
+#include "GrClip.h"
+#include "SkAtlasTextContext.h"
+#include "SkAtlasTextFont.h"
+#include "SkAtlasTextRenderer.h"
+#include "SkGr.h"
+#include "SkInternalAtlasTextContext.h"
+#include "ops/GrAtlasTextOp.h"
+#include "text/GrAtlasTextContext.h"
+
+SkAtlasTextTarget::SkAtlasTextTarget(sk_sp<SkAtlasTextContext> context, int width, int height,
+                                     void* handle)
+        : fHandle(handle), fContext(std::move(context)), fWidth(width), fHeight(height) {}
+
+SkAtlasTextTarget::~SkAtlasTextTarget() { fContext->renderer()->targetDeleted(fHandle); }
+
+//////////////////////////////////////////////////////////////////////////////
+
+static const GrColorSpaceInfo kColorSpaceInfo(nullptr, kRGBA_8888_GrPixelConfig);
+
+//////////////////////////////////////////////////////////////////////////////
+
+class SkInternalAtlasTextTarget : public GrTextUtils::Target, public SkAtlasTextTarget {
+public:
+    SkInternalAtlasTextTarget(sk_sp<SkAtlasTextContext> context, int width, int height,
+                              void* handle)
+            : GrTextUtils::Target(width, height, kColorSpaceInfo)
+            , SkAtlasTextTarget(std::move(context), width, height, handle) {}
+
+    /** GrTextUtils::Target overrides */
+
+    void addDrawOp(const GrClip&, std::unique_ptr<GrAtlasTextOp> op) override;
+
+    void drawPath(const GrClip&, const SkPath&, const SkPaint&, const SkMatrix& viewMatrix,
+                  const SkMatrix* pathMatrix, const SkIRect& clipBounds) override {
+        SkDebugf("Path glyph??");
+    }
+
+    void makeGrPaint(GrMaskFormat, const SkPaint& skPaint, const SkMatrix&,
+                     GrPaint* grPaint) override {
+        grPaint->setColor4f(SkColorToPremulGrColor4fLegacy(skPaint.getColor()));
+    }
+
+    /** SkAtlasTextTarget overrides */
+
+    void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, uint32_t color,
+                  const SkAtlasTextFont&) override;
+    void flush() override;
+
+private:
+    uint32_t fColor;
+    using SkAtlasTextTarget::fWidth;
+    using SkAtlasTextTarget::fHeight;
+    struct RecordedOp {
+        std::unique_ptr<GrAtlasTextOp> fOp;
+        uint32_t fColor;
+    };
+    SkTArray<RecordedOp, true> fOps;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<SkAtlasTextTarget> SkAtlasTextTarget::Make(sk_sp<SkAtlasTextContext> context,
+                                                           int width, int height, void* handle) {
+    return std::unique_ptr<SkAtlasTextTarget>(
+            new SkInternalAtlasTextTarget(std::move(context), width, height, handle));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+#include "GrContextPriv.h"
+#include "GrDrawingManager.h"
+
+void SkInternalAtlasTextTarget::drawText(const void* text, size_t byteLength, SkScalar x,
+                                         SkScalar y, uint32_t color, const SkAtlasTextFont& font) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setTypeface(font.refTypeface());
+    paint.setTextSize(font.size());
+    paint.setStyle(SkPaint::kFill_Style);
+
+    // TODO: Figure out what if anything to do with these:
+    // Paint setTextEncoding? Font isEnableByteCodeHints()? Font isUseNonLinearMetrics()?
+
+    // The atlas text context does munging of the paint color. We store the client's color here
+    // and the context will write it into the final vertices given to the client's renderer.
+    fColor = color;
+
+    // The pixel geometry here is arbitrary. We don't draw LCD text.
+    SkSurfaceProps props(SkSurfaceProps::kUseDistanceFieldFonts_Flag, kUnknown_SkPixelGeometry);
+    auto* grContext = this->context()->internal().grContext();
+    auto bounds = SkIRect::MakeWH(fWidth, fHeight);
+    auto atlasTextContext = grContext->contextPriv().drawingManager()->getAtlasTextContext();
+    atlasTextContext->drawText(grContext, this, GrNoClip(), paint, SkMatrix::I(), props,
+                               (const char*)text, byteLength, x, y, bounds);
+}
+
+void SkInternalAtlasTextTarget::addDrawOp(const GrClip& clip, std::unique_ptr<GrAtlasTextOp> op) {
+    SkASSERT(clip.quickContains(SkRect::MakeIWH(fWidth, fHeight)));
+    // The SkAtlasTextRenderer currently only handles grayscale SDF glyphs.
+    if (op->maskType() != GrAtlasTextOp::kGrayscaleDistanceField_MaskType) {
+        return;
+    }
+    // TODO: batch ops here.
+    op->visitProxies([](GrSurfaceProxy*) {});
+    fOps.emplace_back(RecordedOp{std::move(op), fColor});
+}
+
+void SkInternalAtlasTextTarget::flush() {
+    for (int i = 0; i < fOps.count(); ++i) {
+        fOps[i].fOp->executeForTextTarget(this, fOps[i].fColor);
+    }
+    this->context()->internal().flush();
+    fOps.reset();
+}
+
+void GrAtlasTextOp::executeForTextTarget(SkAtlasTextTarget* target, uint32_t color) {
+    FlushInfo flushInfo;
+    SkAutoGlyphCache glyphCache;
+    auto& context = target->context()->internal();
+    auto* atlasGlyphCache = context.grContext()->getAtlasGlyphCache();
+    for (int i = 0; i < fGeoCount; ++i) {
+        GrAtlasTextBlob::VertexRegenerator regenerator(
+                fGeoData[i].fBlob, fGeoData[i].fRun, fGeoData[i].fSubRun, fGeoData[i].fViewMatrix,
+                fGeoData[i].fX, fGeoData[i].fY, color, &context, atlasGlyphCache, &glyphCache);
+        GrAtlasTextBlob::VertexRegenerator::Result result;
+        do {
+            result = regenerator.regenerate();
+            context.recordDraw(result.fFirstVertex, result.fGlyphsRegenerated, target->handle());
+            if (!result.fFinished) {
+                // Make space in the atlas so we can continue generating vertices.
+                context.flush();
+            }
+        } while (!result.fFinished);
+    }
+}
diff --git a/src/atlastext/SkInternalAtlasTextContext.cpp b/src/atlastext/SkInternalAtlasTextContext.cpp
new file mode 100644
index 0000000..8be7e1f
--- /dev/null
+++ b/src/atlastext/SkInternalAtlasTextContext.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkInternalAtlasTextContext.h"
+#include "GrContext.h"
+#include "SkAtlasTextContext.h"
+#include "SkAtlasTextRenderer.h"
+#include "text/GrAtlasGlyphCache.h"
+
+SkAtlasTextRenderer* SkGetAtlasTextRendererFromInternalContext(
+        class SkInternalAtlasTextContext& internal) {
+    return internal.renderer();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+std::unique_ptr<SkInternalAtlasTextContext> SkInternalAtlasTextContext::Make(
+        sk_sp<SkAtlasTextRenderer> renderer) {
+    return std::unique_ptr<SkInternalAtlasTextContext>(
+            new SkInternalAtlasTextContext(std::move(renderer)));
+}
+
+SkInternalAtlasTextContext::SkInternalAtlasTextContext(sk_sp<SkAtlasTextRenderer> renderer)
+        : fRenderer(std::move(renderer)) {
+    GrContextOptions options;
+    options.fAllowMultipleGlyphCacheTextures = GrContextOptions::Enable::kNo;
+    options.fMinDistanceFieldFontSize = 0.f;
+    options.fGlyphsAsPathsFontSize = SK_ScalarInfinity;
+    fGrContext = GrContext::MakeMock(nullptr, options);
+}
+
+SkInternalAtlasTextContext::~SkInternalAtlasTextContext() {
+    if (fDistanceFieldAtlas.fProxy) {
+        SkASSERT(1 == fGrContext->getAtlasGlyphCache()->getAtlasPageCount(kA8_GrMaskFormat));
+        fRenderer->deleteTexture(fDistanceFieldAtlas.fTextureHandle);
+    }
+}
+
+GrAtlasGlyphCache* SkInternalAtlasTextContext::atlasGlyphCache() {
+    return fGrContext->getAtlasGlyphCache();
+}
+
+GrTextBlobCache* SkInternalAtlasTextContext::textBlobCache() {
+    return fGrContext->getTextBlobCache();
+}
+
+GrDeferredUploadToken SkInternalAtlasTextContext::addInlineUpload(
+        GrDeferredTextureUploadFn&& upload) {
+    auto token = this->nextDrawToken();
+    fInlineUploads.append(&fArena, InlineUpload{std::move(upload), token});
+    return token;
+}
+
+GrDeferredUploadToken SkInternalAtlasTextContext::addASAPUpload(
+        GrDeferredTextureUploadFn&& upload) {
+    fASAPUploads.append(&fArena, std::move(upload));
+    return this->nextTokenToFlush();
+}
+
+void SkInternalAtlasTextContext::recordDraw(const void* srcVertexData, int glyphCnt,
+                                            void* targetHandle) {
+    auto vertexDataSize = sizeof(SkAtlasTextRenderer::SDFVertex) * 4 * glyphCnt;
+    auto vertexData = fArena.makeArrayDefault<char>(vertexDataSize);
+    memcpy(vertexData, srcVertexData, vertexDataSize);
+    for (int i = 0; i < 4 * glyphCnt; ++i) {
+        auto* vertex = reinterpret_cast<SkAtlasTextRenderer::SDFVertex*>(vertexData) + i;
+        // GrAtlasTextContext encodes a texture index into the lower bit of each texture coord.
+        // This isn't expected by SkAtlasTextRenderer subclasses.
+        vertex->fTextureCoord.fX /= 2;
+        vertex->fTextureCoord.fY /= 2;
+    }
+    fDraws.append(&fArena, Draw{glyphCnt, this->issueDrawToken(), targetHandle, vertexData});
+}
+
+void SkInternalAtlasTextContext::flush() {
+    auto* atlasGlyphCache = fGrContext->getAtlasGlyphCache();
+    if (!fDistanceFieldAtlas.fProxy) {
+        SkASSERT(1 == atlasGlyphCache->getAtlasPageCount(kA8_GrMaskFormat));
+        fDistanceFieldAtlas.fProxy = atlasGlyphCache->getProxies(kA8_GrMaskFormat)->get();
+        fDistanceFieldAtlas.fTextureHandle =
+                fRenderer->createTexture(SkAtlasTextRenderer::AtlasFormat::kA8,
+                                         fDistanceFieldAtlas.fProxy->width(),
+                                         fDistanceFieldAtlas.fProxy->height());
+    }
+    GrDeferredTextureUploadWritePixelsFn writePixelsFn =
+            [this](GrTextureProxy* proxy, int left, int top, int width, int height,
+                   GrPixelConfig config, const void* data, size_t rowBytes) -> bool {
+        SkASSERT(kAlpha_8_GrPixelConfig == config);
+        SkASSERT(proxy == this->fDistanceFieldAtlas.fProxy);
+        void* handle = fDistanceFieldAtlas.fTextureHandle;
+        this->fRenderer->setTextureData(handle, data, left, top, width, height, rowBytes);
+        return true;
+    };
+    for (const auto& upload : fASAPUploads) {
+        upload(writePixelsFn);
+    }
+    auto draw = fDraws.begin();
+    auto inlineUpload = fInlineUploads.begin();
+    while (draw != fDraws.end()) {
+        while (inlineUpload != fInlineUploads.end() && inlineUpload->fToken == draw->fToken) {
+            inlineUpload->fUpload(writePixelsFn);
+            ++inlineUpload;
+        }
+        auto vertices = reinterpret_cast<const SkAtlasTextRenderer::SDFVertex*>(draw->fVertexData);
+        fRenderer->drawSDFGlyphs(draw->fTargetHandle, fDistanceFieldAtlas.fTextureHandle, vertices,
+                                 draw->fGlyphCnt);
+        ++draw;
+    }
+    fASAPUploads.reset();
+    fInlineUploads.reset();
+    fDraws.reset();
+    fArena.reset();
+}
diff --git a/src/atlastext/SkInternalAtlasTextContext.h b/src/atlastext/SkInternalAtlasTextContext.h
new file mode 100644
index 0000000..1bb12ce
--- /dev/null
+++ b/src/atlastext/SkInternalAtlasTextContext.h
@@ -0,0 +1,80 @@
+/*
+ * 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 SkInternalAtlasTextContext_DEFINED
+#define SkInternalAtlasTextContext_DEFINED
+
+#include "GrDeferredUpload.h"
+#include "SkArenaAlloc.h"
+#include "SkArenaAllocList.h"
+#include "SkRefCnt.h"
+
+class SkAtlasTextRenderer;
+class GrContext;
+class GrAtlasGlyphCache;
+class GrTextBlobCache;
+
+/**
+ * The implementation of SkAtlasTextContext. This exists to hide the details from the public class.
+ * and to be able to use other private types.
+ */
+class SkInternalAtlasTextContext : public GrDeferredUploadTarget {
+public:
+    static std::unique_ptr<SkInternalAtlasTextContext> Make(sk_sp<SkAtlasTextRenderer>);
+
+    ~SkInternalAtlasTextContext() override;
+
+    SkAtlasTextRenderer* renderer() const { return fRenderer.get(); }
+
+    GrContext* grContext() const { return fGrContext.get(); }
+    GrAtlasGlyphCache* atlasGlyphCache();
+    GrTextBlobCache* textBlobCache();
+
+    GrDeferredUploadToken addInlineUpload(GrDeferredTextureUploadFn&&) override;
+
+    GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&&) override;
+
+    void recordDraw(const void* vertexData, int glyphCnt, void* targetHandle);
+
+    void flush();
+
+private:
+    class DeferredUploader;
+    SkInternalAtlasTextContext() = delete;
+    SkInternalAtlasTextContext(const SkInternalAtlasTextContext&) = delete;
+    SkInternalAtlasTextContext& operator=(const SkInternalAtlasTextContext&) = delete;
+
+    SkInternalAtlasTextContext(sk_sp<SkAtlasTextRenderer>);
+
+    sk_sp<SkAtlasTextRenderer> fRenderer;
+
+    struct AtlasTexture {
+        void* fTextureHandle = nullptr;
+        GrTextureProxy* fProxy = nullptr;
+    };
+
+    struct Draw {
+        int fGlyphCnt;
+        GrDeferredUploadToken fToken;
+        void* fTargetHandle;
+        const void* fVertexData;
+    };
+
+    struct InlineUpload {
+        GrDeferredTextureUploadFn fUpload;
+        GrDeferredUploadToken fToken;
+    };
+
+    SkArenaAllocList<InlineUpload> fInlineUploads;
+    SkArenaAllocList<Draw> fDraws;
+    SkArenaAllocList<GrDeferredTextureUploadFn> fASAPUploads;
+    SkArenaAlloc fArena{1024 * 40};
+    sk_sp<GrContext> fGrContext;
+    AtlasTexture fDistanceFieldAtlas;
+};
+
+#endif
diff --git a/src/core/SkArenaAlloc.h b/src/core/SkArenaAlloc.h
index 9e97161..2f23382 100644
--- a/src/core/SkArenaAlloc.h
+++ b/src/core/SkArenaAlloc.h
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#ifndef SkFixedAlloc_DEFINED
-#define SkFixedAlloc_DEFINED
+#ifndef SkArenaAlloc_DEFINED
+#define SkArenaAlloc_DEFINED
 
 #include "SkRefCnt.h"
 #include "SkTFitsIn.h"
@@ -240,4 +240,4 @@
     using INHERITED = SkArenaAlloc;
 };
 
-#endif//SkFixedAlloc_DEFINED
+#endif  // SkArenaAlloc_DEFINED
diff --git a/src/core/SkArenaAllocList.h b/src/core/SkArenaAllocList.h
new file mode 100644
index 0000000..b4e442b
--- /dev/null
+++ b/src/core/SkArenaAllocList.h
@@ -0,0 +1,79 @@
+/*
+ * 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 SkArenaAllocList_DEFINED
+#define SkArenaAllocList_DEFINED
+
+#include "SkArenaAlloc.h"
+
+/**
+ * A singly linked list of Ts stored in a SkArenaAlloc. The arena rather than the list owns
+ * the elements. This supports forward iteration and range based for loops.
+ */
+template <typename T>
+class SkArenaAllocList {
+private:
+    struct Node;
+
+public:
+    SkArenaAllocList() = default;
+
+    void reset() { fHead = fTail = nullptr; }
+
+    template <typename... Args>
+    inline T& append(SkArenaAlloc* arena, Args... args);
+
+    class Iter {
+    public:
+        Iter() = default;
+        inline Iter& operator++();
+        T& operator*() const { return fCurr->fT; }
+        T* operator->() const { return &fCurr->fT; }
+        bool operator==(const Iter& that) const { return fCurr == that.fCurr; }
+        bool operator!=(const Iter& that) const { return !(*this == that); }
+
+    private:
+        friend class SkArenaAllocList;
+        explicit Iter(Node* node) : fCurr(node) {}
+        Node* fCurr = nullptr;
+    };
+
+    Iter begin() { return Iter(fHead); }
+    Iter end() { return Iter(); }
+    Iter tail() { return Iter(fTail); }
+
+private:
+    struct Node {
+        template <typename... Args>
+        Node(Args... args) : fT(std::forward<Args>(args)...) {}
+        T fT;
+        Node* fNext = nullptr;
+    };
+    Node* fHead = nullptr;
+    Node* fTail = nullptr;
+};
+
+template <typename T>
+template <typename... Args>
+T& SkArenaAllocList<T>::append(SkArenaAlloc* arena, Args... args) {
+    SkASSERT(!fHead == !fTail);
+    auto* n = arena->make<Node>(std::forward<Args>(args)...);
+    if (!fTail) {
+        fHead = fTail = n;
+    } else {
+        fTail = fTail->fNext = n;
+    }
+    return fTail->fT;
+}
+
+template <typename T>
+typename SkArenaAllocList<T>::Iter& SkArenaAllocList<T>::Iter::operator++() {
+    fCurr = fCurr->fNext;
+    return *this;
+}
+
+#endif
diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp
index 6ac5d13..5245a9d 100644
--- a/src/gpu/GrOpFlushState.cpp
+++ b/src/gpu/GrOpFlushState.cpp
@@ -12,25 +12,6 @@
 #include "GrResourceProvider.h"
 #include "GrTexture.h"
 
-template <typename T>
-template <typename... Args>
-T& GrOpFlushState::List<T>::append(SkArenaAlloc* arena, Args... args) {
-    SkASSERT(!fHead == !fTail);
-    auto* n = arena->make<Node>(std::forward<Args>(args)...);
-    if (!fTail) {
-        fHead = fTail = n;
-    } else {
-        fTail = fTail->fNext = n;
-    }
-    return fTail->fT;
-}
-
-template <typename T>
-typename GrOpFlushState::List<T>::Iter& GrOpFlushState::List<T>::Iter::operator++() {
-    fCurr = fCurr->fNext;
-    return *this;
-}
-
 //////////////////////////////////////////////////////////////////////////////
 
 GrOpFlushState::GrOpFlushState(GrGpu* gpu, GrResourceProvider* resourceProvider)
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index b20098a..a9cf9a0 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -13,6 +13,7 @@
 #include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
 #include "SkArenaAlloc.h"
+#include "SkArenaAllocList.h"
 #include "ops/GrMeshDrawOp.h"
 
 class GrGpu;
@@ -110,53 +111,6 @@
         uint32_t fOpID;
     };
 
-    /**
-     * A singly linked list of Ts stored in a SkArenaAlloc. The arena rather than the list owns
-     * the elements. This supports forward iteration and range based for loops.
-     */
-    template <typename T>
-    class List {
-    private:
-        struct Node;
-
-    public:
-        List() = default;
-
-        void reset() { fHead = fTail = nullptr; }
-
-        template <typename... Args>
-        T& append(SkArenaAlloc* arena, Args... args);
-
-        class Iter {
-        public:
-            Iter() = default;
-            Iter& operator++();
-            T& operator*() const { return fCurr->fT; }
-            T* operator->() const { return &fCurr->fT; }
-            bool operator==(const Iter& that) const { return fCurr == that.fCurr; }
-            bool operator!=(const Iter& that) const { return !(*this == that); }
-
-        private:
-            friend class List;
-            explicit Iter(Node* node) : fCurr(node) {}
-            Node* fCurr = nullptr;
-        };
-
-        Iter begin() { return Iter(fHead); }
-        Iter end() { return Iter(); }
-        Iter tail() { return Iter(fTail); }
-
-    private:
-        struct Node {
-            template <typename... Args>
-            Node(Args... args) : fT(std::forward<Args>(args)...) {}
-            T fT;
-            Node* fNext = nullptr;
-        };
-        Node* fHead = nullptr;
-        Node* fTail = nullptr;
-    };
-
     // Storage for ops' pipelines, draws, and inline uploads.
     SkArenaAlloc fArena{sizeof(GrPipeline) * 100};
 
@@ -165,9 +119,9 @@
     GrIndexBufferAllocPool fIndexPool;
 
     // Data stored on behalf of the ops being flushed.
-    List<GrDeferredTextureUploadFn> fAsapUploads;
-    List<InlineUpload> fInlineUploads;
-    List<Draw> fDraws;
+    SkArenaAllocList<GrDeferredTextureUploadFn> fAsapUploads;
+    SkArenaAllocList<InlineUpload> fInlineUploads;
+    SkArenaAllocList<Draw> fDraws;
     // TODO: These should go in the arena. However, GrGpuCommandBuffer and other classes currently
     // accept contiguous arrays of meshes.
     SkSTArray<16, GrMesh> fMeshes;
@@ -185,9 +139,9 @@
     GrGpuCommandBuffer* fCommandBuffer = nullptr;
 
     // Variables that are used to track where we are in lists as ops are executed
-    List<Draw>::Iter fCurrDraw;
+    SkArenaAllocList<Draw>::Iter fCurrDraw;
     int fCurrMesh;
-    List<InlineUpload>::Iter fCurrUpload;
+    SkArenaAllocList<InlineUpload>::Iter fCurrUpload;
 };
 
 #endif
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 240b98b..c8ef643 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -12,6 +12,8 @@
 #include "text/GrAtlasTextContext.h"
 #include "text/GrDistanceFieldAdjustTable.h"
 
+class SkAtlasTextTarget;
+
 class GrAtlasTextOp final : public GrMeshDrawOp {
 public:
     DEFINE_OP_CLASS_ID
@@ -116,6 +118,20 @@
     RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
                                 GrPixelConfigIsClamped dstIsClamped) override;
 
+    enum MaskType {
+        kGrayscaleCoverageMask_MaskType,
+        kLCDCoverageMask_MaskType,
+        kColorBitmapMask_MaskType,
+        kAliasedDistanceField_MaskType,
+        kGrayscaleDistanceField_MaskType,
+        kLCDDistanceField_MaskType,
+        kLCDBGRDistanceField_MaskType,
+    };
+
+    MaskType maskType() const { return fMaskType; }
+
+    void executeForTextTarget(SkAtlasTextTarget*, uint32_t color);
+
 private:
     // The minimum number of Geometry we will try to allocate.
     static constexpr auto kMinGeometryAllocated = 12;
@@ -180,16 +196,6 @@
 
     sk_sp<GrGeometryProcessor> setupDfProcessor() const;
 
-    enum MaskType {
-        kGrayscaleCoverageMask_MaskType,
-        kLCDCoverageMask_MaskType,
-        kColorBitmapMask_MaskType,
-        kAliasedDistanceField_MaskType,
-        kGrayscaleDistanceField_MaskType,
-        kLCDDistanceField_MaskType,
-        kLCDBGRDistanceField_MaskType,
-    };
-
     SkAutoSTMalloc<kMinGeometryAllocated, Geometry> fGeoData;
     int fGeoDataAllocSize;
     GrColor fColor;
diff --git a/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp
new file mode 100644
index 0000000..543f460
--- /dev/null
+++ b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp
@@ -0,0 +1,439 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GLTestAtlasTextRenderer.h"
+#include "../gl/GLTestContext.h"
+#include "SkBitmap.h"
+#include "TestAtlasTextRenderer.h"
+#include "gl/GrGLDefines.h"
+
+using sk_gpu_test::GLTestContext;
+
+namespace {
+
+class GLTestAtlasTextRenderer : public sk_gpu_test::TestAtlasTextRenderer {
+public:
+    GLTestAtlasTextRenderer(std::unique_ptr<GLTestContext>);
+
+    void* createTexture(AtlasFormat, int width, int height) override;
+
+    void deleteTexture(void* textureHandle) override;
+
+    void setTextureData(void* textureHandle, const void* data, int x, int y, int width, int height,
+                        size_t rowBytes) override;
+
+    void drawSDFGlyphs(void* targetHandle, void* textureHandle, const SDFVertex vertices[],
+                       int quadCnt) override;
+
+    void* makeTargetHandle(int width, int height) override;
+
+    void targetDeleted(void* target) override;
+
+    SkBitmap readTargetHandle(void* target) override;
+
+    bool initialized() const { return 0 != fProgram; }
+
+private:
+    struct AtlasTexture {
+        GrGLuint fID;
+        AtlasFormat fFormat;
+        int fWidth;
+        int fHeight;
+    };
+
+    struct Target {
+        GrGLuint fFBOID;
+        GrGLuint fRBID;
+        int fWidth;
+        int fHeight;
+    };
+
+    std::unique_ptr<GLTestContext> fContext;
+    GrGLuint fProgram = 0;
+    GrGLint fDstScaleAndTranslateLocation = 0;
+    GrGLint fAtlasInvSizeLocation = 0;
+    GrGLint fSamplerLocation = 0;
+};
+
+#define callgl(NAME, ...) fContext->gl()->fFunctions.f##NAME(__VA_ARGS__)
+#define checkgl()                                               \
+    do {                                                        \
+        static constexpr auto line = __LINE__;                  \
+        auto error = fContext->gl()->fFunctions.fGetError();    \
+        if (error != GR_GL_NO_ERROR) {                          \
+            SkDebugf("GL ERROR: 0x%x, line %d\n", error, line); \
+        }                                                       \
+    } while (false)
+
+GLTestAtlasTextRenderer::GLTestAtlasTextRenderer(std::unique_ptr<GLTestContext> context)
+        : fContext(std::move(context)) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+    auto vs = callgl(CreateShader, GR_GL_VERTEX_SHADER);
+    static constexpr char kGLVersionString[] = "#version 430 compatibility";
+    static constexpr char kGLESVersionString[] = "#version 300 es";
+    GrGLint lengths[2];
+    const GrGLchar* strings[2];
+    switch (fContext->gl()->fStandard) {
+        case kGL_GrGLStandard:
+            strings[0] = kGLVersionString;
+            lengths[0] = static_cast<GrGLint>(SK_ARRAY_COUNT(kGLVersionString)) - 1;
+            break;
+        case kGLES_GrGLStandard:
+            strings[0] = kGLESVersionString;
+            lengths[0] = static_cast<GrGLint>(SK_ARRAY_COUNT(kGLESVersionString)) - 1;
+            break;
+        default:
+            strings[0] = nullptr;
+            lengths[0] = 0;
+            break;
+    }
+
+    static constexpr const char kVS[] = R"(
+        uniform vec4 uDstScaleAndTranslate;
+        uniform vec2 uAtlasInvSize;
+
+        layout (location = 0) in vec2 inPosition;
+        layout (location = 1) in vec4 inColor;
+        layout (location = 2) in uvec2 inTextureCoords;
+
+        out vec2 vTexCoord;
+        out vec4 vColor;
+        out vec2 vIntTexCoord;
+
+        void main() {
+            vec2 intCoords;
+            // floor(vec2) doesn't seem to work on some ES devices.
+            intCoords.x = floor(float(inTextureCoords.x));
+            intCoords.y = floor(float(inTextureCoords.y));
+            vTexCoord = intCoords * uAtlasInvSize;
+            vIntTexCoord = intCoords;
+            vColor = inColor;
+            gl_Position = vec4(inPosition.x * uDstScaleAndTranslate.x + uDstScaleAndTranslate.y,
+                               inPosition.y * uDstScaleAndTranslate.z + uDstScaleAndTranslate.w,
+                               0.0, 1.0);
+        }
+    )";
+    strings[1] = kVS;
+    lengths[1] = SK_ARRAY_COUNT(kVS) - 1;
+    callgl(ShaderSource, vs, 2, strings, lengths);
+    callgl(CompileShader, vs);
+    GrGLint compileStatus;
+    callgl(GetShaderiv, vs, GR_GL_COMPILE_STATUS, &compileStatus);
+    if (compileStatus == GR_GL_FALSE) {
+        GrGLint logLength;
+        callgl(GetShaderiv, vs, GR_GL_INFO_LOG_LENGTH, &logLength);
+        std::unique_ptr<GrGLchar[]> log(new GrGLchar[logLength + 1]);
+        log[logLength] = '\0';
+        callgl(GetShaderInfoLog, vs, logLength, &logLength, log.get());
+        SkDebugf("Vertex Shader failed to compile\n%s", log.get());
+        callgl(DeleteShader, vs);
+        return;
+    }
+
+    auto fs = callgl(CreateShader, GR_GL_FRAGMENT_SHADER);
+    static constexpr const char kFS[] = R"(
+        uniform sampler2D uSampler;
+
+        in vec2 vTexCoord;
+        in vec4 vColor;
+        in vec2 vIntTexCoord;
+
+        layout (location = 0) out vec4 outColor;
+
+        void main() {
+            float sdfValue = texture(uSampler, vTexCoord).r;
+            float distance = 7.96875 * (sdfValue - 0.50196078431000002);
+            vec2 dist_grad = vec2(dFdx(distance), dFdy(distance));
+            vec2 Jdx = dFdx(vIntTexCoord);
+            vec2 Jdy = dFdy(vIntTexCoord);
+            float dg_len2 = dot(dist_grad, dist_grad);
+            if (dg_len2 < 0.0001) {
+                dist_grad = vec2(0.7071, 0.7071);
+            } else {
+                dist_grad = dist_grad * inversesqrt(dg_len2);
+            }
+            vec2 grad = vec2(dist_grad.x * Jdx.x + dist_grad.y * Jdy.x,
+                             dist_grad.x * Jdx.y + dist_grad.y * Jdy.y);
+            float afwidth = abs(0.65000000000000002 * length(grad));
+            float value = smoothstep(-afwidth, afwidth, distance);
+            outColor = value * vec4(vColor.rgb * vColor.a, vColor.a);
+        }
+    )";
+    strings[1] = kFS;
+    lengths[1] = SK_ARRAY_COUNT(kFS) - 1;
+    callgl(ShaderSource, fs, 2, strings, lengths);
+    callgl(CompileShader, fs);
+    callgl(GetShaderiv, fs, GR_GL_COMPILE_STATUS, &compileStatus);
+    if (compileStatus == GR_GL_FALSE) {
+        GrGLint logLength;
+        callgl(GetShaderiv, fs, GR_GL_INFO_LOG_LENGTH, &logLength);
+        std::unique_ptr<GrGLchar[]> log(new GrGLchar[logLength + 1]);
+        log[logLength] = '\0';
+        callgl(GetShaderInfoLog, fs, logLength, &logLength, log.get());
+        SkDebugf("Fragment Shader failed to compile\n%s", log.get());
+        callgl(DeleteShader, vs);
+        callgl(DeleteShader, fs);
+        return;
+    }
+
+    fProgram = callgl(CreateProgram);
+    if (!fProgram) {
+        callgl(DeleteShader, vs);
+        callgl(DeleteShader, fs);
+        return;
+    }
+
+    callgl(AttachShader, fProgram, vs);
+    callgl(AttachShader, fProgram, fs);
+    callgl(LinkProgram, fProgram);
+    GrGLint linkStatus;
+    callgl(GetProgramiv, fProgram, GR_GL_LINK_STATUS, &linkStatus);
+    if (linkStatus == GR_GL_FALSE) {
+        GrGLint logLength = 0;
+        callgl(GetProgramiv, vs, GR_GL_INFO_LOG_LENGTH, &logLength);
+        std::unique_ptr<GrGLchar[]> log(new GrGLchar[logLength + 1]);
+        log[logLength] = '\0';
+        callgl(GetProgramInfoLog, vs, logLength, &logLength, log.get());
+        SkDebugf("Program failed to link\n%s", log.get());
+        callgl(DeleteShader, vs);
+        callgl(DeleteShader, fs);
+        callgl(DeleteProgram, fProgram);
+        fProgram = 0;
+        return;
+    }
+    fDstScaleAndTranslateLocation = callgl(GetUniformLocation, fProgram, "uDstScaleAndTranslate");
+    fAtlasInvSizeLocation = callgl(GetUniformLocation, fProgram, "uAtlasInvSize");
+    fSamplerLocation = callgl(GetUniformLocation, fProgram, "uSampler");
+    if (fDstScaleAndTranslateLocation < 0 || fAtlasInvSizeLocation < 0 || fSamplerLocation < 0) {
+        callgl(DeleteShader, vs);
+        callgl(DeleteShader, fs);
+        callgl(DeleteProgram, fProgram);
+        fProgram = 0;
+    }
+
+    checkgl();
+}
+
+inline bool atlas_format_to_gl_types(SkAtlasTextRenderer::AtlasFormat format,
+                                     GrGLenum* internalFormat, GrGLenum* externalFormat,
+                                     GrGLenum* type) {
+    switch (format) {
+        case SkAtlasTextRenderer::AtlasFormat::kA8:
+            *internalFormat = GR_GL_R8;
+            *externalFormat = GR_GL_RED;
+            *type = GR_GL_UNSIGNED_BYTE;
+            return true;
+    }
+    return false;
+}
+
+inline int atlas_format_bytes_per_pixel(SkAtlasTextRenderer::AtlasFormat format) {
+    switch (format) {
+        case SkAtlasTextRenderer::AtlasFormat::kA8:
+            return 1;
+    }
+    return 0;
+}
+
+void* GLTestAtlasTextRenderer::createTexture(AtlasFormat format, int width, int height) {
+    GrGLenum internalFormat;
+    GrGLenum externalFormat;
+    GrGLenum type;
+    if (!atlas_format_to_gl_types(format, &internalFormat, &externalFormat, &type)) {
+        return nullptr;
+    }
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    GrGLuint id;
+    callgl(GenTextures, 1, &id);
+    if (!id) {
+        return nullptr;
+    }
+
+    callgl(BindTexture, GR_GL_TEXTURE_2D, id);
+    callgl(TexImage2D, GR_GL_TEXTURE_2D, 0, internalFormat, width, height, 0, externalFormat, type,
+           nullptr);
+    checkgl();
+
+    AtlasTexture* atlas = new AtlasTexture;
+    atlas->fID = id;
+    atlas->fFormat = format;
+    atlas->fWidth = width;
+    atlas->fHeight = height;
+    return atlas;
+}
+
+void GLTestAtlasTextRenderer::deleteTexture(void* textureHandle) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    auto* atlasTexture = reinterpret_cast<const AtlasTexture*>(textureHandle);
+
+    callgl(DeleteTextures, 1, &atlasTexture->fID);
+    checkgl();
+
+    delete atlasTexture;
+}
+
+void GLTestAtlasTextRenderer::setTextureData(void* textureHandle, const void* data, int x, int y,
+                                             int width, int height, size_t rowBytes) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    auto atlasTexture = reinterpret_cast<const AtlasTexture*>(textureHandle);
+
+    GrGLenum internalFormat;
+    GrGLenum externalFormat;
+    GrGLenum type;
+    if (!atlas_format_to_gl_types(atlasTexture->fFormat, &internalFormat, &externalFormat, &type)) {
+        return;
+    }
+    int bpp = atlas_format_bytes_per_pixel(atlasTexture->fFormat);
+    GrGLint rowLength = static_cast<GrGLint>(rowBytes / bpp);
+    if (static_cast<size_t>(rowLength * bpp) != rowBytes) {
+        return;
+    }
+    callgl(PixelStorei, GR_GL_UNPACK_ALIGNMENT, 1);
+    callgl(PixelStorei, GR_GL_UNPACK_ROW_LENGTH, rowLength);
+    callgl(BindTexture, GR_GL_TEXTURE_2D, atlasTexture->fID);
+    callgl(TexSubImage2D, GR_GL_TEXTURE_2D, 0, x, y, width, height, externalFormat, type, data);
+    checkgl();
+}
+
+void GLTestAtlasTextRenderer::drawSDFGlyphs(void* targetHandle, void* textureHandle,
+                                            const SDFVertex vertices[], int quadCnt) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    auto target = reinterpret_cast<const Target*>(targetHandle);
+    auto atlas = reinterpret_cast<const AtlasTexture*>(textureHandle);
+
+    callgl(UseProgram, fProgram);
+
+    callgl(ActiveTexture, GR_GL_TEXTURE0);
+    callgl(BindTexture, GR_GL_TEXTURE_2D, atlas->fID);
+    callgl(TexParameteri, GR_GL_TEXTURE_2D, GR_GL_TEXTURE_MAG_FILTER, GR_GL_LINEAR);
+    callgl(TexParameteri, GR_GL_TEXTURE_2D, GR_GL_TEXTURE_MIN_FILTER, GR_GL_LINEAR);
+
+    float uniformScaleAndTranslate[4] = {2.f / target->fWidth, -1.f, 2.f / target->fHeight, -1.f};
+    callgl(Uniform4fv, fDstScaleAndTranslateLocation, 1, uniformScaleAndTranslate);
+    callgl(Uniform2f, fAtlasInvSizeLocation, 1.f / atlas->fWidth, 1.f / atlas->fHeight);
+    callgl(Uniform1i, fSamplerLocation, 0);
+
+    callgl(BindFramebuffer, GR_GL_FRAMEBUFFER, target->fFBOID);
+    callgl(Viewport, 0, 0, target->fWidth, target->fHeight);
+
+    callgl(Enable, GR_GL_BLEND);
+    callgl(BlendFunc, GR_GL_ONE, GR_GL_ONE_MINUS_SRC_ALPHA);
+    callgl(Disable, GR_GL_DEPTH_TEST);
+
+    callgl(BindVertexArray, 0);
+    callgl(BindBuffer, GR_GL_ARRAY_BUFFER, 0);
+    callgl(BindBuffer, GR_GL_ELEMENT_ARRAY_BUFFER, 0);
+    callgl(VertexAttribPointer, 0, 2, GR_GL_FLOAT, GR_GL_FALSE, sizeof(SDFVertex), vertices);
+    size_t colorOffset = 2 * sizeof(float);
+    callgl(VertexAttribPointer, 1, 4, GR_GL_UNSIGNED_BYTE, GR_GL_TRUE, sizeof(SDFVertex),
+           reinterpret_cast<const char*>(vertices) + colorOffset);
+    size_t texOffset = colorOffset + sizeof(uint32_t);
+    callgl(VertexAttribIPointer, 2, 2, GR_GL_UNSIGNED_SHORT, sizeof(SDFVertex),
+           reinterpret_cast<const char*>(vertices) + texOffset);
+    callgl(EnableVertexAttribArray, 0);
+    callgl(EnableVertexAttribArray, 1);
+    callgl(EnableVertexAttribArray, 2);
+
+    std::unique_ptr<uint16_t[]> indices(new uint16_t[quadCnt * 6]);
+    for (int q = 0; q < quadCnt; ++q) {
+        indices[q * 6 + 0] = 0 + 4 * q;
+        indices[q * 6 + 1] = 1 + 4 * q;
+        indices[q * 6 + 2] = 2 + 4 * q;
+        indices[q * 6 + 3] = 2 + 4 * q;
+        indices[q * 6 + 4] = 1 + 4 * q;
+        indices[q * 6 + 5] = 3 + 4 * q;
+    }
+    callgl(DrawElements, GR_GL_TRIANGLES, 6 * quadCnt, GR_GL_UNSIGNED_SHORT, indices.get());
+    checkgl();
+}
+
+void* GLTestAtlasTextRenderer::makeTargetHandle(int width, int height) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    GrGLuint fbo;
+    callgl(GenFramebuffers, 1, &fbo);
+    if (!fbo) {
+        return nullptr;
+    }
+    GrGLuint rb;
+    callgl(GenRenderbuffers, 1, &rb);
+    if (!rb) {
+        callgl(DeleteFramebuffers, 1, &fbo);
+        return nullptr;
+    }
+    callgl(BindFramebuffer, GR_GL_FRAMEBUFFER, fbo);
+    callgl(BindRenderbuffer, GR_GL_RENDERBUFFER, rb);
+    callgl(RenderbufferStorage, GR_GL_RENDERBUFFER, GR_GL_RGBA8, width, height);
+    callgl(FramebufferRenderbuffer, GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0, GR_GL_RENDERBUFFER,
+           rb);
+    GrGLenum status = callgl(CheckFramebufferStatus, GR_GL_FRAMEBUFFER);
+    if (GR_GL_FRAMEBUFFER_COMPLETE != status) {
+        callgl(DeleteFramebuffers, 1, &fbo);
+        callgl(DeleteRenderbuffers, 1, &rb);
+        return nullptr;
+    }
+    callgl(Disable, GR_GL_SCISSOR_TEST);
+    callgl(ClearColor, 0.5, 0.5, 0.5, 1.0);
+    callgl(Clear, GR_GL_COLOR_BUFFER_BIT);
+    checkgl();
+    Target* target = new Target;
+    target->fFBOID = fbo;
+    target->fRBID = rb;
+    target->fWidth = width;
+    target->fHeight = height;
+    return target;
+}
+
+void GLTestAtlasTextRenderer::targetDeleted(void* target) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    Target* t = reinterpret_cast<Target*>(target);
+    callgl(DeleteFramebuffers, 1, &t->fFBOID);
+    callgl(DeleteRenderbuffers, 1, &t->fRBID);
+    delete t;
+}
+
+SkBitmap GLTestAtlasTextRenderer::readTargetHandle(void* target) {
+    auto restore = fContext->makeCurrentAndAutoRestore();
+
+    Target* t = reinterpret_cast<Target*>(target);
+
+    auto info =
+            SkImageInfo::Make(t->fWidth, t->fHeight, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    SkBitmap bmp;
+    bmp.setInfo(info, sizeof(uint32_t) * t->fWidth);
+    bmp.allocPixels();
+
+    callgl(BindFramebuffer, GR_GL_FRAMEBUFFER, t->fFBOID);
+    callgl(ReadPixels, 0, 0, t->fWidth, t->fHeight, GR_GL_RGBA, GR_GL_UNSIGNED_BYTE,
+           bmp.getPixels());
+    checkgl();
+    return bmp;
+}
+
+}  // anonymous namespace
+
+namespace sk_gpu_test {
+
+sk_sp<TestAtlasTextRenderer> MakeGLTestAtlasTextRenderer() {
+    std::unique_ptr<GLTestContext> context(CreatePlatformGLTestContext(kGL_GrGLStandard));
+    if (!context) {
+        context.reset(CreatePlatformGLTestContext(kGLES_GrGLStandard));
+    }
+    if (!context) {
+        return nullptr;
+    }
+    auto restorer = context->makeCurrentAndAutoRestore();
+    auto renderer = sk_make_sp<GLTestAtlasTextRenderer>(std::move(context));
+    return renderer->initialized() ? std::move(renderer) : nullptr;
+}
+
+}  // namespace sk_gpu_test
diff --git a/tools/gpu/atlastext/GLTestAtlasTextRenderer.h b/tools/gpu/atlastext/GLTestAtlasTextRenderer.h
new file mode 100644
index 0000000..df01b34
--- /dev/null
+++ b/tools/gpu/atlastext/GLTestAtlasTextRenderer.h
@@ -0,0 +1,25 @@
+/*
+ * 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 GLTestAtlasTextRenderer_DEFINED
+#define GLTestAtlasTextRenderer_DEFINED
+
+#include "SkRefCnt.h"
+
+namespace sk_gpu_test {
+
+class TestAtlasTextRenderer;
+
+/**
+ * Creates a TestAtlasTextRenderer that uses its own OpenGL context to implement
+ * SkAtlasTextRenderer.
+ */
+sk_sp<TestAtlasTextRenderer> MakeGLTestAtlasTextRenderer();
+
+}  // namespace sk_gpu_test
+
+#endif
diff --git a/tools/gpu/atlastext/TestAtlasTextRenderer.h b/tools/gpu/atlastext/TestAtlasTextRenderer.h
new file mode 100644
index 0000000..068a60d
--- /dev/null
+++ b/tools/gpu/atlastext/TestAtlasTextRenderer.h
@@ -0,0 +1,34 @@
+/*
+ * 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 TestAtlasTextRenderer_DEFINED
+#define TestAtlasTextRenderer_DEFINED
+
+#include "SkAtlasTextRenderer.h"
+#include "SkBitmap.h"
+
+namespace sk_gpu_test {
+
+class TestContext;
+
+/**
+ * Base class for implementations of SkAtlasTextRenderer in order to test the SkAtlasText APIs.
+ * Adds a helper for creating SkAtlasTextTargets and to read back the contents of a target as a
+ * bitmap.
+ */
+class TestAtlasTextRenderer : public SkAtlasTextRenderer {
+public:
+    /** Returns a handle that can be used to construct a SkAtlasTextTarget instance. */
+    virtual void* makeTargetHandle(int width, int height) = 0;
+
+    /** Makes a SkBitmap of the target handle's contents. */
+    virtual SkBitmap readTargetHandle(void* targetHandle) = 0;
+};
+
+}  // namespace sk_gpu_test
+
+#endif
diff --git a/tools/gpu/gl/glx/CreatePlatformGLTestContext_glx.cpp b/tools/gpu/gl/glx/CreatePlatformGLTestContext_glx.cpp
index a6b2637..066784d 100644
--- a/tools/gpu/gl/glx/CreatePlatformGLTestContext_glx.cpp
+++ b/tools/gpu/gl/glx/CreatePlatformGLTestContext_glx.cpp
@@ -107,6 +107,11 @@
     , fDisplay(nullptr)
     , fPixmap(0)
     , fGlxPixmap(0) {
+    // We cross our fingers that this is the first X call in the program and that if the application
+    // is actually threaded that this succeeds.
+    static SkOnce gOnce;
+    gOnce([] { XInitThreads(); });
+
     fDisplay = get_display();
 
     GLXContext glxShareContext = shareContext ? shareContext->fContext : nullptr;