Howto: Building Chromium on Ozone-GBM for RPi4
Tags: chromium, raspberrypi, embedded
Published: 2020-08-18 06:49:42

## Introduction

I recently spent several days on building [Chromium](https://www.chromium.org/Home) on [the Ozone-GBM platform](https://chromium.googlesource.com/chromium/src.git/+/master/docs/ozone_overview.md) for [Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) (RPi4).

This attempt is to gain smaller footprint and better performance for my embedded system projects, but unfortunately no satisfactory result for now.  In this article I'll share steps and tips I got so that you can join the effort as a tester or a developer.

My former attempt with Ozone-Dispmanx for RPi3 has been shared in GitHub: {% github yaegashi/ozone-dispmanx %}

## Cross building environment

To build Chromium for RPi4, you'll first follow the generic instruction on [the official developer site](https://www.chromium.org/developers/how-tos/get-the-code). Read [Checking out and building Chromium on Linux](https://chromium.googlesource.com/chromium/src/+/master/docs/linux/build_instructions.md).

System requirements:

- Linux build machine 
  - Denoted as `(build)` in this article
  - OS: Ubuntu 18.04 or 20.04
  - CPU: AMD64, more than 4 cores / 8 threads recommended
  - Memory: more than 16GB recommended
  - Storage: more than 100GB free space
- [Raspberry Pi 4 Model B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)
  - Denoted as `(pi)` in this article
  - OS: [Raspberry Pi OS (32-bit) with desktop](https://www.raspberrypi.org/downloads/raspberry-pi-os/) 
  - Storage: 1GB free space

In this article, we're going to build Chromium executable for 32-bit ARM (Debian armhf).

Clone depot_tools.git anywhere you want:

```terminal
(build) $ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
```

Add its path to `PATH`.  Put the following line in `~/.bashrc` and restart your shell:

```bash
export PATH=$PATH:/path/to/depot_tools
```

Run `fetch` to clone the Chromium sources anywhere you want, then run `install-build-deps.sh` to set up build dependencies:

```terminal
(build) $ mkdir ~/chromium
(build) $ cd ~/chromium
(build) $ fetch --nohooks chromium
(build) $ ./src/build/install-build-deps.sh
```

To run Chromium on Ozone-GBM, you have to set up the cross-build environment targeting ChromiumOS on ARM.  Details are explained in [Linux Chromium Arm Recipes](https://chromium.googlesource.com/chromium/src/+/master/docs/linux/chromium_arm.md).

Edit `.gclient` file generated by previous `fetch`.  Append 2 lines as follows:

```
target_cpu = ["arm"]
target_os = ["chromeos"]
```

Run `gclient` sync and runhooks:

```terminal
(build) $ gclient sync
(build) $ gclient runhooks
```

This should acquire additional dependencies including ChromiumOS sources and install Debian armhf sysroot in `src/build/linux/debian_sid_arm-sysroot`.

Using `ccache` is strongly recommended, so set it up now:

```terminal
(build) $ sudo apt install ccache
(build) $ ccache -M 20GB
```

## First ninja build (ozone_demo)

Run `gn` in the source tree to prepare a directory for the production build:

```terminal
(build) $ cd ~/chromium/src
(build) $ gn args out/p1
```

This creates a build directory `out/p1` and launch a text editor on `args.gn` in it for you.  You should put the following lines and save it.

```
# Set build arguments here. See `gn help buildargs`.
cc_wrapper = "ccache"
dcheck_always_on = true
enable_nacl = false
has_native_accessibility = false
is_chrome_branded = false
is_debug = false
is_official_build = false
ozone_auto_platforms = false
ozone_platform = "gbm"
ozone_platform_gbm = true
target_cpu = "arm"
target_os = "chromeos"
target_sysroot = "//build/linux/debian_sid_arm-sysroot"
use_evdev_gestures = false
use_glib = false
use_gtk = false
use_ozone = true
use_pulseaudio = false
use_system_minigbm = false
use_vc4_minigbm = true
use_xkbcommon = false
```

You can manually edit `out/p1/args.gn`.  Make sure to run `gn gen out/p1` after making any changes.

Now you can run `ninja` to build chromium executables.  For the first attempt, I recommend to build `ozone_demo` for smoke testing:

```terminal
(build) $ ninja -C out/p1 -j16 ozone_demo
```

`-jN` specifies the number of parallel build jobs.  The recommended `N` setting is the number of CPU threads of the build machine.  `ozone_demo` build contains less than 10k targets and it can take several hours depending on the spec of your build machine.

Each time it builds, you need to bring executable files to your RPi4.  Because there're so many temporary files in the build directory, I use the following shell script `pack.sh` to make an archive of files needed on runtime:

```bash:pack.sh
#!/bin/bash

OUTDIR=${1-out/production}
DESTDIR=chromium-${OUTDIR##*/}
ARCHIVE=$DESTDIR.tar.gz

if ! test -d "$OUTDIR"; then
        echo "Not a directory: $OUTDIR"
        exit 1
fi

cd $OUTDIR
rm -rf $DESTDIR $ARCHIVE
mkdir -p $DESTDIR

tar     --exclude $DESTDIR \
        --exclude $ARCHIVE \
        --exclude gen \
        --exclude obj \
        --exclude pyproto \
        --exclude resources \
        --exclude Packages \
        --exclude 'newlib_*' \
        --exclude 'nacl_bootstrap*' \
        --exclude 'glibc_*' \
        --exclude 'clang_*' \
        --exclude 'irt_*' \
        --exclude '*.ninja*' \
        --exclude '*.runtime_deps' \
        --exclude '*.info' \
        --exclude '*.TOC' \
        -cf - . | tar -C $DESTDIR -xf -

tar -czvf $ARCHIVE $DESTDIR
```

`pack.sh` creates a tar+gz archive in the build directory.  You can transfer it to your RPi4 host.

```terminal
(build) $ ./pack.sh out/p1
chromium-p1/
chromium-p1/libEGL.so
chromium-p1/ozone_demo
chromium-p1/libminigbm.so
chromium-p1/gn_logs.txt
chromium-p1/android_clang_arm64/
chromium-p1/icudtl.dat
chromium-p1/libGLESv2.so
chromium-p1/android_clang_arm/
chromium-p1/swiftshader/
chromium-p1/swiftshader/libEGL.so
chromium-p1/swiftshader/libGLESv2.so
chromium-p1/args.gn
(build) $ ls -lh out/p1/chromium-p1.tar.gz 
-rw-rw-r-- 1 yaegashi yaegashi 13M Aug 16 13:49 out/p1/chromium-p1.tar.gz
(build) $ scp out/p1/chromium-p1.tar.gz pi@raspberrypi.local:
```

## RPi4 settings

This section is assuming Raspberry Pi 4B running the latest Raspbian 10 as of 2020-08-16.  Tested with RPi4s of RAM 4GB/8GB.

First, check `/boot/config.txt` for `dtoverlay=vc4-fkms-v3d` and `gpu_mem` with sufficient memory (>256MB) exists.

```text
[pi4]
# Enable DRM VC4 V3D driver on top of the dispmanx display stack
dtoverlay=vc4-fkms-v3d
max_framebuffers=2

[all]
#dtoverlay=vc4-fkms-v3d
gpu_mem=256
```

With vc4-fkms-v3d enabled, you should be able to see device files in `/dev/dri`:

```terminal
(pi) $ ls -l /dev/dri
total 0
drwxr-xr-x  2 root root        100 Aug 16 15:15 by-path
crw-rw----+ 1 root video  226,   0 Aug 16 15:15 card0
crw-rw----+ 1 root video  226,   1 Aug 16 15:15 card1
crw-rw----+ 1 root render 226, 128 Aug 16 15:15 renderD128
```

You have to be given permissions to access those devices.  Use `adduser` to join `video` and `render` group, `id` to check your current membership:

```terminal
(pi) $ sudo adduser pi video
(pi) $ sudo adduser pi render
(pi) $ exit
... logging in another session
(pi) $ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),105(input),107(render),109(netdev),997(gpio),998(i2c),999(spi)
```

Unfortunately, the latest Raspbian kernel is known to have [a problem](https://www.raspberrypi.org/forums/viewtopic.php?f=67&t=280864) in the DRM driver with Ozone-GBM as of the time I'm writing this article.  If your Raspberry Pi is already running kernel version 5.4.x or above, you have to downgrade it to version 4.19.118 in the previous release.

Before downgrading, upgrade every package to the latest version, including Linux kernel:

```terminal
(pi) $ sudo apt update
(pi) $ sudo apt dist-upgrade
(pi) $ sudo reboot
```

After reboot, manually downgrade `raspberrypi-kernel` package to version 1.20200512-2 acquired from [the package repository](http://archive.raspberrypi.org/debian/pool/main/r/raspberrypi-firmware/):

```terminal
(pi) $ wget http://archive.raspberrypi.org/debian/pool/main/r/raspberrypi-firmware/raspberrypi-kernel_1.20200512-2_armhf.deb
(pi) $ sudo apt install ./raspberrypi-kernel_1.20200512-2_armhf.deb 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Note, selecting 'raspberrypi-kernel' instead of './raspberrypi-kernel_1.20200512-2_armhf.deb'
The following packages will be DOWNGRADED:
  raspberrypi-kernel
0 upgraded, 0 newly installed, 1 downgraded, 0 to remove and 0 not upgraded.
Need to get 0 B/71.7 MB of archives.
After this operation, 1,245 kB disk space will be freed.
Do you want to continue? [Y/n] Y
...
Unpacking raspberrypi-kernel (1.20200512-2) over (1.20200811-1) ...
...
(pi) $ sudo reboot
```

After reboot, you should check the running kernel version by `uname -a`:

```terminal
(pi) $ uname -a
Linux pi4b2 4.19.118-v7l+ #1311 SMP Mon Apr 27 14:26:42 BST 2020 armv7l GNU/Linux
```

You cannot run Ozone-GBM executables and Raspbian GUI desktop at the same time.  To suspend the desktop, press  <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>F1</kbd> to switch to the VT console.  To return to the desktop, press <kbd>Alt</kbd> + <kbd>←</kbd> or <kbd>→</kbd> several times to get to VT7.

Now you're ready to run your first build.  Extract copied archive and run `ozone_demo` executable with the following environment variable and command line switches:

```terminal
(pi) $ tar -xzvf chromium-p1.tar.gz
(pi) $ cd chromium-p1
(pi) $ EGL_PLATFORM=surfaceless ./ozone_demo --disable-explicit-dma-fences
```

If everything goes well, you will be able to see your RPi4 cycling colors in the whole screen.  Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to stop it.

When it craches or renders unexpected output, you should collect debug log messages and examine what's going on:

```terminal
(pi) $ EGL_PLATFORM=surfaceless EGL_LOG_LEVEL=debug ./ozone_demo --enable-logging --v=1 --disable-explicit-dma-fences 2>&1 | tee debug.log
```

Pro Tip: you can run Ozone-GBM executables in the SSH remote sessions, and use `chvt` to switch the VT console.  Allocating a new VT (8th or later) is the safer and recommended option.  Switch to 7th VT to return to the desktop:

```terminal
(pi) $ sudo chvt 8
(pi) $ EGL_PLATFORM=surfaceless ./ozone_demo --disable-explicit-dma-fences
(pi) $ sudo chvt 7
```

## Building and running Chromium

After succesfull smoke testing with `ozone_demo`, let's continue bulding the next executable: `chrome`, the chromium browser.

```terminal
(build) $ ninja -C out/p1 -j16 chrome
```

This will display to you approx. 50k build targets and it can take 10+ hours to finish.

When it builds, run again `pack.sh` to make a new archive of the build directory and transfer to the RPi4 host:

```terminal
(build) $ ./pack.sh out/p1
(build) $ scp out/p1/chromium-p1.tar.gz pi@raspberrypi.local:
```

Extract it on RPi4 host and run `chrome` as follows:

```terminal
(pi) $ tar -xzvf chromium-p1.tar.gz
(pi) $ cd chromium-p1
(pi) $ EGL_PLATFORM=surfaceless ./chrome --force-system-compositor-mode
```

This finally brings you the Chromium browser on Ozone-GBM on RPi4, along with the mocked ChromiumOS desktop.

![Screenshot](https://dev-to-uploads.s3.amazonaws.com/i/ewpiierumu78c7cdzsu6.png)

## Current status and known problems

Before you actually test it, I have to say we shouldn't expect too much on it for now.  You'll soon realize it's quite buggy, unstable compared to the Raspbian desktop (Xorg) version, and has too many issues to use in production.

- Always too many flickers on frame updates.  It often gets quite disordered and shows previous frames again.  Compositor problem?
- WebGL is working, not perfect though.
  - Many of [three.js examples](https://threejs.org/examples) work, but the performance is terribly low with badly noticeable flickers.  [Broken rendering seen in Ozone-Dispmanx on RPi3](https://www.youtube.com/watch?v=61sEBhJa7k8) gets fixed.
  - You can play [AngryBots2 (Unity's demo)](https://beta.unity3d.com/jonas/AngryBots/), but it takes so long time to load the scene.
- For [YouTube](https://youtube.com), it often suffers intermittent interrupts of playbacks in the beginning of movies before it gets stably smooth.
- The mouse wheel input is not working.
- The keyboard input is also passed to the shell in the active VT console.  The current workaround would be to run on an unused VT console (`chvt 8`) from the remote shell.
- `content_shell` on Ozone-GBM cannot display anything, while the browser instance is running offscreen.  Apparently it's missing event handlers that should set up screens connected.

## Prebuilt binaries for testing

I've uploaded the archive of my build on OneDrive:

- https://1drv.ms/u/s!ArStVFUJcWgSjHOsjfgt21fAB_pD?e=KBP7Vg

Download chromium-p1.tar.gz and test it on your RPi4 if you have interest with Chromium on Ozone-GBM development.  Beware that it won't run on the latest kernel from Raspbian and the special settings is needed.  Read [the instruction](#rpi4-settings) in this article.

## Conclusions

Current Chromium on Ozone-GBM for RPi4 is experimental; it's full of issues and at limited performance, far from production use.  But you can say it's just immature and in the early stage of development, so there's a great margin to improve.

After deprecation of dispmanx, Ozone-GBM with [drm/v3d driver](https://www.kernel.org/doc/html/latest/gpu/v3d.html) would be in the right direction for Chromium of embedded use for RPi4, worth for us to make effort.

I've shared the first step guide for developing and testing Ozone-GBM for RPi4, along with prebuilt binaries.  I'll update this article as needed in future.  Any comments and suggestions to improve Ozone-GBM and this article are much appreciated!

## Acknowledgements

I thank people in [Raspberry Pi's graphics programming forum](https://www.raspberrypi.org/forums/viewforum.php?f=67) for sharing valuable information.