[canvaskit] POC bindings for testing gms
In this CL, I forked compile.sh and created a new gm_bindings.cpp. I also moved viewer.html into wasm_tools and created a gmtests.html for testing out the bindings locally. Right now there is only one gm file compiled in. I plan in a followup CL to have some way to generate the list of cpp files that need to be compiled in from gms.gni. I was unable to get it to work with simply linking the lib_gm.gni, probably due to the same issue with Registry that csmartdalton@ ran into when adding viewer.html and the associated bindings. Suggested reviewing order: - gmtests.html to get a sense of how the test flow works. - gm_bindings.cpp to make sure I setup the contexts/GMs correctly. - compile_gm.sh to see how the gms are compiled in. - The remaining files in any order. When I tested this locally, the bleed_downscale digest was exactly the same (pixel for pixel, byte for byte) as a known digest in Gold, so I'm fairly confident in how things work. Change-Id: I2babef848ca60f7db74e4adf27b8952a66bdeee1 Bug: skia:10812 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/322956 Reviewed-by: Chris Dalton <csmartdalton@google.com> Commit-Queue: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
7868692b9d
commit
0039874105
22
BUILD.gn
22
BUILD.gn
@ -1971,6 +1971,17 @@ if (skia_enable_tools) {
|
||||
"modules/skshaper",
|
||||
]
|
||||
}
|
||||
test_lib("hash_and_encode") {
|
||||
sources = [
|
||||
"tools/HashAndEncode.cpp",
|
||||
"tools/HashAndEncode.h",
|
||||
]
|
||||
deps = [
|
||||
":flags",
|
||||
":skia",
|
||||
"//third_party/libpng",
|
||||
]
|
||||
}
|
||||
if (target_cpu != "wasm") {
|
||||
test_app("convert-to-nia") {
|
||||
sources = [ "tools/convert-to-nia.cpp" ]
|
||||
@ -1983,17 +1994,6 @@ if (skia_enable_tools) {
|
||||
":skia",
|
||||
]
|
||||
}
|
||||
test_lib("hash_and_encode") {
|
||||
sources = [
|
||||
"tools/HashAndEncode.cpp",
|
||||
"tools/HashAndEncode.h",
|
||||
]
|
||||
deps = [
|
||||
":flags",
|
||||
":skia",
|
||||
"//third_party/libpng",
|
||||
]
|
||||
}
|
||||
test_app("fm") {
|
||||
sources = [
|
||||
"dm/DMGpuTestProcs.cpp", # blech
|
||||
|
@ -13,3 +13,12 @@ component("viewer_wasm") {
|
||||
]
|
||||
deps = [ "../..:samples" ]
|
||||
}
|
||||
|
||||
component("gm_wasm") {
|
||||
testonly = true
|
||||
include_dirs = [ "../.." ]
|
||||
deps = [
|
||||
"../..:hash_and_encode",
|
||||
"../..:tool_utils",
|
||||
]
|
||||
}
|
||||
|
@ -76,6 +76,12 @@ npm:
|
||||
cp ../../out/canvaskit_wasm/canvaskit.js ./canvaskit/bin/core
|
||||
cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin/core
|
||||
|
||||
gm_tests:
|
||||
./compile_gm.sh
|
||||
mkdir -p ./out
|
||||
cp ../../out/wasm_gm_tests/wasm_gm_tests.js ./out
|
||||
cp ../../out/wasm_gm_tests/wasm_gm_tests.wasm ./out
|
||||
|
||||
local-example:
|
||||
rm -rf node_modules/canvaskit
|
||||
mkdir -p node_modules
|
||||
|
188
modules/canvaskit/compile_gm.sh
Executable file
188
modules/canvaskit/compile_gm.sh
Executable file
@ -0,0 +1,188 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
set -ex
|
||||
|
||||
BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
|
||||
# This expects the environment variable EMSDK to be set
|
||||
if [[ ! -d $EMSDK ]]; then
|
||||
cat >&2 << "EOF"
|
||||
Be sure to set the EMSDK environment variable to the location of Emscripten SDK:
|
||||
|
||||
https://emscripten.org/docs/getting_started/downloads.html
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Navigate to SKIA_HOME from where this file is located.
|
||||
pushd $BASE_DIR/../..
|
||||
|
||||
source $EMSDK/emsdk_env.sh
|
||||
EMCC=`which emcc`
|
||||
EMCXX=`which em++`
|
||||
EMAR=`which emar`
|
||||
|
||||
RELEASE_CONF="-O3 -DSK_RELEASE --pre-js $BASE_DIR/release.js \
|
||||
-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0 -DGR_TEST_UTILS"
|
||||
EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\", \"-DGR_TEST_UTILS\", "
|
||||
IS_OFFICIAL_BUILD="false"
|
||||
|
||||
BUILD_DIR=${BUILD_DIR:="out/wasm_gm_tests"}
|
||||
|
||||
mkdir -p $BUILD_DIR
|
||||
# sometimes the .a files keep old symbols around - cleaning them out makes sure
|
||||
# we get a fresh build.
|
||||
rm -f $BUILD_DIR/*.a
|
||||
|
||||
GN_GPU="skia_enable_gpu=true skia_gl_standard = \"webgl\""
|
||||
GN_GPU_FLAGS="\"-DSK_DISABLE_LEGACY_SHADERCONTEXT\","
|
||||
WASM_GPU="-lGL -DSK_SUPPORT_GPU=1 -DSK_GL \
|
||||
-DSK_DISABLE_LEGACY_SHADERCONTEXT --pre-js $BASE_DIR/cpu.js --pre-js $BASE_DIR/gpu.js\
|
||||
-s USE_WEBGL2=1"
|
||||
|
||||
GM_LIB="$BUILD_DIR/libgm_wasm.a"
|
||||
|
||||
GN_FONT="skia_enable_fontmgr_custom_directory=false "
|
||||
BUILTIN_FONT="$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp"
|
||||
# Generate the font's binary file (which is covered by .gitignore)
|
||||
python tools/embed_resources.py \
|
||||
--name SK_EMBEDDED_FONTS \
|
||||
--input $BASE_DIR/fonts/NotoMono-Regular.ttf \
|
||||
--output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
|
||||
--align 4
|
||||
GN_FONT+="skia_enable_fontmgr_custom_embedded=true skia_enable_fontmgr_custom_empty=false"
|
||||
|
||||
|
||||
GN_SHAPER="skia_use_icu=true skia_use_system_icu=false skia_use_harfbuzz=true skia_use_system_harfbuzz=false"
|
||||
#SHAPER_LIB="$BUILD_DIR/libharfbuzz.a $BUILD_DIR/libicu.a"
|
||||
SHAPER_LIB=""
|
||||
|
||||
DO_DECODE="true"
|
||||
ENCODE_PNG="true"
|
||||
ENCODE_JPEG="false"
|
||||
ENCODE_WEBP="false"
|
||||
|
||||
# Turn off exiting while we check for ninja (which may not be on PATH)
|
||||
set +e
|
||||
NINJA=`which ninja`
|
||||
if [[ -z $NINJA ]]; then
|
||||
git clone "https://chromium.googlesource.com/chromium/tools/depot_tools.git" --depth 1 $BUILD_DIR/depot_tools
|
||||
NINJA=$BUILD_DIR/depot_tools/ninja
|
||||
fi
|
||||
# Re-enable error checking
|
||||
set -e
|
||||
|
||||
./bin/fetch-gn
|
||||
|
||||
echo "Compiling bitcode"
|
||||
|
||||
# With emsdk 2.0.0 we get a false positive on tautological-value-range-compare. This appears to be
|
||||
# fixed in the emsdk 2.0.4 toolchain. Disable the warning while we maintain support for 2.0.0.
|
||||
EXTRA_CFLAGS+="\"-Wno-tautological-value-range-compare\","
|
||||
|
||||
# Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
|
||||
./bin/gn gen ${BUILD_DIR} \
|
||||
--args="cc=\"${EMCC}\" \
|
||||
cxx=\"${EMCXX}\" \
|
||||
ar=\"${EMAR}\" \
|
||||
extra_cflags_cc=[\"-frtti\"] \
|
||||
extra_cflags=[\"-s\", \"WARN_UNALIGNED=1\", \"-s\", \"MAIN_MODULE=1\",
|
||||
\"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\",
|
||||
\"-DSK_FORCE_8_BYTE_ALIGNMENT\",
|
||||
${GN_GPU_FLAGS}
|
||||
${EXTRA_CFLAGS}
|
||||
] \
|
||||
is_debug=false \
|
||||
is_official_build=${IS_OFFICIAL_BUILD} \
|
||||
is_component_build=false \
|
||||
werror=true \
|
||||
target_cpu=\"wasm\" \
|
||||
\
|
||||
skia_use_angle=false \
|
||||
skia_use_dng_sdk=false \
|
||||
skia_use_webgl=true \
|
||||
skia_use_fontconfig=false \
|
||||
skia_use_freetype=true \
|
||||
skia_use_libheif=false \
|
||||
skia_use_libjpeg_turbo_decode=${DO_DECODE} \
|
||||
skia_use_libjpeg_turbo_encode=${ENCODE_JPEG} \
|
||||
skia_use_libpng_decode=${DO_DECODE} \
|
||||
skia_use_libpng_encode=${ENCODE_PNG} \
|
||||
skia_use_libwebp_decode=${DO_DECODE} \
|
||||
skia_use_libwebp_encode=${ENCODE_WEBP} \
|
||||
skia_use_lua=false \
|
||||
skia_use_piex=false \
|
||||
skia_use_system_freetype2=false \
|
||||
skia_use_system_libjpeg_turbo=false \
|
||||
skia_use_system_libpng=false \
|
||||
skia_use_system_libwebp=false \
|
||||
skia_use_system_zlib=false\
|
||||
skia_use_vulkan=false \
|
||||
skia_use_wuffs=true \
|
||||
skia_use_zlib=true \
|
||||
\
|
||||
${GN_SHAPER} \
|
||||
${GN_GPU} \
|
||||
${GN_FONT} \
|
||||
skia_use_expat=false \
|
||||
skia_enable_ccpr=false \
|
||||
\
|
||||
skia_enable_skshaper=true \
|
||||
skia_enable_nvpr=false \
|
||||
skia_enable_skparagraph=true \
|
||||
skia_enable_pdf=false"
|
||||
|
||||
# Build all the libs we will need below
|
||||
parse_targets() {
|
||||
for LIBPATH in $@; do
|
||||
basename $LIBPATH
|
||||
done
|
||||
}
|
||||
${NINJA} -C ${BUILD_DIR} libskia.a libskshaper.a \
|
||||
$(parse_targets $SHAPER_LIB $GM_LIB)
|
||||
|
||||
echo "Generating final wasm"
|
||||
|
||||
# Disable '-s STRICT=1' outside of Linux until
|
||||
# https://github.com/emscripten-core/emscripten/issues/12118 is resovled.
|
||||
STRICTNESS="-s STRICT=1"
|
||||
if [[ `uname` != "Linux" ]]; then
|
||||
echo "Disabling '-s STRICT=1'. See: https://github.com/emscripten-core/emscripten/issues/12118"
|
||||
STRICTNESS=""
|
||||
fi
|
||||
|
||||
# Emscripten prefers that the .a files go last in order, otherwise, it
|
||||
# may drop symbols that it incorrectly thinks aren't used. One day,
|
||||
# Emscripten will use LLD, which may relax this requirement.
|
||||
EMCC_DEBUG=1 ${EMCXX} \
|
||||
$RELEASE_CONF \
|
||||
-I. \
|
||||
-DSK_DISABLE_AAA \
|
||||
-DSK_FORCE_8_BYTE_ALIGNMENT \
|
||||
$WASM_GPU \
|
||||
-std=c++17 \
|
||||
--bind \
|
||||
--no-entry \
|
||||
--pre-js $BASE_DIR/gm.js \
|
||||
$BASE_DIR/gm_bindings.cpp \
|
||||
gm/bleed.cpp \
|
||||
gm/gm.cpp \
|
||||
$GM_LIB \
|
||||
$BUILD_DIR/libskshaper.a \
|
||||
$SHAPER_LIB \
|
||||
$BUILD_DIR/libskia.a \
|
||||
$BUILTIN_FONT \
|
||||
-s LLD_REPORT_UNDEFINED \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s EXPORT_NAME="InitWasmGMTests" \
|
||||
-s FORCE_FILESYSTEM=0 \
|
||||
-s FILESYSTEM=0 \
|
||||
-s MODULARIZE=1 \
|
||||
-s NO_EXIT_RUNTIME=1 \
|
||||
-s INITIAL_MEMORY=128MB \
|
||||
-s WASM=1 \
|
||||
$STRICTNESS \
|
||||
-o $BUILD_DIR/wasm_gm_tests.js
|
37
modules/canvaskit/gm.js
Normal file
37
modules/canvaskit/gm.js
Normal file
@ -0,0 +1,37 @@
|
||||
// When this file is loaded in, the high level object is "Module";
|
||||
var WasmGMTests = Module;
|
||||
WasmGMTests.onRuntimeInitialized = function() {
|
||||
|
||||
WasmGMTests.GetWebGLContext = function(canvas, webGLVersion) {
|
||||
if (!canvas) {
|
||||
throw 'null canvas passed into makeWebGLContext';
|
||||
}
|
||||
if (webGLVersion !== 1 && webGLVersion !== 2 ) {
|
||||
throw 'invalid webGLVersion';
|
||||
}
|
||||
var contextAttributes = {
|
||||
'alpha': 1,
|
||||
'depth': 0, // can be 0 because off-screen.
|
||||
'stencil': 0, // can be 0 because off-screen.
|
||||
'antialias': 0,
|
||||
'premultipliedAlpha': 1,
|
||||
'preserveDrawingBuffer': 0,
|
||||
'preferLowPowerToHighPerformance': 0,
|
||||
'failIfMajorPerformanceCaveat': 0,
|
||||
'enableExtensionsByDefault': 1,
|
||||
'explicitSwapControl': 0,
|
||||
'renderViaOffscreenBackBuffer': 0,
|
||||
'majorVersion': webGLVersion,
|
||||
};
|
||||
|
||||
// Creates a WebGL context and sets it to be the current context.
|
||||
// These functions are defined in emscripten's library_webgl.js
|
||||
var handle = GL.createContext(canvas, contextAttributes);
|
||||
if (!handle) {
|
||||
return 0;
|
||||
}
|
||||
GL.makeContextCurrent(handle);
|
||||
return handle;
|
||||
};
|
||||
|
||||
}
|
173
modules/canvaskit/gm_bindings.cpp
Normal file
173
modules/canvaskit/gm_bindings.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 <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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
|
||||
// We do not need to include the keys because they are optional - they are not read by Gold.
|
||||
CommandLineFlags::StringArray empty;
|
||||
SkDynamicMemoryWStream stream;
|
||||
// TODO(kjlubick) make emission of PNGs optional and make it so we can check the hash against
|
||||
// the list of known digests to not emit it. This will hopefully speed tests up.
|
||||
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);
|
||||
result.set("hash", md5.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(GMs) {
|
||||
function("ListGMs", &ListGMs);
|
||||
function("MakeGrContext", &MakeGrContext);
|
||||
function("RunGM", &RunGM);
|
||||
|
||||
class_<GrDirectContext>("GrDirectContext")
|
||||
.smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
|
||||
}
|
113
modules/canvaskit/wasm_tools/gmtests.html
Normal file
113
modules/canvaskit/wasm_tools/gmtests.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<title>GMs and unit tests against WASM/WebGL</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#debug_canvas {
|
||||
/* Same checkboard pattern as is on debugger.skia.org, just a little darker. */
|
||||
background-position: 0 0, 10px 10px;
|
||||
background-size: 20px 20px;
|
||||
background-image: linear-gradient(45deg, #CCC 25%, transparent 25%, transparent 75%, #CCC 75%, #CCC 100%),
|
||||
linear-gradient(45deg, #CCC 25%, white 25%, white 75%, #CCC 75%, #CCC 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<canvas id=debug_canvas height=1000 width=1000></canvas>
|
||||
|
||||
<canvas id=gm_canvas></canvas>
|
||||
|
||||
<script type="text/javascript" src="/out/wasm_gm_tests.js"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
const loadTests = InitWasmGMTests({
|
||||
locateFile: (file) => '/out/'+file,
|
||||
});
|
||||
|
||||
Promise.all([loadTests]).then(([GM]) => {
|
||||
RunGMs(GM);
|
||||
});
|
||||
|
||||
function RunGMs(GM) {
|
||||
const canvas = document.getElementById('gm_canvas');
|
||||
const ctx = GM.GetWebGLContext(canvas, 2);
|
||||
const grcontext = GM.MakeGrContext(ctx);
|
||||
requestAnimationFrame(drawQueuedPNGs);
|
||||
|
||||
const names = GM.ListGMs();
|
||||
names.sort();
|
||||
for (const name of names) {
|
||||
const pngAndHash = GM.RunGM(grcontext, name);
|
||||
if (!pngAndHash) {
|
||||
continue;
|
||||
}
|
||||
drawDebugPNG(pngAndHash.png);
|
||||
// We need to know the digest of the image as well as which gm produced it.
|
||||
// As such, we include both parts in the name.
|
||||
outputPNG(pngAndHash.png, pngAndHash.hash + '_' + name + '.png');
|
||||
}
|
||||
|
||||
grcontext.delete();
|
||||
}
|
||||
|
||||
const msPerGM = 500;
|
||||
let timeSinceLastPNGSwapped = 0;
|
||||
const queuedDebugPNGs = [];
|
||||
|
||||
// This decodes the given PNG and queues it up to be drawn. Because decoding the image
|
||||
// (createImageBitmap) is asynchronous, we queue this in a list and have a drawing loop that
|
||||
// occasionally pulls the next image off the queue to be displayed to the human. That way we
|
||||
// have a minimum amount of time an image is seen so the human can casually inspect the outputs
|
||||
// as they are generated.
|
||||
function drawDebugPNG(pngBytes) {
|
||||
const blob = new Blob([pngBytes], {type: 'image/png'});
|
||||
createImageBitmap(blob).then((bitmap) => {
|
||||
queuedDebugPNGs.push(bitmap);
|
||||
});
|
||||
}
|
||||
|
||||
function drawQueuedPNGs() {
|
||||
requestAnimationFrame(drawQueuedPNGs);
|
||||
if (!queuedDebugPNGs.length) {
|
||||
return; // no new image to show
|
||||
}
|
||||
if ((Date.now() - timeSinceLastPNGSwapped) < msPerGM) {
|
||||
return; // not been displayed long enough.
|
||||
}
|
||||
// Draw the first image in the queue.
|
||||
const bitmap = queuedDebugPNGs.shift();
|
||||
|
||||
const debugCanvas = document.getElementById('debug_canvas');
|
||||
debugCanvas.width = bitmap.width;
|
||||
debugCanvas.height = bitmap.height;
|
||||
|
||||
const ctx = debugCanvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, 1000, 1000);
|
||||
ctx.drawImage(bitmap, 0, 0);
|
||||
timeSinceLastPNGSwapped = Date.now();
|
||||
}
|
||||
|
||||
// This triggers a download of the created PNG using the provided filename. For a production
|
||||
// testing environment, it will probably be good to swap this out with a webserver because it
|
||||
// might not be easy to determine where the download folder for a given browser is.
|
||||
function outputPNG(pngBytes, fileName) {
|
||||
// https://stackoverflow.com/a/32094834
|
||||
const blob = new Blob([pngBytes], {type: 'image/png'});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
// clean up after because FF might not download it synchronously
|
||||
setTimeout(function() {
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
</script>
|
@ -9,7 +9,7 @@
|
||||
|
||||
// HashAndEncode transforms any SkBitmap into a standard format, currently
|
||||
// 16-bit unpremul RGBA in the Rec. 2020 color space. This lets us compare
|
||||
// images from different backends or configurations, using writeForHash() for
|
||||
// images from different backends or configurations, using feedHash() for
|
||||
// direct content-based hashing, or encodePNG() for visual comparison.
|
||||
class HashAndEncode {
|
||||
public:
|
||||
|
Loading…
Reference in New Issue
Block a user