diff options
author | Kevin Lubick <kjlubick@google.com> | 2018-10-16 10:15:01 -0400 |
---|---|---|
committer | Kevin Lubick <kjlubick@google.com> | 2018-10-16 14:32:28 +0000 |
commit | 3d99b1e347bef67f77c2d373f5d7824f23b0515c (patch) | |
tree | 84b926c0540830b9ffa3aa59cbfb51be128b9619 /experimental | |
parent | 1bfcbb57e6cbffed2bab932a77d5637dcbde7fa2 (diff) | |
download | skqp-3d99b1e347bef67f77c2d373f5d7824f23b0515c.tar.gz |
Add Correctness tests for CanvasKit
Also make a CPU only and GPU only build (although
the latter still has a lot of CPU logic).
Bug: skia:
Change-Id: I857c2300021c2adb5344865c28e4ad3e8d332954
Reviewed-on: https://skia-review.googlesource.com/c/162022
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Diffstat (limited to 'experimental')
-rw-r--r-- | experimental/canvaskit/Makefile | 25 | ||||
-rw-r--r-- | experimental/canvaskit/canvas-kit/.gitignore | 1 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit/.gitignore | 3 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit/cpu_example.html (renamed from experimental/canvaskit/canvas-kit/cpu_example.html) | 0 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit/example.html (renamed from experimental/canvaskit/canvas-kit/example.html) | 0 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit/package.json (renamed from experimental/canvaskit/canvas-kit/package.json) | 0 | ||||
-rw-r--r-- | experimental/canvaskit/canvaskit_bindings.cpp | 11 | ||||
-rwxr-xr-x | experimental/canvaskit/compile.sh | 15 | ||||
-rw-r--r-- | experimental/canvaskit/externs.js | 4 | ||||
-rw-r--r-- | experimental/canvaskit/interface.js | 88 | ||||
-rw-r--r-- | experimental/canvaskit/karma.conf.js | 72 | ||||
-rw-r--r-- | experimental/canvaskit/package.json | 15 | ||||
-rw-r--r-- | experimental/canvaskit/tests/path.spec.js | 166 |
13 files changed, 332 insertions, 68 deletions
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile index 65764fb8ef..f63395dd47 100644 --- a/experimental/canvaskit/Makefile +++ b/experimental/canvaskit/Makefile @@ -1,25 +1,30 @@ clean: rm -rf ../../out/canvaskit_wasm - rm -rf ./canvas-kit/bin + rm -rf ./canvaskit/bin $(MAKE) release release: # Does an incremental build where possible. ./compile.sh - mkdir -p ./canvas-kit/bin - cp ../../out/canvaskit_wasm/canvaskit.js ./canvas-kit/bin - cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin + mkdir -p ./canvaskit/bin + cp ../../out/canvaskit_wasm/canvaskit.js ./canvaskit/bin + cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin debug: # Does an incremental build where possible. ./compile.sh debug - mkdir -p ./canvas-kit/bin - cp ../../out/canvaskit_wasm/canvaskit.js ./canvas-kit/bin - cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin + mkdir -p ./canvaskit/bin + cp ../../out/canvaskit_wasm_debug/canvaskit.js ./canvaskit/bin + cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin local-example: - rm -rf node_modules/canvas-kit + rm -rf node_modules/canvaskit mkdir -p node_modules - ln -s -T ../canvas-kit node_modules/canvas-kit - echo "Go check out http://localhost:8000/canvas-kit/example.html" + ln -s -T ../canvaskit node_modules/canvaskit + echo "Go check out http://localhost:8000/canvaskit/example.html" python serve.py + +test-continuous: + echo "Assuming npm install has been run by user" + echo "Also assuming make debug or release has also been run by a user (if needed)" + npx karma start ./karma.conf.js --no-single-run --watch-poll
\ No newline at end of file diff --git a/experimental/canvaskit/canvas-kit/.gitignore b/experimental/canvaskit/canvas-kit/.gitignore deleted file mode 100644 index 6dd29b7f8d..0000000000 --- a/experimental/canvaskit/canvas-kit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin/
\ No newline at end of file diff --git a/experimental/canvaskit/canvaskit/.gitignore b/experimental/canvaskit/canvaskit/.gitignore new file mode 100644 index 0000000000..d3c34990ba --- /dev/null +++ b/experimental/canvaskit/canvaskit/.gitignore @@ -0,0 +1,3 @@ +bin/ +package-lock.json +node_modules/
\ No newline at end of file diff --git a/experimental/canvaskit/canvas-kit/cpu_example.html b/experimental/canvaskit/canvaskit/cpu_example.html index f4bf85bee3..f4bf85bee3 100644 --- a/experimental/canvaskit/canvas-kit/cpu_example.html +++ b/experimental/canvaskit/canvaskit/cpu_example.html diff --git a/experimental/canvaskit/canvas-kit/example.html b/experimental/canvaskit/canvaskit/example.html index 8b2fb51fad..8b2fb51fad 100644 --- a/experimental/canvaskit/canvas-kit/example.html +++ b/experimental/canvaskit/canvaskit/example.html diff --git a/experimental/canvaskit/canvas-kit/package.json b/experimental/canvaskit/canvaskit/package.json index 76dd8e9922..76dd8e9922 100644 --- a/experimental/canvaskit/canvas-kit/package.json +++ b/experimental/canvaskit/canvaskit/package.json diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp index 82e65c7bd6..fcf0042faa 100644 --- a/experimental/canvaskit/canvaskit_bindings.cpp +++ b/experimental/canvaskit/canvaskit_bindings.cpp @@ -13,7 +13,6 @@ #endif #include "SkCanvas.h" -#include "SkCanvas.h" #include "SkDashPathEffect.h" #include "SkCornerPathEffect.h" #include "SkDiscretePathEffect.h" @@ -211,10 +210,12 @@ EMSCRIPTEN_BINDINGS(Skia) { function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers()); function("currentContext", &emscripten_webgl_get_current_context); function("setCurrentContext", &emscripten_webgl_make_context_current); -#endif + constant("gpu", true); +#else function("_getRasterN32PremulSurface", optional_override([](int width, int height)->sk_sp<SkSurface> { return SkSurface::MakeRasterN32Premul(width, height, nullptr); }), allow_raw_pointers()); +#endif function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers()); function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers()); // Won't be called directly, there's a JS helper to deal with typed arrays. @@ -236,6 +237,9 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("drawPath", &SkCanvas::drawPath) .function("drawRect", &SkCanvas::drawRect) .function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) { + // TODO(kjlubick): This does not work well for non-ascii + // Need to maybe add a helper in interface.js that supports UTF-8 + // Otherwise, go with std::wstring and set UTF-32 encoding. self.drawText(text.c_str(), text.length(), x, y, p); })) .function("flush", &SkCanvas::flush) @@ -271,7 +275,6 @@ EMSCRIPTEN_BINDINGS(Skia) { class_<SkPathEffect>("SkPathEffect") .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>"); - //TODO make these chainable like PathKit class_<SkPath>("SkPath") .constructor<>() .constructor<const SkPath&>() @@ -297,6 +300,7 @@ EMSCRIPTEN_BINDINGS(Skia) { .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>") .function("width", &SkSurface::width) .function("height", &SkSurface::height) + .function("_flush", &SkSurface::flush) .function("makeImageSnapshot", &SkSurface::makeImageSnapshot) .function("_readPixels", optional_override([](SkSurface& self, int width, int height, uintptr_t /* uint8_t* */ cptr)->bool { auto* dst = reinterpret_cast<uint8_t*>(cptr); @@ -358,5 +362,6 @@ EMSCRIPTEN_BINDINGS(Skia) { }), allow_raw_pointers()); function("MakeAnimation", &MakeAnimation); + constant("skottie", true); #endif } diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh index 67be659dca..7cfdd042b4 100755 --- a/experimental/canvaskit/compile.sh +++ b/experimental/canvaskit/compile.sh @@ -13,8 +13,6 @@ if [[ ! -d $EMSDK ]]; then exit 1 fi -BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"} -mkdir -p $BUILD_DIR # Navigate to SKIA_HOME from where this file is located. pushd $BASE_DIR/../.. @@ -24,17 +22,24 @@ EMCXX=`which em++` RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE" EXTRA_CFLAGS="\"-DSK_RELEASE\"" + if [[ $@ == *debug* ]]; then echo "Building a Debug build" EXTRA_CFLAGS="\"-DSK_DEBUG\"" - RELEASE_CONF="-O0 --js-opts 0 -s SAFE_HEAP=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG" + RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s SAFE_HEAP=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG" + BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_debug"} +else + BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"} fi +mkdir -p $BUILD_DIR + GN_GPU="skia_enable_gpu=true" WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1" -if [[ $@ == *no_gpu* ]]; then - echo "Omitting the GPU backend" +if [[ $@ == *cpu* ]]; then + echo "Using the CPU backend instead of the GPU backend" GN_GPU="skia_enable_gpu=false" + GN_GPU_FLAGS="" WASM_GPU="-DSK_SUPPORT_GPU=0" fi diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js index c0f6c66444..801bad620e 100644 --- a/experimental/canvaskit/externs.js +++ b/experimental/canvaskit/externs.js @@ -31,6 +31,8 @@ var CanvasKit = { MakeSkDashPathEffect: function(intervals, phase) {}, setCurrentContext: function() {}, LTRBRect: function(l, t, r, b) {}, + gpu: {}, + skottie: {}, // private API (i.e. things declared in the bindings that we use // in the pre-js file) @@ -78,9 +80,11 @@ var CanvasKit = { SkSurface: { // public API should go below because closure still will // remove things declared here and not on the prototype. + flush: function() {}, // private API _readPixels: function(w, h, ptr) {}, + _flush: function() {}, } } diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js index 98be5ef1af..938ef3eda9 100644 --- a/experimental/canvaskit/interface.js +++ b/experimental/canvaskit/interface.js @@ -107,49 +107,55 @@ return this; }; - CanvasKit.SkSurface.prototype.flush = function() { - var success = this._readPixels(this._width, this._height, this._pixelPtr); - if (!success) { - console.err('could not read pixels'); - return; + if (CanvasKit.gpu) { + CanvasKit.getWebGLSurface = function(htmlID) { + var canvas = document.getElementById(htmlID); + if (!canvas) { + throw 'Canvas with id ' + htmlID + ' was not found'; + } + // Maybe better to use clientWidth/height. See: + // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html + return this._getWebGLSurface(htmlID, canvas.width, canvas.height); + }; + + CanvasKit.SkSurface.prototype.flush = function() { + this._flush(); } - - var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen); - var imageData = new ImageData(pixels, this._width, this._height); - - this.canvas.getContext('2d').putImageData(imageData, 0, 0); - - }; - } - - CanvasKit.getWebGLSurface = function(htmlID) { - var canvas = document.getElementById(htmlID); - if (!canvas) { - throw 'Canvas with id ' + htmlID + ' was not found'; + } else { + CanvasKit.getRasterN32PremulSurface = function(htmlID) { + var canvas = document.getElementById(htmlID); + if (!canvas) { + throw 'Canvas with id ' + htmlID + ' was not found'; + } + // Maybe better to use clientWidth/height. See: + // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html + var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height); + if (surface) { + surface.canvas = canvas; + surface._width = canvas.width; + surface._height = canvas.height; + surface._pixelLen = surface._width * surface._height * 4; // it's 8888 + // Allocate the buffer of pixels to be used to draw back and forth. + surface._pixelPtr = CanvasKit._malloc(surface._pixelLen); + } + return surface; + }; + + CanvasKit.SkSurface.prototype.flush = function() { + this._flush(); + var success = this._readPixels(this._width, this._height, this._pixelPtr); + if (!success) { + console.err('could not read pixels'); + return; + } + + var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen); + var imageData = new ImageData(pixels, this._width, this._height); + + this.canvas.getContext('2d').putImageData(imageData, 0, 0); + }; } - // Maybe better to use clientWidth/height. See: - // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html - return this._getWebGLSurface(htmlID, canvas.width, canvas.height); - } - - CanvasKit.getRasterN32PremulSurface = function(htmlID) { - var canvas = document.getElementById(htmlID); - if (!canvas) { - throw 'Canvas with id ' + htmlID + ' was not found'; - } - // Maybe better to use clientWidth/height. See: - // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html - var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height); - if (surface) { - surface.canvas = canvas; - surface._width = canvas.width; - surface._height = canvas.height; - surface._pixelLen = surface._width * surface._height * 4; // it's 8888 - // Allocate the buffer of pixels to be used to draw back and forth. - surface._pixelPtr = CanvasKit._malloc(surface._pixelLen); - } - return surface; - } + } // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic. // Likely only used for tests. CanvasKit.LTRBRect = function(l, t, r, b) { diff --git a/experimental/canvaskit/karma.conf.js b/experimental/canvaskit/karma.conf.js new file mode 100644 index 0000000000..31f5b463b7 --- /dev/null +++ b/experimental/canvaskit/karma.conf.js @@ -0,0 +1,72 @@ +const isDocker = require('is-docker')(); + +module.exports = function(config) { + // Set the default values to be what are needed when testing the + // WebAssembly build locally. + let cfg = { + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + // list of files / patterns to load in the browser + files: [ + { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true}, + '../../modules/pathkit/tests/testReporter.js', + 'canvaskit/bin/canvaskit.js', + 'tests/*.spec.js' + ], + + proxies: { + '/canvaskit/': '/base/canvaskit/bin/' + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + // web server port + port: 4444, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + browserDisconnectTimeout: 15000, + browserNoActivityTimeout: 15000, + + // start these browsers + browsers: ['Chrome'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + }; + + if (isDocker) { + // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3 + cfg.browsers = ['ChromeHeadlessNoSandbox'], + cfg.customLaunchers = { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: [ + // Without this flag, we see an error: + // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted + '--no-sandbox' + ], + }, + }; + } + + config.set(cfg); +} diff --git a/experimental/canvaskit/package.json b/experimental/canvaskit/package.json index 9986143406..3a0a8620f0 100644 --- a/experimental/canvaskit/package.json +++ b/experimental/canvaskit/package.json @@ -4,15 +4,14 @@ "description": "private", "private": true, "main": "index.js", - "dependencies": { - }, + "dependencies": {}, "devDependencies": { - "is-docker": "^1.1.0", - "jasmine-core": "^3.1.0", - "karma": "^2.0.5", - "karma-chrome-launcher": "^2.2.0", - "karma-jasmine": "^1.1.2", - "requirejs": "^2.3.5" + "is-docker": "~1.1.0", + "jasmine-core": "~3.1.0", + "karma": "~3.0.0", + "karma-chrome-launcher": "~2.2.0", + "karma-jasmine": "~1.1.2", + "requirejs": "~2.3.5" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/experimental/canvaskit/tests/path.spec.js b/experimental/canvaskit/tests/path.spec.js new file mode 100644 index 0000000000..0be3032d15 --- /dev/null +++ b/experimental/canvaskit/tests/path.spec.js @@ -0,0 +1,166 @@ + +describe('CanvasKit\'s Path Behavior', function() { + // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up. + var CanvasKit = null; + const LoadCanvasKit = new Promise(function(resolve, reject) { + if (CanvasKit) { + resolve(); + } else { + CanvasKitInit({ + locateFile: (file) => '/canvaskit/'+file, + }).then((_CanvasKit) => { + CanvasKit = _CanvasKit; + CanvasKit.initFonts(); + resolve(); + }); + } + }); + + let container = document.createElement('div'); + document.body.appendChild(container); + const CANVAS_WIDTH = 600; + const CANVAS_HEIGHT = 600; + + beforeEach(function() { + container.innerHTML = ` + <canvas width=600 height=600 id=test></canvas> + <canvas width=600 height=600 id=report></canvas>`; + }); + + afterEach(function() { + container.innerHTML = ''; + }); + + function getSurface() { + if (CanvasKit.gpu) { + return CanvasKit.getWebGLSurface('test'); + } + return CanvasKit.getRasterN32PremulSurface('test'); + } + + + function reportSurface(surface, testname, done) { + // In docker, the webgl canvas is blank, but the surface has the pixel + // data. So, we copy it out and draw it to a normal canvas to take a picture. + // To be consistent across CPU and GPU, we just do it for all configurations + // (even though the CPU canvas shows up after flush just fine). + let pixelLen = CANVAS_WIDTH * CANVAS_HEIGHT * 4; // 4 bytes for r,g,b,a + let pixelPtr = CanvasKit._malloc(pixelLen); + let success = surface._readPixels(CANVAS_WIDTH, CANVAS_HEIGHT, pixelPtr); + if (!success) { + done(); + expect(success).toBeFalsy('could not read pixels'); + return; + } + let pixels = new Uint8ClampedArray(CanvasKit.buffer, pixelPtr, pixelLen); + var imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT); + + let reportingCanvas = document.getElementById('report'); + reportingCanvas.getContext('2d').putImageData(imageData, 0, 0); + CanvasKit._free(pixelPtr); + reportCanvas(reportingCanvas, testname).then(() => { + done(); + }).catch(reportError(done)); + } + + it('can draw a path', function(done) { + LoadCanvasKit.then(() => { + // This is taken from example.html + const surface = getSurface(); + expect(surface).toBeTruthy('Could not make surface') + if (!surface) { + done(); + return; + } + const canvas = surface.getCanvas(); + const paint = new CanvasKit.SkPaint(); + paint.setStrokeWidth(1.0); + paint.setAntiAlias(true); + paint.setColor(CanvasKit.Color(0, 0, 0, 1.0)); + paint.setStyle(CanvasKit.PaintStyle.STROKE); + + const path = new CanvasKit.SkPath(); + path.moveTo(20, 5); + path.lineTo(30, 20); + path.lineTo(40, 10); + path.lineTo(50, 20); + path.lineTo(60, 0); + path.lineTo(20, 5); + + path.moveTo(20, 80); + path.cubicTo(90, 10, 160, 150, 190, 10); + + path.moveTo(36, 148); + path.quadTo(66, 188, 120, 136); + path.lineTo(36, 148); + + path.moveTo(150, 180); + path.arcTo(150, 100, 50, 200, 20); + path.lineTo(160, 160); + + path.moveTo(20, 120); + path.lineTo(20, 120); + + path.transform([2, 0, 0, + 0, 2, 0, + 0, 0, 1 ]) + + canvas.drawPath(path, paint); + surface.flush(); + + path.delete(); + paint.delete(); + + reportSurface(surface, 'path_api_example', done); + }); + // See CanvasKit for more tests, since they share implementation + }); + + function starPath(CanvasKit, X=128, Y=128, R=116) { + let p = new CanvasKit.SkPath(); + p.moveTo(X + R, Y); + for (let i = 1; i < 8; i++) { + let a = 2.6927937 * i; + p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a)); + } + return p; + } + + it('can apply an effect and draw text', function(done) { + LoadCanvasKit.then(() => { + const surface = getSurface(); + expect(surface).toBeTruthy('Could not make surface') + if (!surface) { + done(); + return; + } + const canvas = surface.getCanvas(); + const path = starPath(CanvasKit); + + const paint = new CanvasKit.SkPaint(); + + const textPaint = new CanvasKit.SkPaint(); + textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0)); + textPaint.setTextSize(30); + textPaint.setAntiAlias(true); + + const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1); + + paint.setPathEffect(dpe); + paint.setStyle(CanvasKit.PaintStyle.STROKE); + paint.setStrokeWidth(5.0); + paint.setAntiAlias(true); + paint.setColor(CanvasKit.Color(66, 129, 164, 1.0)); + + canvas.clear(CanvasKit.Color(255, 255, 255, 1.0)); + + canvas.drawPath(path, paint); + canvas.drawText('This is text', 10, 280, textPaint); + surface.flush(); + dpe.delete(); + path.delete(); + + reportSurface(surface, 'effect_and_text_example', done); + }); + }); +}); |