High CPU load on trivial Skia WASM app

207 views
Skip to first unread message

Chris Schütte

unread,
Oct 23, 2023, 8:41:32 AM10/23/23
to skia-discuss
Hi devs,

I am working for Solid Labs and we are currently evaluating 2D graphics libraries for a web-based content creation tool. I wanted to conduct some quick performance tests, but ran into behavior I did not expect.

I set up a simple project which only clears the canvas and flushes the context
canvas_->clear(SK_ColorGRAY);
ctx_->flush();
with canvas_ a SkCanvas* and ctx_ a sk_sp<GrDirectContext>.

The above code is called at 60 FPS using
editor::emscripten::SetMainLoop(OnApplicationUpdate, app);

Surprisingly I am getting 100% load on one of my cores with 25% GPU utilization (RTX 3080). I looked into Chrome's performance tools.

Screenshot_20231023_103520.png

I am building inside the Skia repo using Bazel with --config=ck_full_webgl2_release_chrome and the Ganesh webgl backend. I am running on Ubuntu desktop with Chrome 114 and an NVIDIA GeForce RTX 3080 Ti.

I this expected behavior? If not, can anyone point me in the right direction? Happy to spend time investigating this as I think Skia would potentially be a great choice for what we building.

Thanks :)

Best,
Chris

P.S.: I am initializing my WebGL context using
EmscriptenWebGLContextAttributes attr;
emscripten_webgl_init_context_attributes(&attr);
attr.alpha = 1;
attr.depth = 1;
attr.stencil = 8;
attr.antialias = 0;
attr.premultipliedAlpha = 1;
attr.preserveDrawingBuffer = 0;
attr.enableExtensionsByDefault = 1;
attr.explicitSwapControl = 0;
attr.renderViaOffscreenBackBuffer = 0;
attr.majorVersion = 2;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx =
emscripten_webgl_create_context("#canvas", &attr);
emscripten_webgl_make_context_current(ctx);

// Set up and clear viewport.
glViewport(0, 0, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0, 0, 0, 0);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

and Skia using
// Initialize the Skia surface and canvas.
auto interface = GrGLMakeNativeInterface();
assert(interface);
ctx_ = GrDirectContext::MakeGL(interface);
assert(ctx_);
GrGLint sampleCnt;
glGetIntegerv(GL_SAMPLES, &sampleCnt);
GrGLint stencil;
glGetIntegerv(GL_STENCIL_BITS, &stencil);
ctx_->resetContext(kRenderTarget_GrGLBackendState | kMisc_GrGLBackendState);
GrGLFramebufferInfo info;
info.fFBOID = 0;
sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
const auto colorSettings = ColorSettings(colorSpace);
info.fFormat = colorSettings.pixFormat;
target_ = GrBackendRenderTargets::MakeGL(canvas_width, canvas_height,
sampleCnt, stencil, info);
surface_ = SkSurfaces::WrapBackendRenderTarget(
ctx_.get(), target_, kBottomLeft_GrSurfaceOrigin, colorSettings.colorType,
colorSpace, nullptr);
assert(surface_);

I am building using the following LINKOTPS, DEFINES and flags

LINKOPTS = [
"--bind", # Compiles the source code using the Embind bindings to connect C/C++ and JavaScript
"-fno-rtti",
"-sALLOW_MEMORY_GROWTH",
"-sUSE_PTHREADS=0", # Disable pthreads
"-sMODULARIZE=1",
"-sDISABLE_EXCEPTION_CATCHING", # Disable all exception catching
"-sWASM",
"-sUSE_GLFW=3",
"-sMAX_WEBGL_VERSION=2",
"-sASSERTIONS", # Turn on assertions
"-sGL_ASSERTIONS",
"-sEXPORT_NAME=createModule",
]

DEFINES = [
"EMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0", # Allows us to compile with -fno-rtti
]

...

set_flags = {
"gpu_backend": [
"gl_ganesh",
],
"with_gl_standard": [
"webgl_standard",
],
},



John Stiles

unread,
Oct 23, 2023, 9:51:34 AM10/23/23
to skia-d...@googlegroups.com
This sounds like the expected behavior of a tight loop which is clearing the buffer repeatedly as fast as possible. How are you limiting it to 60fps? Could you show us the work loop you've set up? Thanks.

--
You received this message because you are subscribed to the Google Groups "skia-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to skia-discuss...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/skia-discuss/3894bc7d-4df9-4562-9808-35b1baa82569n%40googlegroups.com.

Chris Schütte

unread,
Oct 23, 2023, 10:23:09 AM10/23/23
to skia-discuss
Hi John,

Thanks for your message! I am using emscripten's emscripten_request_animation_frame_loop which limits the fps to 60 fps. I verified that the provided function is actually called at 60 fps.

The CPU load disappears when I don't flush the Skia context, i.e. comment

ctx_->flush();

so the load does not seem to be an artifact of some busy wait on emscripten's behalf or something, but work generated by the Skia context flushing.

Thanks,
Chris

John Stiles

unread,
Oct 23, 2023, 10:33:18 AM10/23/23
to skia-d...@googlegroups.com
FWIW, the link doesn't actually say that emscripten_request_animation_frame_loop limits the framerate to 60. It looks like it limits it to your browser's refresh rate, which is dependent on your particular setup (e.g. 144Hz monitors could run at a higher refresh rate, or the browser could artificially limit the rate in low-power mode).
Out of curiosity, what happens if you use emscripten_set_main_loop() instead? This lets you set the framerate yourself.

Chris Schütte

unread,
Oct 23, 2023, 11:05:05 AM10/23/23
to skia-discuss
On Monday, 23 October 2023 at 16:33:18 UTC+2 johns...@google.com wrote:
FWIW, the link doesn't actually say that emscripten_request_animation_frame_loop limits the framerate to 60. It looks like it limits it to your browser's refresh rate, which is dependent on your particular setup (e.g. 144Hz monitors could run at a higher refresh rate, or the browser could artificially limit the rate in low-power mode).

You are right of course. I did measure my refresh rate using std::chrono::high_resolution_clock over 100 frames and the rate is 60 FPS.
 
Out of curiosity, what happens if you use emscripten_set_main_loop() instead? This lets you set the framerate yourself.

Thanks, that was a good experiment to run:

1) emscripten_set_main_loop with fps = 60 shows close to no load, however actual FPS vary between 50 - 60 FPS. emscripten then tells me that

"Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!
warnOnce @ editor.js:8"

2)  emscripten_set_main_loop with fps = 0, has actual FPS constant at 60 FPS, however results in considerable load on one of the cores. 

Ok, so it seems that this is a behavior due to some interaction with requestAnimationFrame (because w/o the flush() no load appears in this case as well). I think I am happy with that for now. If you know why that might be, I'd be interested to be educated :)

Thanks for your help!
Chris
Reply all
Reply to author
Forward
0 new messages