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
This commit is contained in:
parent
5a6c2d8208
commit
be19b9ef9a
3
Makefile
3
Makefile
@ -48,7 +48,8 @@ VALID_TARGETS := \
|
|||||||
SkiaAndroidApp \
|
SkiaAndroidApp \
|
||||||
skia_lib \
|
skia_lib \
|
||||||
tests \
|
tests \
|
||||||
tools
|
tools \
|
||||||
|
skpdiff
|
||||||
|
|
||||||
# Default target. This must be listed before all other targets.
|
# Default target. This must be listed before all other targets.
|
||||||
.PHONY: default
|
.PHONY: default
|
||||||
|
210
experimental/skpdiff/SkCLImageDiffer.cpp
Normal file
210
experimental/skpdiff/SkCLImageDiffer.cpp
Normal file
@ -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;
|
||||||
|
}
|
117
experimental/skpdiff/SkCLImageDiffer.h
Normal file
117
experimental/skpdiff/SkCLImageDiffer.h
Normal file
@ -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
|
39
experimental/skpdiff/SkImageDiffer.cpp
Normal file
39
experimental/skpdiff/SkImageDiffer.cpp
Normal file
@ -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);
|
||||||
|
}
|
70
experimental/skpdiff/SkImageDiffer.h
Normal file
70
experimental/skpdiff/SkImageDiffer.h
Normal file
@ -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
|
26
experimental/skpdiff/diff_pixels.cl
Normal file
26
experimental/skpdiff/diff_pixels.cl
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
127
experimental/skpdiff/main.cpp
Normal file
127
experimental/skpdiff/main.cpp
Normal file
@ -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;
|
||||||
|
}
|
35
experimental/skpdiff/skpdiff.gyp
Normal file
35
experimental/skpdiff/skpdiff.gyp
Normal file
@ -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:
|
94
experimental/skpdiff/skpdiff_util.cpp
Normal file
94
experimental/skpdiff/skpdiff_util.cpp
Normal file
@ -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;
|
||||||
|
}
|
38
experimental/skpdiff/skpdiff_util.h
Normal file
38
experimental/skpdiff/skpdiff_util.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user