29

I'm trying to export an image rendered with WebGL on a linux server without a GPU. To do this I'm using headless Chrome however the exported image is black (example exported image, taking a screenshot of page shows its just canvas that is black). I was hoping for some help figuring out why this is happening.

To export the image I render the image into a canvas, export data via canvas.toDataURL('image/jpeg') and then post the data to the server. I'm using Pixi.js for rendering, if I use canvas renderer then everything works on the server; It's WebGL rendering thats not working. It's worth noting the WebGL render works fine in Chrome 63 on a Macbook.

To control Chrome I'm using Puppeteer. All I'm doing is opening a page, waiting a second, and then closing it again:

puppeteer
  .launch({
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
    ],
  })
  .then(browser => {
    return browser.newPage().then(page => {
      return page
        .goto(url)
        .then(() => page.waitFor(1000))
        .then(() => browser.close())
        .catch(err => console.error('Failed', err));
    });
  })

These are the arguments puppeteer passes to Chrome:

[
  '--disable-background-networking',
  '--disable-background-timer-throttling',
  '--disable-client-side-phishing-detection',
  '--disable-default-apps',
  '--disable-extensions',
  '--disable-hang-monitor',
  '--disable-popup-blocking',
  '--disable-prompt-on-repost',
  '--disable-sync',
  '--disable-translate',
  '--metrics-recording-only',
  '--no-first-run',
  '--remote-debugging-port=0',
  '--safebrowsing-disable-auto-update',
  '--enable-automation',
  '--password-store=basic',
  '--use-mock-keychain',
  '--user-data-dir=/tmp/puppeteer_dev_profile-GhEAXZ',
  '--headless',
  '--disable-gpu',
  '--hide-scrollbars',
  '--mute-audio',
  '--no-sandbox',
  '--disable-setuid-sandbox'
]

The swiftshader author said in June headless WebGL rendering is possible and it seems to be confirmed by this Chromium issue so I guess I'm missing something. Has anyone got any ideas what I'm doing wrong?

A couple of things I've tried:

  • Not passing in --disable-gpu
  • --use-gl=swiftshader-webgl, --use-gl=swiftshader, --use-gl=osmesa
  • Taking a full screen screenshot to see if its just canvas. Whole screen is just black.

Versions

  • Chrome: linux-515411
  • puppeteer: 0.13.0
  • node: 8.2.1
  • Linux: CentOS 7

This is what I needed to install on my server to get chrome to run (Source)

yum install cups-libs dbus-glib libXrandr libXcursor libXinerama cairo cairo-gobject pango ffmpeg
rpm -ivh --nodeps http://mirror.centos.org/centos/7/os/x86_64/Packages/atk-2.22.0-3.el7.x86_64.rpm
rpm -ivh --nodeps http://mirror.centos.org/centos/7/os/x86_64/Packages/at-spi2-atk-2.22.0-2.el7.x86_64.rpm
rpm -ivh --nodeps http://mirror.centos.org/centos/7/os/x86_64/Packages/at-spi2-core-2.22.0-1.el7.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/g/GConf2-3.2.6-7.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/l/libXScrnSaver-1.2.2-6.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/l/libxkbcommon-0.3.1-1.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/l/libwayland-client-1.2.0-3.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/l/libwayland-cursor-1.2.0-3.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/20/Fedora/x86_64/os/Packages/g/gtk3-3.10.4-1.fc20.x86_64.rpm
rpm -ivh --nodeps http://dl.fedoraproject.org/pub/archive/fedora/linux/releases/16/Fedora/x86_64/os/Packages/gdk-pixbuf2-2.24.0-1.fc16.x86_64.rpm
BoffinBrain
  • 5,697
  • 5
  • 28
  • 53
