experiment/fiddle

Review URL: https://codereview.chromium.org/1349163003
diff --git a/experimental/fiddle/.gitignore b/experimental/fiddle/.gitignore
new file mode 100644
index 0000000..dd6cc4f
--- /dev/null
+++ b/experimental/fiddle/.gitignore
@@ -0,0 +1,3 @@
+*.gch
+*.o
+fiddler
\ No newline at end of file
diff --git a/experimental/fiddle/draw.cpp b/experimental/fiddle/draw.cpp
new file mode 100644
index 0000000..dfed793
--- /dev/null
+++ b/experimental/fiddle/draw.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// This is an example of the translation unit that needs to be
+// assembled by the fiddler program to compile into a fiddle: an
+// implementation of the GetDrawOptions() and draw() functions.
+
+#include "fiddle_main.h"
+DrawOptions GetDrawOptions() {
+    // path *should* be absolute.
+    static const char path[] = "../../resources/color_wheel.png";
+    return DrawOptions(256, 256, true, true, true, true, path);
+}
+void draw(SkCanvas* canvas) {
+    canvas->clear(SK_ColorWHITE);
+    SkMatrix matrix;
+    matrix.setScale(0.75f, 0.75f);
+    matrix.preRotate(30.0f);
+    SkAutoTUnref<SkShader> shader(
+            image->newShader(SkShader::kRepeat_TileMode,
+                             SkShader::kRepeat_TileMode,
+                             &matrix));
+    SkPaint paint;
+    paint.setShader(shader);
+    canvas->drawPaint(paint);
+}
diff --git a/experimental/fiddle/fiddle_main.cpp b/experimental/fiddle/fiddle_main.cpp
new file mode 100644
index 0000000..eb41b51
--- /dev/null
+++ b/experimental/fiddle/fiddle_main.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdio.h>
+
+#include <GL/osmesa.h>
+
+#include "fiddle_main.h"
+
+// Globals externed in fiddle_main.h
+SkBitmap source;
+SkImage* image(nullptr);
+
+static void encode_to_base64(const void* data, size_t size, FILE* out) {
+    const uint8_t* input = reinterpret_cast<const uint8_t*>(data);
+    const uint8_t* end = &input[size];
+    static const char codes[] =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+            "abcdefghijklmnopqrstuvwxyz0123456789+/";
+    while (input != end) {
+        uint8_t b = (*input & 0xFC) >> 2;
+        fputc(codes[b], out);
+        b = (*input & 0x03) << 4;
+        ++input;
+        if (input == end) {
+            fputc(codes[b], out);
+            fputs("==", out);
+            return;
+        }
+        b |= (*input & 0xF0) >> 4;
+        fputc(codes[b], out);
+        b = (*input & 0x0F) << 2;
+        ++input;
+        if (input == end) {
+            fputc(codes[b], out);
+            fputc('=', out);
+            return;
+        }
+        b |= (*input & 0xC0) >> 6;
+        fputc(codes[b], out);
+        b = *input & 0x3F;
+        fputc(codes[b], out);
+        ++input;
+    }
+}
+
+static void dump_output(SkData* data, const char* name, bool last = true) {
+    if (data) {
+        printf("\t\"%s\": \"", name);
+        encode_to_base64(data->data(), data->size(), stdout);
+        fputs(last ? "\"\n" : "\",\n", stdout);
+    }
+}
+
+static SkData* encode_snapshot(SkSurface* surface) {
+    SkAutoTUnref<SkImage> img(surface->newImageSnapshot());
+    return img ? img->encode() : nullptr;
+}
+
+static OSMesaContext create_osmesa_context() {
+    OSMesaContext osMesaContext =
+        OSMesaCreateContextExt(OSMESA_BGRA, 0, 0, 0, nullptr);
+    if (osMesaContext != nullptr) {
+        static uint32_t buffer[16 * 16];
+        OSMesaMakeCurrent(osMesaContext, &buffer, GL_UNSIGNED_BYTE, 16, 16);
+    }
+    return osMesaContext;
+}
+
+static GrContext* create_mesa_grcontext() {
+    SkAutoTUnref<const GrGLInterface> mesa(GrGLCreateMesaInterface());
+    intptr_t backend = reinterpret_cast<intptr_t>(mesa.get());
+    return backend ? GrContext::Create(kOpenGL_GrBackend, backend) : nullptr;
+}
+
+
+int main() {
+    const DrawOptions options = GetDrawOptions();
+    fprintf(stderr, "%s\n", options.source);
+    if (options.source) {
+        SkAutoTUnref<SkData> data(SkData::NewFromFileName(options.source));
+        if (!data) {
+            perror(options.source);
+            return 1;
+        } else {
+            image = SkImage::NewFromEncoded(data);
+            if (!image) {
+                perror("Unable to decode the source image.");
+                return 1;
+            }
+            SkAssertResult(image->asLegacyBitmap(
+                                   &source, SkImage::kRO_LegacyBitmapMode));
+        }
+    }
+    SkAutoTUnref<SkData> rasterData, gpuData, pdfData, skpData;
+    if (options.raster) {
+        SkAutoTUnref<SkSurface> rasterSurface(
+                SkSurface::NewRaster(SkImageInfo::MakeN32Premul(options.size)));
+        draw(rasterSurface->getCanvas());
+        rasterData.reset(encode_snapshot(rasterSurface));
+    }
+    if (options.gpu) {
+        OSMesaContext osMesaContext = create_osmesa_context();
+        SkAutoTUnref<GrContext> grContext(create_mesa_grcontext());
+        if (!grContext) {
+            fputs("Unable to get Mesa GrContext.\n", stderr);
+        } else {
+            SkAutoTUnref<SkSurface> surface(
+                    SkSurface::NewRenderTarget(
+                            grContext,
+                            SkSurface::kNo_Budgeted,
+                            SkImageInfo::MakeN32Premul(options.size)));
+            if (!surface) {
+                fputs("Unable to get render surface.\n", stderr);
+                exit(1);
+            }
+            draw(surface->getCanvas());
+            gpuData.reset(encode_snapshot(surface));
+        }
+        if (osMesaContext) {
+            OSMesaDestroyContext(osMesaContext);
+        }
+    }
+    if (options.pdf) {
+        SkDynamicMemoryWStream pdfStream;
+        SkAutoTUnref<SkDocument> document(SkDocument::CreatePDF(&pdfStream));
+        draw(document->beginPage(options.size.width(), options.size.height()));
+        document->close();
+        pdfData.reset(pdfStream.copyToData());
+    }
+    if (options.skp) {
+        SkSize size;
+        size = options.size;
+        SkPictureRecorder recorder;
+        draw(recorder.beginRecording(size.width(), size.height()));
+        SkAutoTUnref<SkPicture> picture(recorder.endRecordingAsPicture());
+        SkDynamicMemoryWStream skpStream;
+        picture->serialize(&skpStream);
+        skpData.reset(skpStream.copyToData());
+    }
+
+    printf("{\n");
+    dump_output(rasterData, "Raster", !gpuData && !pdfData && !skpData);
+    dump_output(gpuData, "Gpu", !pdfData && !skpData);
+    dump_output(pdfData, "Pdf", !skpData);
+    dump_output(skpData, "Skp");
+    printf("}\n");
+
+    SkSafeSetNull(image);
+    return 0;
+}
diff --git a/experimental/fiddle/fiddle_main.h b/experimental/fiddle/fiddle_main.h
new file mode 100644
index 0000000..ab2aa8b
--- /dev/null
+++ b/experimental/fiddle/fiddle_main.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef fiddle_main_DEFINED
+#define fiddle_main_DEFINED
+
+#include "skia.h"
+
+extern SkBitmap source;
+extern SkImage* image;
+
+struct DrawOptions {
+    DrawOptions(int w, int h, bool r, bool g, bool p, bool k, const char* s)
+        : size(SkISize::Make(w, h))
+        , raster(r)
+        , gpu(g)
+        , pdf(p)
+        , skp(k)
+        , source(s) {}
+    SkISize size;
+    bool raster;
+    bool gpu;
+    bool pdf;
+    bool skp;
+    const char* source;
+};
+
+extern DrawOptions GetDrawOptions();
+extern void draw(SkCanvas*);
+
+#endif  // fiddle_main_DEFINED
diff --git a/experimental/fiddle/fiddle_test b/experimental/fiddle/fiddle_test
new file mode 100644
index 0000000..7955d68
--- /dev/null
+++ b/experimental/fiddle/fiddle_test
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Script for building Fiddle build bots.
+
+set -e
+set -x
+
+cd "$(dirname "$0")/../.."
+skia_dir="$PWD"
+cores=32
+
+echo "Bootstrapping CMake"
+cmake_dir="${skia_dir}/third_party/externals/cmake"
+cd "$cmake_dir"
+./bootstrap --parallel=$cores
+make -j $cores cmake
+
+echo "Building fiddle bootstrapped CMake"
+cd "${skia_dir}/experimental/fiddle"
+export PATH="${cmake_dir}/bin:${PATH}"
+go build fiddler.go
+./fiddler "$skia_dir"
+./fiddler "$skia_dir" draw.cpp > /dev/null
+
+echo "cleaning up"
+cd "$skia_dir"
+git clean -fxd cmake experimental/fiddle
diff --git a/experimental/fiddle/fiddler.go b/experimental/fiddle/fiddler.go
new file mode 100644
index 0000000..d3056c9
--- /dev/null
+++ b/experimental/fiddle/fiddler.go
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+package main
+
+// Example use:
+//   git clone https://skia.googlesource.com/skia.git
+//   cd skia
+//   SKIA=$PWD
+//   cd experimental/fiddle
+//   go get github.com/skia-dev/glog
+//   go get go.skia.org/infra/go/util
+//   go build fiddler.go
+//   # compile prerequisites
+//   ./fiddler "$SKIA"
+//   # compile and run a fiddle
+//   ./fiddler "$SKIA" draw.cpp | ./parse-fiddle-output
+//   # compile and run a different fiddle
+//   ./fiddler "$SKIA" ANOTHER_FIDDLE.cpp | ./parse-fiddle-output
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"syscall"
+
+	"github.com/skia-dev/glog"
+	"go.skia.org/infra/go/util"
+)
+
+func setResourceLimits() error {
+	const maximumTimeInSeconds = 5
+	limit := syscall.Rlimit{maximumTimeInSeconds, maximumTimeInSeconds}
+	if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &limit); err != nil {
+		return err
+	}
+	const maximumMemoryInBytes = 1 << 28
+	limit = syscall.Rlimit{maximumMemoryInBytes, maximumMemoryInBytes}
+	return syscall.Setrlimit(syscall.RLIMIT_AS, &limit)
+}
+
+// execCommand runs command and returns an error if it fails.  If there is no
+// error, all output is discarded.
+func execCommand(input io.Reader, dir string, name string, arg ...string) error {
+	var buffer bytes.Buffer
+	cmd := exec.Command(name, arg...)
+	cmd.Dir = dir
+	cmd.Stdout = &buffer
+	cmd.Stderr = &buffer
+	cmd.Stdin = input
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
+	}
+	return nil
+}
+
+func compileArgs(skiaSrc string) string {
+	return "@" + path.Join(skiaSrc, "cmake", "skia_compile_arguments.txt")
+}
+
+func linkArgs(skiaSrc string) string {
+	return "@" + path.Join(skiaSrc, "cmake", "skia_link_arguments.txt")
+}
+
+// fiddler compiles the input, links against skia, and runs the executable.
+// @param skiaSrc: the base directory of the Skia repository
+// @param inputReader: C++ fiddle source
+// @param output: stdout of executable sent here
+// @param tempDir: where to place the compiled executable
+func fiddler(skiaSrc string, inputReader io.Reader, output io.Writer, tempDir string) error {
+	binarypath := path.Join(tempDir, "fiddle")
+	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
+	if err := execCommand(inputReader, fiddle_dir,
+		"c++",
+		compileArgs(skiaSrc),
+		"-I", fiddle_dir,
+		"-o", binarypath,
+		"-x", "c++", "-", "-x", "none",
+		"fiddle_main.o",
+		"-lOSMesa",
+		linkArgs(skiaSrc),
+	); err != nil {
+		return err
+	}
+	var buffer bytes.Buffer
+	runCmd := exec.Cmd{Path: binarypath, Stdout: output, Stderr: &buffer}
+	if err := runCmd.Run(); err != nil {
+		return fmt.Errorf("execution failed:\n\n%s\n[%v]", buffer.String(), err)
+	}
+	return nil
+}
+
+// Compile Skia library and fiddle_main.cpp
+// @param skiaSrc: the base directory of the Skia repository.
+func fiddlerPrerequisites(skiaSrc string) error {
+	cmakeDir := path.Join(skiaSrc, "cmake")
+	if err := execCommand(nil, cmakeDir, "cmake", "-G", "Ninja", "."); err != nil {
+		return err
+	}
+	if err := execCommand(nil, cmakeDir, "ninja", "skia"); err != nil {
+		return err
+	}
+	fiddle_dir := path.Join(skiaSrc, "experimental", "fiddle")
+	if err := execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
+		"fiddle_main.h"); err != nil {
+		return err
+	}
+	return execCommand(nil, fiddle_dir, "c++", compileArgs(skiaSrc),
+		"-c", "-o", "fiddle_main.o", "fiddle_main.cpp")
+}
+
+func main() {
+	if len(os.Args) < 2 {
+		glog.Fatalf("usage: %s SKIA_SRC_PATH [PATH_TO_DRAW.CPP]", os.Args[0])
+	}
+	skiaSrc := os.Args[1]
+	if len(os.Args) < 3 {
+		// execCommand(nil, skiaSrc, "git", "fetch")
+		// execCommand(nil, skiaSrc, "git", "checkout", "origin/master")
+		if err := fiddlerPrerequisites(skiaSrc); err != nil {
+			glog.Fatal(err)
+		}
+	} else {
+		if err := setResourceLimits(); err != nil {
+			glog.Fatal(err)
+		}
+		tempDir, err := ioutil.TempDir("", "fiddle_")
+		if err != nil {
+			glog.Fatal(err)
+		}
+		defer func() {
+			err = os.RemoveAll(tempDir)
+			if err != nil {
+				glog.Fatalf("os.RemoveAll(tempDir) failed: %v", err)
+			}
+		}()
+		if os.Args[2] == "-" {
+			if err := fiddler(skiaSrc, os.Stdin, os.Stdout, tempDir); err != nil {
+				glog.Fatal(err)
+			}
+		} else {
+			inputFile, err := os.Open(os.Args[2])
+			if err != nil {
+				glog.Fatalf("unable to open \"%s\": %v", os.Args[2], err)
+			}
+			util.Close(inputFile)
+			if err = fiddler(skiaSrc, inputFile, os.Stdout, tempDir); err != nil {
+				glog.Fatal(err)
+			}
+		}
+	}
+}
diff --git a/experimental/fiddle/parse-fiddle-output b/experimental/fiddle/parse-fiddle-output
new file mode 100755
index 0000000..f9a2fac
--- /dev/null
+++ b/experimental/fiddle/parse-fiddle-output
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Parse the output of fiddle_main, for use in testing
+while IFS= read -r line; do
+    type=$(echo $line | sed -n 's/[^"]*"\([^"]*\)":.*/\1/p')
+    if [ "$type" ]; then
+        case "$type" in
+            Raster|Gpu) ext='.png';;
+            Pdf)        ext='.pdf';;
+            Skp)        ext='.skp';;
+        esac
+        dst="${TMPDIR:-/tmp}/fiddle_${type}${ext}"
+        echo $line | sed 's/[^"]*"[^"]*": "//; s/"\(,\|\)$//' \
+            | base64 -d > "$dst"
+        echo $dst
+    fi
+done