add skpdiff tool to compare bitmaps
- start framework for pluggable algorithms
- implement simple number of pixels different OpenCL algo
R=djsollen@google.com, bsalomon@google.com, jvanverth@google.com
Author: zachr@google.com
Review URL: https://chromiumcodereview.appspot.com/16284007
git-svn-id: http://skia.googlecode.com/svn/trunk@9616 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/Makefile b/Makefile
index 1a0e5a5..0bb3b70 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,8 @@
SkiaAndroidApp \
skia_lib \
tests \
- tools
+ tools \
+ skpdiff
# Default target. This must be listed before all other targets.
.PHONY: default
diff --git a/experimental/skpdiff/SkCLImageDiffer.cpp b/experimental/skpdiff/SkCLImageDiffer.cpp
new file mode 100644
index 0000000..4bbb18c
--- /dev/null
+++ b/experimental/skpdiff/SkCLImageDiffer.cpp
@@ -0,0 +1,210 @@
+
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <cstring>
+
+#include "SkBitmap.h"
+#include "SkStream.h"
+
+#include "SkCLImageDiffer.h"
+#include "skpdiff_util.h"
+
+SkCLImageDiffer::SkCLImageDiffer() {
+ fIsGood = false;
+}
+
+
+bool SkCLImageDiffer::init(cl_device_id device, cl_context context) {
+ fContext = context;
+ fDevice = device;
+
+ cl_int queueErr;
+ fCommandQueue = clCreateCommandQueue(fContext, fDevice, 0, &queueErr);
+ if (CL_SUCCESS != queueErr) {
+ SkDebugf("Command queue creation failed: %s\n", cl_error_to_string(queueErr));
+ fIsGood = false;
+ return false;
+ }
+
+ fIsGood = this->onInit();
+ return fIsGood;
+}
+
+bool SkCLImageDiffer::loadKernelFile(const char file[], const char name[], cl_kernel* kernel) {
+ // Open the kernel source file
+ SkFILEStream sourceStream(file);
+ if (!sourceStream.isValid()) {
+ SkDebugf("Failed to open kernel source file");
+ return false;
+ }
+
+ return loadKernelStream(&sourceStream, name, kernel);
+}
+
+bool SkCLImageDiffer::loadKernelStream(SkStream* stream, const char name[], cl_kernel* kernel) {
+ // Read the kernel source into memory
+ SkString sourceString;
+ sourceString.resize(stream->getLength());
+ size_t bytesRead = stream->read(sourceString.writable_str(), sourceString.size());
+ if (bytesRead != sourceString.size()) {
+ SkDebugf("Failed to read kernel source file");
+ return false;
+ }
+
+ return loadKernelSource(sourceString.c_str(), name, kernel);
+}
+
+bool SkCLImageDiffer::loadKernelSource(const char source[], const char name[], cl_kernel* kernel) {
+ // Build the kernel source
+ size_t sourceLen = strlen(source);
+ cl_program program = clCreateProgramWithSource(fContext, 1, &source, &sourceLen, NULL);
+ cl_int programErr = clBuildProgram(program, 1, &fDevice, "", NULL, NULL);
+ if (CL_SUCCESS != programErr) {
+ SkDebugf("Program creation failed: %s\n", cl_error_to_string(programErr));
+
+ // Attempt to get information about why the build failed
+ char buildLog[4096];
+ clGetProgramBuildInfo(program, fDevice, CL_PROGRAM_BUILD_LOG, sizeof(buildLog), buildLog, NULL);
+ SkDebugf("Build log: %s\n", buildLog);
+
+ return false;
+ }
+
+ cl_int kernelErr;
+ *kernel = clCreateKernel(program, name, &kernelErr);
+ if (CL_SUCCESS != kernelErr) {
+ SkDebugf("Kernel creation failed: %s\n", cl_error_to_string(kernelErr));
+ return false;
+ }
+
+ return true;
+}
+
+bool SkCLImageDiffer::makeImage2D(SkBitmap* bitmap, cl_mem* image) {
+ cl_int imageErr;
+ cl_image_format bitmapFormat;
+ switch (bitmap->config()) {
+ case SkBitmap::kA8_Config:
+ bitmapFormat.image_channel_order = CL_A;
+ bitmapFormat.image_channel_data_type = CL_UNSIGNED_INT8;
+ break;
+ case SkBitmap::kRGB_565_Config:
+ bitmapFormat.image_channel_order = CL_RGB;
+ bitmapFormat.image_channel_data_type = CL_UNORM_SHORT_565;
+ break;
+ case SkBitmap::kARGB_8888_Config:
+ bitmapFormat.image_channel_order = CL_RGBA;
+ bitmapFormat.image_channel_data_type = CL_UNSIGNED_INT8;
+ break;
+ default:
+ SkDebugf("Image format is unsupported\n");
+ return false;
+ }
+
+ // Upload the bitmap data to OpenCL
+ bitmap->lockPixels();
+ *image = clCreateImage2D(fContext, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
+ &bitmapFormat, bitmap->width(), bitmap->height(),
+ bitmap->rowBytes(), bitmap->getPixels(),
+ &imageErr);
+ bitmap->unlockPixels();
+
+ if (CL_SUCCESS != imageErr) {
+ SkDebugf("Input image creation failed: %s\n", cl_error_to_string(imageErr));
+ return false;
+ }
+
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////
+
+const char* SkDifferentPixelsImageDiffer::getName() {
+ return "Find Different Pixels";
+}
+
+int SkDifferentPixelsImageDiffer::queueDiff(SkBitmap * baseline, SkBitmap * test) {
+ int diffID = fQueuedDiffs.count();
+ double startTime = get_seconds();
+ QueuedDiff* diff = fQueuedDiffs.push();
+
+ // Ensure the images are comparable
+ if (baseline->width() != test->width() || baseline->height() != test->height() ||
+ baseline->width() <= 0 || baseline->height() <= 0) {
+ diff->finished = true;
+ diff->result = 0.0;
+ return diffID;
+ }
+
+ // Upload images to the CL device
+ if (!this->makeImage2D(baseline, &diff->baseline) || !this->makeImage2D(test, &diff->test)) {
+ diff->finished = true;
+ diff->result = 0.0;
+ fIsGood = false;
+ return -1;
+ }
+
+ // A small hack that makes calculating percentage difference easier later on.
+ diff->result = 1.0 / ((double)baseline->width() * baseline->height());
+
+ // Make a buffer to store results into
+ int numDiffPixels = 0;
+ diff->resultsBuffer = clCreateBuffer(fContext, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
+ sizeof(int), &numDiffPixels, NULL);
+
+ // Set all kernel arguments
+ cl_int setArgErr = clSetKernelArg(fKernel, 0, sizeof(cl_mem), &diff->baseline);
+ setArgErr |= clSetKernelArg(fKernel, 1, sizeof(cl_mem), &diff->test);
+ setArgErr |= clSetKernelArg(fKernel, 2, sizeof(cl_mem), &diff->resultsBuffer);
+ if (CL_SUCCESS != setArgErr) {
+ SkDebugf("Set arg failed: %s\n", cl_error_to_string(setArgErr));
+ fIsGood = false;
+ return -1;
+ }
+
+ // Queue this diff on the CL device
+ cl_event event;
+ const size_t workSize[] = { baseline->width(), baseline->height() };
+ cl_int enqueueErr;
+ enqueueErr = clEnqueueNDRangeKernel(fCommandQueue, fKernel, 2, NULL, workSize, NULL, 0, NULL, &event);
+ if (CL_SUCCESS != enqueueErr) {
+ SkDebugf("Enqueue failed: %s\n", cl_error_to_string(enqueueErr));
+ fIsGood = false;
+ return -1;
+ }
+
+ // This makes things totally synchronous. Actual queue is not ready yet
+ clWaitForEvents(1, &event);
+ diff->finished = true;
+
+ // Immediate read back the results
+ clEnqueueReadBuffer(fCommandQueue, diff->resultsBuffer, CL_TRUE, 0, sizeof(int), &numDiffPixels, 0, NULL, NULL);
+ diff->result *= (double)numDiffPixels;
+ diff->result = (1.0 - diff->result);
+ SkDebugf("Time: %f\n", (get_seconds() - startTime));
+
+ return diffID;
+}
+
+bool SkDifferentPixelsImageDiffer::isFinished(int id) {
+ return fQueuedDiffs[id].finished;
+}
+
+double SkDifferentPixelsImageDiffer::getResult(int id) {
+ return fQueuedDiffs[id].result;
+}
+
+
+bool SkDifferentPixelsImageDiffer::onInit() {
+ if (!loadKernelFile("experimental/skpdiff/diff_pixels.cl", "diff", &fKernel)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/experimental/skpdiff/SkCLImageDiffer.h b/experimental/skpdiff/SkCLImageDiffer.h
new file mode 100644
index 0000000..565b371
--- /dev/null
+++ b/experimental/skpdiff/SkCLImageDiffer.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCLImageDiffer_DEFINED
+#define SkCLImageDiffer_DEFINED
+
+#include <CL/cl.h>
+#include "SkTDArray.h"
+
+#include "SkImageDiffer.h"
+
+class SkStream;
+
+/**
+ * An SkImageDiffer that requires initialization with an OpenCL device and context.
+ */
+class SkCLImageDiffer : public SkImageDiffer {
+public:
+ SkCLImageDiffer();
+
+ /**
+ * Initializes the OpenCL resources this differ needs to work
+ * @param device An OpenCL device
+ * @param context An OpenCL context of the given device
+ * @return True on success, false otherwise
+ */
+ virtual bool init(cl_device_id device, cl_context context);
+
+protected:
+ /**
+ * Called by init after fDevice, fContext, and fCommandQueue are successfully initialized
+ * @return True on success, false otherwise
+ */
+ virtual bool onInit() = 0;
+
+ /**
+ * Loads an OpenCL kernel from the file with the given named entry point. This only works after
+ * init is called.
+ * @param file The file path of the kernel
+ * @param name The name of the entry point of the desired kernel in the file
+ * @param kernel A pointer to return the loaded kernel into
+ * @return True on success, false otherwise
+ */
+ bool loadKernelFile(const char file[], const char name[], cl_kernel* kernel);
+
+ /**
+ * Loads an OpenCL kernel from the stream with the given named entry point. This only works
+ * after init is called.
+ * @param stream The stream that contains the kernel
+ * @param name The name of the entry point of the desired kernel in the stream
+ * @param kernel A pointer to return the loaded kernel into
+ * @return True on success, false otherwise
+ */
+ bool loadKernelStream(SkStream* stream, const char name[], cl_kernel* kernel);
+
+ /**
+ * Loads an OpenCL kernel from the source string with the given named entry point. This only
+ * works after init is called.
+ * @param source The string that contains the kernel
+ * @param name The name of the entry point of the desired kernel in the source string
+ * @param kernel A pointer to return the loaded kernel into
+ * @return True on success, false otherwise
+ */
+ bool loadKernelSource(const char source[], const char name[], cl_kernel* kernel);
+
+ /**
+ * Loads a read only copy of the given bitmap into device memory and returns the block of
+ * memory. This only works after init is called.
+ * @param bitmap The bitmap to load into memory
+ * @param image A pointer to return the allocated image to
+ * @return True on success, false otherwise
+ */
+ bool makeImage2D(SkBitmap* bitmap, cl_mem* image);
+
+ cl_device_id fDevice;
+ cl_context fContext;
+ cl_command_queue fCommandQueue;
+
+private:
+
+ typedef SkImageDiffer INHERITED;
+};
+
+/**
+ * A OpenCL differ that measures the percentage of different corresponding pixels. If the two images
+ * are not the same size or have no pixels, the result will always be zero.
+ */
+class SkDifferentPixelsImageDiffer : public SkCLImageDiffer {
+public:
+ virtual const char* getName() SK_OVERRIDE;
+ virtual int queueDiff(SkBitmap* baseline, SkBitmap* test) SK_OVERRIDE;
+ virtual bool isFinished(int id) SK_OVERRIDE;
+ virtual double getResult(int id) SK_OVERRIDE;
+
+protected:
+ virtual bool onInit() SK_OVERRIDE;
+
+private:
+ struct QueuedDiff {
+ bool finished;
+ double result;
+ cl_mem baseline;
+ cl_mem test;
+ cl_mem resultsBuffer;
+ };
+
+ SkTDArray<QueuedDiff> fQueuedDiffs;
+ cl_kernel fKernel;
+
+ typedef SkCLImageDiffer INHERITED;
+};
+
+#endif
diff --git a/experimental/skpdiff/SkImageDiffer.cpp b/experimental/skpdiff/SkImageDiffer.cpp
new file mode 100644
index 0000000..41be949
--- /dev/null
+++ b/experimental/skpdiff/SkImageDiffer.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <cstring>
+
+#include "SkBitmap.h"
+#include "SkImageDecoder.h"
+
+#include "SkImageDiffer.h"
+
+#include "skpdiff_util.h"
+
+
+SkImageDiffer::SkImageDiffer()
+ : fIsGood(true) {
+
+}
+
+SkImageDiffer::~SkImageDiffer() {
+
+}
+
+int SkImageDiffer::queueDiffOfFile(const char baseline[], const char test[]) {
+ SkBitmap baselineBitmap;
+ SkBitmap testBitmap;
+ if (!SkImageDecoder::DecodeFile(baseline, &baselineBitmap)) {
+ SkDebugf("Failed to load bitmap \"%s\"\n", baseline);
+ return -1;
+ }
+ if (!SkImageDecoder::DecodeFile(test, &testBitmap)) {
+ SkDebugf("Failed to load bitmap \"%s\"\n", test);
+ return -1;
+ }
+ return this->queueDiff(&baselineBitmap, &testBitmap);
+}
diff --git a/experimental/skpdiff/SkImageDiffer.h b/experimental/skpdiff/SkImageDiffer.h
new file mode 100644
index 0000000..86cf5bf
--- /dev/null
+++ b/experimental/skpdiff/SkImageDiffer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkImageDiffer_DEFINED
+#define SkImageDiffer_DEFINED
+
+class SkBitmap;
+
+/**
+ * Encapsulates an image difference metric algorithm that can be potentially run asynchronously.
+ */
+class SkImageDiffer {
+public:
+ SkImageDiffer();
+ virtual ~SkImageDiffer();
+
+ /**
+ * Gets a unique and descriptive name of this differ
+ * @return A statically allocated null terminated string that is the name of this differ
+ */
+ virtual const char* getName() = 0;
+
+ /**
+ * Gets if this differ is in a usable state
+ * @return True if this differ can be used, false otherwise
+ */
+ bool isGood() { return fIsGood; }
+
+ /**
+ * Wraps a call to queueDiff by loading the given filenames into SkBitmaps
+ * @param baseline The file path of the baseline image
+ * @param test The file path of the test image
+ * @return The results of queueDiff with the loaded bitmaps
+ */
+ int queueDiffOfFile(const char baseline[], const char test[]);
+
+ /**
+ * Queues a diff on a pair of bitmaps to be done at some future time.
+ * @param baseline The correct bitmap
+ * @param test The bitmap whose difference is being tested
+ * @return An non-negative diff ID on success, a negative integer on failure.
+ */
+ virtual int queueDiff(SkBitmap* baseline, SkBitmap* test) = 0;
+
+ /**
+ * Gets whether a queued diff of the given id has finished
+ * @param id The id of the queued diff to query
+ * @return True if the queued diff is finished and has results, false otherwise
+ */
+ virtual bool isFinished(int id) = 0;
+
+ /**
+ * Gets the results of the queued diff of the given id. The results are only meaningful after
+ * the queued diff has finished.
+ * @param id The id of the queued diff to query
+ * @return A score between of how different the compared images are, with lower numbers being
+ * more different.
+ */
+ virtual double getResult(int id) = 0;
+
+protected:
+ bool fIsGood;
+};
+
+
+#endif
diff --git a/experimental/skpdiff/diff_pixels.cl b/experimental/skpdiff/diff_pixels.cl
new file mode 100644
index 0000000..c1ecb52
--- /dev/null
+++ b/experimental/skpdiff/diff_pixels.cl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#pragma OPENCL_EXTENSION cl_khr_global_int32_base_atomics
+
+const sampler_t gInSampler = CLK_NORMALIZED_COORDS_FALSE |
+ CLK_ADDRESS_CLAMP_TO_EDGE |
+ CLK_FILTER_NEAREST;
+
+__kernel void diff(read_only image2d_t baseline, read_only image2d_t test, __global int* result) {
+ int2 coord = (int2)(get_global_id(0), get_global_id(1));
+ uint4 baselinePixel = read_imageui(baseline, gInSampler, coord);
+ uint4 testPixel = read_imageui(test, gInSampler, coord);
+ int4 pixelCompare = baselinePixel == testPixel;
+ if (baselinePixel.x != testPixel.x ||
+ baselinePixel.y != testPixel.y ||
+ baselinePixel.z != testPixel.z ||
+ baselinePixel.w != testPixel.w) {
+
+ atom_inc(result);
+ }
+}
\ No newline at end of file
diff --git a/experimental/skpdiff/main.cpp b/experimental/skpdiff/main.cpp
new file mode 100644
index 0000000..21bf716
--- /dev/null
+++ b/experimental/skpdiff/main.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#define __NO_STD_VECTOR // Uses cl::vectpr instead of std::vectpr
+#define __NO_STD_STRING // Uses cl::STRING_CLASS instead of std::string
+#include <CL/cl.hpp>
+
+#include "SkOSFile.h"
+#include "SkStream.h"
+#include "SkString.h"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+
+#include "SkImageDiffer.h"
+#include "SkCLImageDiffer.h"
+#include "skpdiff_util.h"
+
+/// A callback for any OpenCL errors
+CL_CALLBACK void error_notify(const char* errorInfo, const void* privateInfoSize, ::size_t cb, void* userData) {
+ SkDebugf("OpenCL error notify: %s\n", errorInfo);
+ exit(1);
+}
+
+/// Creates a device and context with OpenCL
+static bool init_device_and_context(cl::Device* device, cl::Context* context) {
+ // Query for a platform
+ cl::vector<cl::Platform> platformList;
+ cl::Platform::get(&platformList);
+ SkDebugf("The number of platforms is %u\n", platformList.size());
+
+ // Print some information about the platform for debugging
+ cl::Platform& platform = platformList[0];
+ cl::STRING_CLASS platformName;
+ platform.getInfo(CL_PLATFORM_NAME, &platformName);
+ SkDebugf("Platform index 0 is named %s\n", platformName.c_str());
+
+ // Query for a device
+ cl::vector<cl::Device> deviceList;
+ platform.getDevices(CL_DEVICE_TYPE_GPU, &deviceList);
+ SkDebugf("The number of GPU devices is %u\n", deviceList.size());
+
+ // Print some information about the device for debugging
+ *device = deviceList[0];
+ cl::STRING_CLASS deviceName;
+ device->getInfo(CL_DEVICE_NAME, &deviceName);
+ SkDebugf("Device index 0 is named %s\n", deviceName.c_str());
+
+ // Create a CL context and check for all errors
+ cl_int contextErr = CL_SUCCESS;
+ *context = cl::Context(deviceList, NULL, error_notify, NULL, &contextErr);
+ if (contextErr != CL_SUCCESS) {
+ SkDebugf("Context creation failed: %s\n", cl_error_to_string(contextErr));
+ return false;
+ }
+
+ return true;
+}
+
+/// Compares two directories of images with the given differ
+static void diff_directories(const char baselinePath[], const char testPath[], SkImageDiffer* differ) {
+ // Get the files in the baseline, we will then look for those inside the test path
+ SkTArray<SkString> baselineEntries;
+ if (!get_directory(baselinePath, &baselineEntries)) {
+ SkDebugf("Unable to open path \"%s\"\n", baselinePath);
+ return;
+ }
+
+ SkTDArray<int> queuedDiffIDs;
+ for (int baselineIndex = 0; baselineIndex < baselineEntries.count(); baselineIndex++) {
+ const char* baseFilename = baselineEntries[baselineIndex].c_str();
+ SkDebugf("%s\n", baseFilename);
+
+ // Find the real location of each file to compare
+ SkString baselineFile = SkOSPath::SkPathJoin(baselinePath, baseFilename);
+ SkString testFile = SkOSPath::SkPathJoin(testPath, baseFilename);
+
+ // Check that the test file exists and is a file
+ if (sk_exists(testFile.c_str()) && !sk_isdir(testFile.c_str())) {
+ // Queue up the comparison with the differ
+ int diffID = differ->queueDiffOfFile(baselineFile.c_str(), testFile.c_str());
+ if (diffID >= 0) {
+ queuedDiffIDs.push(diffID);
+ SkDebugf("Result: %f\n", differ->getResult(diffID));
+ }
+ } else {
+ SkDebugf("Baseline file \"%s\" has no corresponding test file\n", baselineFile.c_str());
+ }
+ }
+}
+
+static void print_help()
+{
+ SkDebugf(
+ "Usage:\n" \
+ "skpdiff <baseline directory> <test directory>\n\n"
+ );
+}
+
+int main(int argc, char** argv) {
+ if (argc != 3)
+ {
+ print_help();
+ return 1;
+ }
+
+ // Setup OpenCL
+ cl::Device device;
+ cl::Context context;
+ if (!init_device_and_context(&device, &context)) {
+ return 1;
+ }
+
+ // Setup our differ of choice
+ SkCLImageDiffer* differ = SkNEW(SkDifferentPixelsImageDiffer);
+ if (!differ->init(device(), context())) {
+ return 1;
+ }
+
+ // Diff our folders
+ diff_directories(argv[1], argv[2], differ);
+
+ return 0;
+}
diff --git a/experimental/skpdiff/skpdiff.gyp b/experimental/skpdiff/skpdiff.gyp
new file mode 100644
index 0000000..5b07a44
--- /dev/null
+++ b/experimental/skpdiff/skpdiff.gyp
@@ -0,0 +1,35 @@
+# GYP file to build skpdiff.
+#
+# To build on Linux:
+# ./gyp_skia skpdiff.gyp && make skpdiff
+#
+{
+ 'targets': [
+ {
+ 'target_name': 'skpdiff',
+ 'type': 'executable',
+ 'sources': [
+ 'main.cpp',
+ 'SkImageDiffer.cpp',
+ 'SkCLImageDiffer.cpp',
+ 'skpdiff_util.cpp',
+ ],
+ 'dependencies': [
+ '../../gyp/skia_lib.gyp:skia_lib',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lOpenCL',
+ ],
+ },
+ },
+ ],
+ 'conditions': [
+ ],
+}
+
+# Local Variables:
+# tab-width:2
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=2 shiftwidth=2:
diff --git a/experimental/skpdiff/skpdiff_util.cpp b/experimental/skpdiff/skpdiff_util.cpp
new file mode 100644
index 0000000..285e04c
--- /dev/null
+++ b/experimental/skpdiff/skpdiff_util.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <time.h>
+#include <dirent.h>
+#include "SkOSFile.h"
+#include "skpdiff_util.h"
+
+const char* cl_error_to_string(cl_int err) {
+ switch (err) {
+ case CL_SUCCESS: return "CL_SUCCESS";
+ case CL_DEVICE_NOT_FOUND: return "CL_DEVICE_NOT_FOUND";
+ case CL_DEVICE_NOT_AVAILABLE: return "CL_DEVICE_NOT_AVAILABLE";
+ case CL_COMPILER_NOT_AVAILABLE: return "CL_COMPILER_NOT_AVAILABLE";
+ case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
+ case CL_OUT_OF_RESOURCES: return "CL_OUT_OF_RESOURCES";
+ case CL_OUT_OF_HOST_MEMORY: return "CL_OUT_OF_HOST_MEMORY";
+ case CL_PROFILING_INFO_NOT_AVAILABLE: return "CL_PROFILING_INFO_NOT_AVAILABLE";
+ case CL_MEM_COPY_OVERLAP: return "CL_MEM_COPY_OVERLAP";
+ case CL_IMAGE_FORMAT_MISMATCH: return "CL_IMAGE_FORMAT_MISMATCH";
+ case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "CL_IMAGE_FORMAT_NOT_SUPPORTED";
+ case CL_BUILD_PROGRAM_FAILURE: return "CL_BUILD_PROGRAM_FAILURE";
+ case CL_MAP_FAILURE: return "CL_MAP_FAILURE";
+ case CL_INVALID_VALUE: return "CL_INVALID_VALUE";
+ case CL_INVALID_DEVICE_TYPE: return "CL_INVALID_DEVICE_TYPE";
+ case CL_INVALID_PLATFORM: return "CL_INVALID_PLATFORM";
+ case CL_INVALID_DEVICE: return "CL_INVALID_DEVICE";
+ case CL_INVALID_CONTEXT: return "CL_INVALID_CONTEXT";
+ case CL_INVALID_QUEUE_PROPERTIES: return "CL_INVALID_QUEUE_PROPERTIES";
+ case CL_INVALID_COMMAND_QUEUE: return "CL_INVALID_COMMAND_QUEUE";
+ case CL_INVALID_HOST_PTR: return "CL_INVALID_HOST_PTR";
+ case CL_INVALID_MEM_OBJECT: return "CL_INVALID_MEM_OBJECT";
+ case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
+ case CL_INVALID_IMAGE_SIZE: return "CL_INVALID_IMAGE_SIZE";
+ case CL_INVALID_SAMPLER: return "CL_INVALID_SAMPLER";
+ case CL_INVALID_BINARY: return "CL_INVALID_BINARY";
+ case CL_INVALID_BUILD_OPTIONS: return "CL_INVALID_BUILD_OPTIONS";
+ case CL_INVALID_PROGRAM: return "CL_INVALID_PROGRAM";
+ case CL_INVALID_PROGRAM_EXECUTABLE: return "CL_INVALID_PROGRAM_EXECUTABLE";
+ case CL_INVALID_KERNEL_NAME: return "CL_INVALID_KERNEL_NAME";
+ case CL_INVALID_KERNEL_DEFINITION: return "CL_INVALID_KERNEL_DEFINITION";
+ case CL_INVALID_KERNEL: return "CL_INVALID_KERNEL";
+ case CL_INVALID_ARG_INDEX: return "CL_INVALID_ARG_INDEX";
+ case CL_INVALID_ARG_VALUE: return "CL_INVALID_ARG_VALUE";
+ case CL_INVALID_ARG_SIZE: return "CL_INVALID_ARG_SIZE";
+ case CL_INVALID_KERNEL_ARGS: return "CL_INVALID_KERNEL_ARGS";
+ case CL_INVALID_WORK_DIMENSION: return "CL_INVALID_WORK_DIMENSION";
+ case CL_INVALID_WORK_GROUP_SIZE: return "CL_INVALID_WORK_GROUP_SIZE";
+ case CL_INVALID_WORK_ITEM_SIZE: return "CL_INVALID_WORK_ITEM_SIZE";
+ case CL_INVALID_GLOBAL_OFFSET: return "CL_INVALID_GLOBAL_OFFSET";
+ case CL_INVALID_EVENT_WAIT_LIST: return "CL_INVALID_EVENT_WAIT_LIST";
+ case CL_INVALID_EVENT: return "CL_INVALID_EVENT";
+ case CL_INVALID_OPERATION: return "CL_INVALID_OPERATION";
+ case CL_INVALID_GL_OBJECT: return "CL_INVALID_GL_OBJECT";
+ case CL_INVALID_BUFFER_SIZE: return "CL_INVALID_BUFFER_SIZE";
+ case CL_INVALID_MIP_LEVEL: return "CL_INVALID_MIP_LEVEL";
+ default: return "UNKNOWN";
+ }
+ return "UNKNOWN";
+}
+
+
+double get_seconds() {
+ struct timespec currentTime;
+ clock_gettime(CLOCK_REALTIME, ¤tTime);
+ return currentTime.tv_sec + (double)currentTime.tv_nsec / 1e9;
+}
+
+bool get_directory(const char path[], SkTArray<SkString>* entries) {
+ // Open the directory and check for success
+ DIR* dir = opendir(path);
+ if (NULL == dir) {
+ return false;
+ }
+
+ // Loop through dir entries until there are none left (i.e. readdir returns NULL)
+ struct dirent* entry;
+ while ((entry = readdir(dir))) {
+ // dirent only gives relative paths, we need to join them to the base path to check if they
+ // are directories.
+ SkString joinedPath = SkOSPath::SkPathJoin(path, entry->d_name);
+
+ // We only care about files
+ if (!sk_isdir(joinedPath.c_str())) {
+ entries->push_back(SkString(entry->d_name));
+ }
+ }
+
+ return true;
+}
diff --git a/experimental/skpdiff/skpdiff_util.h b/experimental/skpdiff/skpdiff_util.h
new file mode 100644
index 0000000..34fa8f3
--- /dev/null
+++ b/experimental/skpdiff/skpdiff_util.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skpdiff_util_DEFINED
+#define skpdiff_util_DEFINED
+
+#include <CL/cl.h>
+#include "SkString.h"
+#include "SkTArray.h"
+
+/**
+ * Converts an OpenCL error number into the string of its enumeration name.
+ * @param err The OpenCL error number
+ * @return The string of the name of the error; "UNKOWN" if the error number is invalid
+ */
+const char* cl_error_to_string(cl_int err);
+
+/**
+ * Get a positive monotonic real-time measure of the amount of seconds since some undefined epoch.
+ * Maximum precision is the goal of this routine.
+ * @return Amount of time in seconds since some epoch
+ */
+double get_seconds();
+
+/**
+ * Get file entries of the given directory.
+ * @param path A path to a directory to enumerate
+ * @param entries A vector to return the results into
+ * @return True on success, false otherwise
+ */
+bool get_directory(const char path[], SkTArray<SkString>* entries);
+
+
+#endif