James Hollingworth
  • 13,192
  • 12
  • 37
  • 56
  • Can you give more details about your hardware configuration? AFAIK, WebGL does require a GPU. Many (most?) recent computers do include a GPU in the form of the Intel “integrated graphics” which are more than enough for 3D rendering if you don’t push them too much, but a VM without GPU access would be a problem... – jcaron Jan 01 '18 at 12:56
  • Our servers don't have GPUs. Theres a lot of documentation that suggests its possible: "SwiftShader is a high-performance CPU-based implementation of the OpenGL ES" (https://github.com/google/swiftshader) https://blog.chromium.org/2016/06/universal-rendering-with-swiftshader.html. Chromium Issue adding Swiftshader https://bugs.chromium.org/p/chromium/issues/detail?id=630728. Chromium documentation discussing software compositor for when GPU is not available http://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome#TOC-Appendix-B:-The-Software-Compositor – James Hollingworth Jan 01 '18 at 21:40
  • Did you actually install Swiftshader? Don’t see anything in your post about that? Also, do you have a way to capture Chrome’s console? – jcaron Jan 01 '18 at 21:46
  • Passing --disable-gpu into my chrome options allowed me to render a html canvas pie-chart. Thanks very much for the tip! – Mark Gargan Feb 04 '18 at 18:54
  • The service I run (browserless) doesn't seem to have issues with WebGL (aside from slowness, but that's all dependent on the resources you allocate). You can see an example [here](https://chrome.browserless.io/?script=await%20page.goto(%27http%3A%2F%2Fdavid.li%2Fwaves%2F%27)%3B). – browserless Apr 27 '18 at 03:15

5 Answers5

9

There's an open bug which affects systems without X11 libraries: crbug.com/swiftshader/79. It prevents Chrome OS from running with SwiftShader, but the same issue would also happen on a headless Linux system which has no X11 support.

Fortunately it should be feasible to install X11 and get things running. I'm not 100% sure which packages provide the necessary libraries, but try these: xorg xserver-xorg xvfb libx11-dev libxext-dev libxext-dev:i386

Eventually the SwiftShader bug will be fixed so it doesn't require X11 whatsoever.

Vadim Ovchinnikov
  • 10,848
  • 4
  • 43
  • 73
Nicolas Capens
  • 652
  • 4
  • 10
  • Firstly, Thanks for the help! I installed deps and checked they can be dynamically loaded but problem persists (https://gist.github.com/jhollingworth/24c57fe654d5f9b591d577281c7992c4). I ran a simple WebGL example (https://gist.github.com/jhollingworth/f9bae6a65be1f61eadc4c538c5ed983c) in a debug build of chromium with GPU logging: https://gist.github.com/jhollingworth/0b1843dc4417fd282d238cb6ef46628b. `GL_RENDERER = Google SwiftShader` and `GL_VERSION = OpenGL ES 3.0 SwiftShader 3.3.0.2`. I'm assume that means Swiftshader is loaded? Any thoughts on other things I could try? – James Hollingworth Jan 04 '18 at 19:16
  • One thing I wondered about, each call to `toDataUrl()` ranges from 30ms to 500ms depending on the complexity of the scene (e.g. Loading an image and then transforming it takes much longer than `gl.clearColor`). To me that implies the OpenGL commands are being executed (`gl.getError` returns `NONE` each time) but for whatever reason WebGL isn't actually drawing into the canvas. I dunno if you had any thoughts on that? – James Hollingworth Jan 04 '18 at 19:22
  • is there any update on this? I'm facing the same issue: https://stackoverflow.com/questions/61239552/unity3d-webgl-headless-not-rendering – Apidcloud Apr 15 '20 at 22:42
  • 1
    Just to update: this example seems to work fine under MacOS. Meanwhile I was able to solve the unity3D webgl problem by disabling the anti-aliasing. – Apidcloud Apr 16 '20 at 07:45
9

So i've partially solved the issue by setting premultipliedAlpha to false. When it's true (default), toDataURL would return an empty image. When false, it returns the rendered image.

<!DOCTYPE html>
<html>
<body>
  <canvas id="canvas" width="1080" height="1080"></canvas>
  <script type="text/javascript">
    var canvas = document.getElementById('canvas');
    var gl = canvas.getContext('webgl', {
        premultipliedAlpha: false
    });

    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
    gl.clearColor(0.99, 0, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    var IMAGE_PREFIX = 'data:image/png;base64,';
    var image = canvas.toDataURL('image/png').substring(IMAGE_PREFIX.length);

    // save(image)
  </script>
</body>
</html>

What is interesting is if I take a screenshot using puppeteer I can see the rendered image, regardless of whether premultipliedAlpha is true or false.

James Hollingworth
  • 13,192
  • 12
  • 37
  • 56
  • I also found an issue with Swiftshader in Headless Chrome which might be related to this https://bugs.chromium.org/p/swiftshader/issues/detail?id=92 – James Hollingworth Jan 08 '18 at 08:34
1

I don't know if this can help you, but there are options you can set when creating a WebGL context. Depending on the browser implementation, you can have different default values.

Have you tried to force preserveDrawingBuffer to true ?

var gl = canvas.getContext( "webgl", {
    preserveDrawingBuffer: true
});

Here is what MDN says about this option:

preserveDrawingBuffer: If the value is true the buffers will not be cleared and will preserve their values until cleared or overwritten by the author.

Tolokoban
  • 2,017
  • 11
  • 17
1

If you want to run it on server and have no GPU available there, you need to use something instead of it.

WebGL 1.0 is based on OpenGL ES 2.0 spec, which is based on OpenGL 2.1 spec. There is Mesa library(https://en.wikipedia.org/wiki/Mesa_(computer_graphics)), which implements software renderer and is used for validation of OpenGL implementation by vendors. I think it supports OpenGL up to 3.1, but I can be wrong and now it supports version even highier.

It's possible to install Mesa as driver in *nix and have it do OpenGL rendering using software implementation.

I suggest to check accepted answer here: how to force chrome to use mesa software driver for webgl I'm pretty sure it will solve your issue

Dmitry Tolmachov
  • 375
  • 2
  • 11
  • 1
    Chrome already comes with an OpenGL ES implementation for the CPU, called SwiftShader, which is used as a fallback for WebGL when there's no GPU or it has been blacklisted. It works fine under many conditions, but apparently James found a bug that affects a specific headless setup. – Nicolas Capens Jan 04 '18 at 19:32
0

Setting preserveDrawingBuffer to true solved the problem for me

Andrea Gherardi
  • 780
  • 1
  • 8
  • 17