a25c0619b5
We just make a global set of known png digests and expect that the test harness will load this with the appropriate data. Bug: skia:10812 Change-Id: I8e1fc987d36cc57386167410514803f8c3b90a69 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/328379 Reviewed-by: Chris Dalton <csmartdalton@google.com>
186 lines
6.5 KiB
C++
186 lines
6.5 KiB
C++
/*
|
|
* Copyright 2020 Google LLC
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include <set>
|
|
#include <string>
|
|
#include <emscripten.h>
|
|
#include <emscripten/bind.h>
|
|
#include <emscripten/html5.h>
|
|
|
|
#include "gm/gm.h"
|
|
#include "include/core/SkBitmap.h"
|
|
#include "include/core/SkCanvas.h"
|
|
#include "include/core/SkImageInfo.h"
|
|
#include "include/core/SkStream.h"
|
|
#include "include/core/SkSurface.h"
|
|
#include "include/gpu/GrDirectContext.h"
|
|
#include "include/gpu/gl/GrGLInterface.h"
|
|
#include "include/gpu/gl/GrGLTypes.h"
|
|
#include "src/core/SkMD5.h"
|
|
#include "tools/HashAndEncode.h"
|
|
#include "tools/flags/CommandLineFlags.h"
|
|
|
|
#include "modules/canvaskit/WasmCommon.h"
|
|
|
|
using namespace emscripten;
|
|
|
|
/**
|
|
* Returns a JS array of strings containing the names of the registered GMs. GMs are only registered
|
|
* when their source is included in the "link" step, not if they are in something like libgm.a.
|
|
* The names are also logged to the console.
|
|
*/
|
|
static JSArray ListGMs() {
|
|
SkDebugf("Listing GMs\n");
|
|
JSArray gms = emscripten::val::array();
|
|
for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
|
|
std::unique_ptr<skiagm::GM> gm(fact());
|
|
SkDebugf("gm %s\n", gm->getName());
|
|
gms.call<void>("push", std::string(gm->getName()));
|
|
}
|
|
return gms;
|
|
}
|
|
|
|
static std::unique_ptr<skiagm::GM> getGMWithName(std::string name) {
|
|
for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
|
|
std::unique_ptr<skiagm::GM> gm(fact());
|
|
if (gm->getName() == name) {
|
|
return gm;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Sets the given WebGL context to be "current" and then creates a GrDirectContext from that
|
|
* context.
|
|
*/
|
|
static sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
|
|
{
|
|
EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
|
|
if (r < 0) {
|
|
printf("failed to make webgl context current %d\n", r);
|
|
return nullptr;
|
|
}
|
|
// setup GrDirectContext
|
|
auto interface = GrGLMakeNativeInterface();
|
|
// setup contexts
|
|
sk_sp<GrDirectContext> dContext(GrDirectContext::MakeGL(interface));
|
|
return dContext;
|
|
}
|
|
|
|
static std::set<std::string> gKnownDigests;
|
|
|
|
static void LoadKnownDigest(std::string md5) {
|
|
gKnownDigests.insert(md5);
|
|
}
|
|
|
|
/**
|
|
* Runs the given GM and returns a JS object. If the GM was successful, the object will have the
|
|
* following properties:
|
|
* "png" - a Uint8Array of the PNG data extracted from the surface.
|
|
* "hash" - a string which is the md5 hash of the pixel contents and the metadata.
|
|
*/
|
|
static JSObject RunGM(sk_sp<GrDirectContext> ctx, std::string name) {
|
|
JSObject result = emscripten::val::object();
|
|
auto gm = getGMWithName(name);
|
|
if (!gm) {
|
|
SkDebugf("Could not find gm with name %s\n", name.c_str());
|
|
return result;
|
|
}
|
|
// TODO(kjlubick) make these configurable somehow. This probably makes sense to do as function
|
|
// parameters.
|
|
auto alphaType = SkAlphaType::kPremul_SkAlphaType;
|
|
auto colorType = SkColorType::kN32_SkColorType;
|
|
SkISize size = gm->getISize();
|
|
SkImageInfo info = SkImageInfo::Make(size, colorType, alphaType);
|
|
sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx.get(),
|
|
SkBudgeted::kYes,
|
|
info, 0,
|
|
kBottomLeft_GrSurfaceOrigin,
|
|
nullptr, true));
|
|
if (!surface) {
|
|
SkDebugf("Could not make surface\n");
|
|
return result;
|
|
}
|
|
auto canvas = surface->getCanvas();
|
|
|
|
gm->onceBeforeDraw();
|
|
SkString msg;
|
|
// Based on GMSrc::draw from DM.
|
|
auto gpuSetupResult = gm->gpuSetup(ctx.get(), canvas, &msg);
|
|
if (gpuSetupResult == skiagm::DrawResult::kFail) {
|
|
SkDebugf("Error with gpu setup for gm %s: %s\n", name.c_str(), msg.c_str());
|
|
return result;
|
|
} else if (gpuSetupResult == skiagm::DrawResult::kSkip) {
|
|
return result;
|
|
}
|
|
|
|
auto drawResult = gm->draw(canvas, &msg);
|
|
if (drawResult == skiagm::DrawResult::kFail) {
|
|
SkDebugf("Error with gm %s: %s\n", name.c_str(), msg.c_str());
|
|
return result;
|
|
} else if (drawResult == skiagm::DrawResult::kSkip) {
|
|
return result;
|
|
}
|
|
surface->flushAndSubmit(true);
|
|
|
|
// Based on GPUSink::readBack
|
|
SkBitmap bitmap;
|
|
bitmap.allocPixels(info);
|
|
if (!canvas->readPixels(bitmap, 0, 0)) {
|
|
SkDebugf("Could not read pixels back\n");
|
|
return result;
|
|
}
|
|
|
|
// Now we need to encode to PNG and get the md5 hash of the pixels (and colorspace and stuff).
|
|
// This is based on Task::Run from DM.cpp
|
|
std::unique_ptr<HashAndEncode> hashAndEncode = std::make_unique<HashAndEncode>(bitmap);
|
|
SkString md5;
|
|
SkMD5 hash;
|
|
hashAndEncode->feedHash(&hash);
|
|
SkMD5::Digest digest = hash.finish();
|
|
for (int i = 0; i < 16; i++) {
|
|
md5.appendf("%02x", digest.data[i]);
|
|
}
|
|
|
|
auto ok = gKnownDigests.find(md5.c_str());
|
|
if (ok == gKnownDigests.end()) {
|
|
// We only need to decode the image if it is "interesting", that is, we have not written it
|
|
// before to disk and uploaded it to gold.
|
|
SkDynamicMemoryWStream stream;
|
|
// We do not need to include the keys because they are optional - they are not read by Gold.
|
|
CommandLineFlags::StringArray empty;
|
|
hashAndEncode->encodePNG(&stream, md5.c_str(), empty, empty);
|
|
|
|
auto data = stream.detachAsData();
|
|
|
|
// This is the cleanest way to create a new Uint8Array with a copy of the data that is not
|
|
// in the WASM heap. kjlubick tried returning a pointer inside an SkData, but that lead to
|
|
// some use after free issues. By making the copy using the JS transliteration, we don't
|
|
// risk the SkData object being cleaned up before we make the copy.
|
|
Uint8Array pngData = emscripten::val(
|
|
// https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-views
|
|
typed_memory_view(data->size(), data->bytes())
|
|
).call<Uint8Array>("slice"); // slice with no args makes a copy of the memory view.
|
|
|
|
result.set("png", pngData);
|
|
gKnownDigests.emplace(md5.c_str());
|
|
}
|
|
result.set("hash", md5.c_str());
|
|
return result;
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(GMs) {
|
|
function("ListGMs", &ListGMs);
|
|
function("LoadKnownDigest", &LoadKnownDigest);
|
|
function("MakeGrContext", &MakeGrContext);
|
|
function("RunGM", &RunGM);
|
|
|
|
class_<GrDirectContext>("GrDirectContext")
|
|
.smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
|
|
}
|