Server-Side Rendering

From this section you will find out how to run Verge3D on server to perform rendering in headless mode. This feature can be employed if the user has low-end hardware or slow network, or to provide the best quality possible, yet still having configurable content.

Running Chrome on server in headless mode

You can run Google Chrome (or Chromium if you prefer it) in headless mode, e.g without using screen or GUI. To do this, specify the --headless option:

google-chrome --headless --use-gl=egl

The --use-gl=egl option enables accelerated GPU rendering in headless mode:

WebGL report page
Unmasked Vendor/Renderer lines say that Chrome Headless uses real GPU of the target system

By default headless command does nothing meaningful. To allow remote debugging of the page, specify --remote-debugging-port=9222 option:

google-chrome --headless --use-gl=egl --remote-debugging-port=9222

then run regular desktop Chrome to inspect the page. Configure Discover network targets then click inspect:

Another important feature of headless mode is ability to take screenshots:

google-chrome --headless --use-gl=egl --screenshot

or generate PDF files:

google-chrome --headless --use-gl=egl --print-to-pdf

Check out the official documentation from Google for more info about Chrome Headless.

Using Node.js and Puppeteer

Sometimes you need more control of the Chrome Headless mode. For example, you might need taking screenshots in automated manner, or implement some client-server routine for requesting screenshots from the server. For that, Google developed a nice Node.js library called Puppeteer.

To install it, simply add the following script to any directory and name it package.json (download):

{ "description": "Headless Chrome tests with Puppeteer", "dependencies": { "commander": "*", "puppeteer": "*" } }

Then go to that directory and execute:

npm install

to install Puppeteer and all required dependencies (including pre-built binary of the Chromium browser).

Use the following Node.js script to automate taking screenshots of your Verge3D apps (download):

#!/usr/bin/node const program = require('commander'); const puppeteer = require('puppeteer'); program .option('-u, --url [url]', 'URL to open', 'http://localhost:8668/') .option('-s, --screenshot-path programmers_guide/Server-Side-Rendering', 'A path where to save the screenshot', './screenshot.png') .option('-t, --timeout [ms]', 'Timeout after page loading before taking the screenshot', function(val) { return parseInt(val); }, 1000) .option('-W, --viewport-width [px]', 'The width of the viewport', function(val) { return parseInt(val); }, 1920) .option('-H, --viewport-height [px]', 'The height of the viewport', function(val) { return parseInt(val); }, 1080) .parse(process.argv); async function run() { console.log('Launching headless Chrome...'); const browser = await puppeteer.launch({ args: ['--use-gl=egl'] }); console.log('Opening new page...'); const page = await browser.newPage(); const opts = program.opts(); console.log('Setting viewport size to ' + opts.viewportWidth + 'x' + opts.viewportHeight + '...'); await page.setViewport({ width: opts.viewportWidth, height: opts.viewportHeight }); console.log('Opening ' + opts.url + ' ...'); await page.goto(opts.url); console.log('Waiting for ' + opts.timeout + 'ms ...'); await timeout(opts.timeout); console.log('Taking screenshot...'); await page.screenshot({ path: opts.screenshotPath, type: 'png' }); console.log('Page\'s screenshot saved to ' + opts.screenshotPath); console.log('Closing Chrome...'); await browser.close(); } function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }; run();

Place the code as screenshot.js in the same directory you installed Puppetter and launch:

node screenshot.js -u

to take screenshot of the Spinner application and save it as screenshot.png image:

The screenshot script comes with a variety of useful options. To get help, run it with the -h argument:

node screenshot.js -h

With Puppeteer you can also capture videos of your Verge3D renderings. See here for more info.

Next Steps

In the screenshot.js script we use the timeout property to wait for app loading. In the real-world scenario you might need to provide a more advanced awaiting routine (download):

console.log('Waiting for application loading...'); await page.evaluate(() => { return new Promise(resolve => { const interval = setInterval(() => { if (window.v3d.apps !== undefined && window.v3d.apps.length > 0) { clearInterval(interval); resolve(); } }, 0); }); }); await page.evaluate(() => { const app = window.v3d.apps[0]; return new Promise(resolve => { app.addEventListener('afterRender', () => { resolve(); }); }); });

Also, we did not cover any client-server interaction. For that you can implement a simple HTTP server or a more advanced WebSocket server.

Got Questions?

Feel free to ask on the forums!