Add selectable GPU backend to wasm debugger
Bug: skia:
Change-Id: If34e08cb320a141cb838caafeffa4f1f69c5234f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/201404
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/experimental/wasm-skp-debugger/Makefile b/experimental/wasm-skp-debugger/Makefile
index 51c2d3c..33339c0 100644
--- a/experimental/wasm-skp-debugger/Makefile
+++ b/experimental/wasm-skp-debugger/Makefile
@@ -4,8 +4,6 @@
mkdir -p ./debugger/bin
cp ../../out/debugger_wasm/debugger.js ./debugger/bin
cp ../../out/debugger_wasm/debugger.wasm ./debugger/bin
- # only necessary while emscripten's --preload-file is being used for testing
- cp ../../out/debugger_wasm/debugger.data ./debugger/bin
local-example:
rm -rf node_modules/debugger
@@ -17,4 +15,4 @@
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
+ npx karma start ./karma.conf.js --no-single-run --watch-poll
diff --git a/experimental/wasm-skp-debugger/compile.sh b/experimental/wasm-skp-debugger/compile.sh
index ed9cdf1..801436d 100755
--- a/experimental/wasm-skp-debugger/compile.sh
+++ b/experimental/wasm-skp-debugger/compile.sh
@@ -31,6 +31,10 @@
--output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
--align 4
+GN_GPU_FLAGS="\"-DIS_WEBGL=1\", \"-DSK_DISABLE_LEGACY_SHADERCONTEXT\","
+WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1 \
+ -DSK_DISABLE_LEGACY_SHADERCONTEXT --pre-js $BASE_DIR/cpu.js --pre-js $BASE_DIR/gpu.js"
+
NINJA=`which ninja`
./bin/fetch-gn
@@ -42,7 +46,8 @@
cxx=\"${EMCXX}\" \
extra_cflags_cc=[\"-frtti\"] \
extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-s\", \"WARN_UNALIGNED=1\",
- \"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_DAA\"
+ \"-DSKNX_NO_SIMD\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_DAA\",
+ ${GN_GPU_FLAGS}
] \
is_debug=false \
is_official_build=true \
@@ -111,7 +116,7 @@
-DSK_DISABLE_AAA \
-DSK_DISABLE_DAA \
-std=c++17 \
- --pre-js $BASE_DIR/cpu.js \
+ $WASM_GPU \
--post-js $BASE_DIR/ready.js \
--bind \
$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
@@ -129,6 +134,7 @@
-s USE_LIBPNG=1 \
-s WARN_UNALIGNED=1 \
-s WASM=1 \
+ -s USE_WEBGL2=1 \
-o $BUILD_DIR/debugger.js
# TODO(nifong): write unit tests
diff --git a/experimental/wasm-skp-debugger/cpu.js b/experimental/wasm-skp-debugger/cpu.js
index 2e5ca81..ddd0ac1 100644
--- a/experimental/wasm-skp-debugger/cpu.js
+++ b/experimental/wasm-skp-debugger/cpu.js
@@ -3,7 +3,7 @@
(function(DebuggerView){
// Takes in an html id or a canvas element
DebuggerView.MakeSWCanvasSurface = function(idOrElement) {
- var canvas = idOrElement;
+ let canvas = idOrElement;
if (canvas.tagName !== 'CANVAS') {
canvas = document.getElementById(idOrElement);
if (!canvas) {
@@ -12,10 +12,11 @@
}
// Maybe better to use clientWidth/height. See:
// https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
- var surface = DebuggerView.MakeSurface(canvas.width, canvas.height);
+ let surface = DebuggerView.MakeSurface(canvas.width, canvas.height);
if (surface) {
surface._canvas = canvas;
}
+ console.log('Made HTML Canvas Surface');
return surface;
};
@@ -26,7 +27,7 @@
DebuggerView.MakeSurface = function(width, height) {
/* @dict */
- var imageInfo = {
+ let imageInfo = {
'width': width,
'height': height,
'colorType': DebuggerView.ColorType.RGBA_8888,
@@ -34,11 +35,11 @@
// (and those pixels are un-premultiplied, i.e. straight r,g,b,a)
'alphaType': DebuggerView.AlphaType.Unpremul,
}
- var pixelLen = width * height * 4; // it's 8888, so 4 bytes per pixel
+ let pixelLen = width * height * 4; // it's 8888, so 4 bytes per pixel
// Allocate the buffer of pixels to be drawn into.
- var pixelPtr = DebuggerView._malloc(pixelLen);
+ let pixelPtr = DebuggerView._malloc(pixelLen);
- var surface = this._getRasterDirectSurface(imageInfo, pixelPtr, width*4);
+ let surface = this._getRasterDirectSurface(imageInfo, pixelPtr, width*4);
if (surface) {
surface._canvas = null;
surface._width = width;
@@ -61,8 +62,8 @@
// Do we have an HTML canvas to write the pixels to?
// We will not if this a GPU build or a raster surface, for example.
if (this._canvas) {
- var pixels = new Uint8ClampedArray(DebuggerView.buffer, this._pixelPtr, this._pixelLen);
- var imageData = new ImageData(pixels, this._width, this._height);
+ let pixels = new Uint8ClampedArray(DebuggerView.buffer, this._pixelPtr, this._pixelLen);
+ let imageData = new ImageData(pixels, this._width, this._height);
this._canvas.getContext('2d').putImageData(imageData, 0, 0);
}
};
diff --git a/experimental/wasm-skp-debugger/debugger/index.html b/experimental/wasm-skp-debugger/debugger/index.html
index 469716c..17223f1 100644
--- a/experimental/wasm-skp-debugger/debugger/index.html
+++ b/experimental/wasm-skp-debugger/debugger/index.html
@@ -5,12 +5,12 @@
<title>Debugger Demo</title>
<script src="bin/debugger.js"></script>
<script>
-var index = 0;
+let index = 0;
+let surface;
DebuggerInit({
locateFile: (file) => '/node_modules/debugger/bin/'+file,
}).ready().then((Debugger) => {
- let surface;
const player = new Debugger.SkpDebugPlayer();
// Define an event handler for the file input dialog
@@ -41,7 +41,8 @@
canvas = document.getElementById('debugger_view');
canvas.width = bounds.fRight - bounds.fLeft;
canvas.height = bounds.fBottom - bounds.fTop;
- surface = Debugger.MakeSWCanvasSurface('debugger_view');
+ //Assume GPU selected initially
+ surface = Debugger.MakeWebGLCanvasSurface('debugger_view', canvas.width, canvas.height);
document.getElementById('command-count').innerHTML = player.getSize();
player.setClipVizColor(0);
@@ -51,7 +52,7 @@
function playFile() {
interval = parseFloat(document.getElementById('interval').value);
- var intervalID = setInterval(function() {
+ let intervalID = setInterval(function() {
if (index < 789){
player.drawTo(surface, index);
surface.flush();
@@ -60,6 +61,14 @@
}, interval);
}
+ // Discard canvas when switching between cpu/gpu backend because it's bound to a context.
+ function replaceCanvas() {
+ canvas = document.getElementById('debugger_view');
+ let newCanvas = canvas.cloneNode(true);
+ let parent = canvas.parentNode;
+ parent.replaceChild(newCanvas, canvas);
+ }
+
document.getElementById('file-input')
.addEventListener('change', readSkpFile);
@@ -99,12 +108,30 @@
console.log('parsed json');
});
+ document.getElementById('backend_gpu')
+ .addEventListener('change', function(e) {
+ if (e.target.checked) {
+ replaceCanvas();
+ surface = Debugger.MakeWebGLCanvasSurface('debugger_view', canvas.width, canvas.height);
+ }
+ });
+
+ document.getElementById('backend_cpu')
+ .addEventListener('change', function(e) {
+ if (e.target.checked) {
+ replaceCanvas();
+ surface = Debugger.MakeSWCanvasSurface('debugger_view');
+ }
+ });
+
});
</script>
</head>
<body>
<canvas id=debugger_view width=400 height=400></canvas>
<div style="float:right">
+ <input type="radio" name="backend" value="CPU" id="backend_cpu"> CPU
+ <input type="radio" name="backend" value="WebGL" id="backend_gpu" checked> WebGL<br>
<input type="file" id="file-input" /> | <span id="command-count">0</span> commands<br>
<input type="button" id="play_button" value="Play" />
command interval in ms
diff --git a/experimental/wasm-skp-debugger/debugger_bindings.cpp b/experimental/wasm-skp-debugger/debugger_bindings.cpp
index 339bbe2..c278dc0 100644
--- a/experimental/wasm-skp-debugger/debugger_bindings.cpp
+++ b/experimental/wasm-skp-debugger/debugger_bindings.cpp
@@ -14,6 +14,16 @@
#include <emscripten.h>
#include <emscripten/bind.h>
+#if SK_SUPPORT_GPU
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GrGLInterface.h"
+#include "GrGLTypes.h"
+
+#include <GL/gl.h>
+#include <emscripten/html5.h>
+#endif
+
using JSColor = int32_t;
struct SimpleImageInfo {
@@ -113,6 +123,67 @@
SkIRect fBounds;
};
+#if SK_SUPPORT_GPU
+sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
+{
+ EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
+ if (r < 0) {
+ SkDebugf("failed to make webgl context current %d\n", r);
+ return nullptr;
+ }
+ // setup GrContext
+ auto interface = GrGLMakeNativeInterface();
+ // setup contexts
+ sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
+ return grContext;
+}
+
+sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
+ glClearColor(0, 0, 0, 0);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+
+ // Wrap the frame buffer object attached to the screen in a Skia render
+ // target so Skia can render to it
+ GrGLint buffer;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
+ GrGLFramebufferInfo info;
+ info.fFBOID = (GrGLuint) buffer;
+ SkColorType colorType;
+
+ info.fFormat = GL_RGBA8;
+ colorType = kRGBA_8888_SkColorType;
+
+ GrBackendRenderTarget target(width, height, 0, 8, info);
+
+ sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
+ kBottomLeft_GrSurfaceOrigin,
+ colorType, nullptr, nullptr));
+ return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, int width, int height) {
+ SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
+
+ sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+ SkBudgeted::kYes,
+ info, 0,
+ kBottomLeft_GrSurfaceOrigin,
+ nullptr, true));
+ return surface;
+}
+
+sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, SimpleImageInfo sii) {
+ sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
+ SkBudgeted::kYes,
+ toSkImageInfo(sii), 0,
+ kBottomLeft_GrSurfaceOrigin,
+ nullptr, true));
+ return surface;
+}
+#endif
+
using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module) {
@@ -166,4 +237,18 @@
// Add a optional_override to change it out.
self.clear(SkColor(color));
}));
+
+ #if SK_SUPPORT_GPU
+ class_<GrContext>("GrContext")
+ .smart_ptr<sk_sp<GrContext>>("sk_sp<GrContext>");
+ function("currentContext", &emscripten_webgl_get_current_context);
+ function("setCurrentContext", &emscripten_webgl_make_context_current);
+ function("MakeGrContext", &MakeGrContext);
+ function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
+ function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
+ sk_sp<GrContext>, int, int)>(&MakeRenderTarget));
+ function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
+ sk_sp<GrContext>, SimpleImageInfo)>(&MakeRenderTarget));
+ constant("gpu", true);
+ #endif
}
diff --git a/experimental/wasm-skp-debugger/gpu.js b/experimental/wasm-skp-debugger/gpu.js
new file mode 100644
index 0000000..61b8787
--- /dev/null
+++ b/experimental/wasm-skp-debugger/gpu.js
@@ -0,0 +1,92 @@
+// Adds compile-time JS functions to augment the DebuggerView interface.
+// Specifically, anything that should only be on the GPU version of DebuggerView.
+(function(DebuggerView){
+ function makeWebGLContext(canvas, attrs) {
+ // These defaults come from the emscripten _emscripten_webgl_create_context
+ // TODO(nifong): All these settings appear to be ignored. investigate.
+ var contextAttributes = {
+ alpha: 1,
+ depth: 1,
+ stencil: 0,
+ antialias: 1,
+ premultipliedAlpha: 1,
+ preserveDrawingBuffer: 0,
+ preferLowPowerToHighPerformance: 0,
+ failIfMajorPerformanceCaveat: 0,
+ majorVersion: 1,
+ minorVersion: 0,
+ enableExtensionsByDefault: 1,
+ explicitSwapControl: 0,
+ renderViaOffscreenBackBuffer: 0,
+ };
+ if (!canvas) {
+ console.log('null canvas passed into makeWebGLContext');
+ return 0;
+ }
+ // This check is from the emscripten version
+ if (contextAttributes['explicitSwapControl']) {
+ console.log('explicitSwapControl is not supported');
+ return 0;
+ }
+ // GL is an enscripten provided helper
+ // See https://github.com/emscripten-core/emscripten/blob/incoming/src/library_webgl.js
+ let context = GL.createContext(canvas, contextAttributes);
+ if (!context) {
+ console.log('Could not get a WebGL context from the canvas element.');
+ }
+ console.log('Made Web Gl Canvas Surface');
+ return context
+ }
+
+ DebuggerView.GetWebGLContext = function(canvas, attrs) {
+ return makeWebGLContext(canvas, attrs);
+ };
+
+ // arg can be of types:
+ // - String - in which case it is interpreted as an id of a
+ // canvas element.
+ // - HTMLCanvasElement - in which the provided canvas element will
+ // be used directly.
+ // Width and height can be provided to override those on the canvas
+ // element, or specify a height for when a context is provided.
+ DebuggerView.MakeWebGLCanvasSurface = function(arg, width, height) {
+ var canvas = arg;
+ if (canvas.tagName !== 'CANVAS') {
+ canvas = document.getElementById(arg);
+ if (!canvas) {
+ throw 'Canvas with id ' + arg + ' was not found';
+ }
+ }
+ // we are ok with all the defaults
+ var ctx = DebuggerView.GetWebGLContext(canvas);
+
+ if (!ctx || ctx < 0) {
+ throw 'failed to create webgl context: err ' + ctx;
+ }
+
+ if (!canvas && (!width || !height)) {
+ throw 'height and width must be provided with context';
+ }
+
+ var grcontext = this.MakeGrContext(ctx);
+ if (!grcontext) {
+ throw (
+ 'failed to create grcontext. Open GL driver may not support all needed functions: err '
+ + grcontext);
+ }
+
+ // Maybe better to use clientWidth/height. See:
+ // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+ var surface = this.MakeOnScreenGLSurface(grcontext,
+ width || canvas.width,
+ height || canvas.height);
+ if (!surface) {
+ // Don't fall back silently in the debugger, the user explicitly controls which backend he
+ // wants via the UI. Calling function may catch this and show the user an error.
+ throw ('Failed to create OpenGL surface. GPU Backend unavailable.');
+ }
+ return surface;
+ };
+ // Default to trying WebGL first.
+ DebuggerView.MakeCanvasSurface = DebuggerView.MakeWebGLCanvasSurface;
+}(Module)); // When this file is loaded in, the high level object is "Module";