This is a follow-up post from an earlier guest post by Blu about OpenGL ES development on Chrome OS.
One can’t practice real-time rendering to disk files for long ‒ it’s just unnatural. So after checking that my habitual GLES tests work as intended on ChromeOS when rendering to an off-screen-buffer-subsequently-saved-to-a-PNG, the next step was to figure out a way how to show frames on screen at a palpable framerate, if possible.
Being as new to Chrome OS as the next guy, I had to start from scratch with ‘How to show EGL surfaces on screen fast’. In the comments section to the first article William Barath kindly mentioned that there was a wayland client library on Chromebrew, so I decided to pursue that as I had had (positive) prior experience with wayland.
Long story short, the established way on most platforms for connecting wayland to EGL (or vice versa) is to ask wayland/weston for an EGL-compatible window surface, and then pass that to EGL as a native window for EGL to set that up as the main framebuffer. Alas, it tuned out ChromeOS-native EGL does not work with the wayland surface format provided by the available wayland interfaces, so that low-friction path ended abruptly. BTW, that comment is not entirely correct ‒ the issue was not in the mismatch of native-window objects and EGL drawable configs ‒ that would produce error code EGL_BAD_MATCH; the issue was that EGL did not recognize the supplied native window as such ‒ error code EGL_BAD_NATIVE_WINDOW.
With the straight-forward EGL-on-wayland approach a no-go I had to look elsewhere. After inspecting the protocols provided by wayland server on ChromeOS, one particular protocol caught my attention: zwp_linux_dmabuf_v1. Turns out that protocol was originally designed as a wayland counterpart to a particular EGL extensions for taking linux DMA buffers and turning those into usable EGLImages.. How convenient! The name of the extension is EGL_EXT_image_dma_buf_import, and it is gaining popularity across various EGL-enabled platforms, ChromeOS included.
But then came the next obstacle ‒ the wayland client libs found in Chromebew did not expose any client-side zwp_linux_dmabuf_v1 functionality. For that I turned for help to the Chromebrew folks, but as it was not the most trivial of requests promising a quick resolution, I simultaneously continued with plan B ‒ tweak the GLES-to-PNG frameloop into a GLES-to-wayland-shared-mem-buffer one. Crude but effective. Moreover, that would be only a temporary solution anyway.
Here it needs mentioning that wayland has rather poorly documented client-side APIs, and independent researchers’ material like Jan Newmarch’s Programming Wayland Clients turned indispensable. Also Henrique Dante de Almeida’s hello-wayland tutorial proved a good starting point.
Long story short, the first outcome from this partially-manual-partially-automated plan-B presentation scheme looked like this:
Firstly, it really takes a bit of luck for such a crude presentation scheme to not be a total disaster. Things like mismatching pixel formats and disagreeing buffer strides could affect it greatly. Surely any of those could be fixed on-the-fly by the CPU, but in a situation where the CPU should not be having any business interfering in the first place, any additional CPU fix-ups could pile up to a serious real-time debt.
GLES’ glReadPixels API and wayland’s wl_shm_pool_create_buffer API’s offer a fair bit of overlap in pixel formats, so the goal there was to find a GLES pixel format of least overhead when converting the internal framebuffer format to the format expected by wayland, whatever that might be. Luckily, GLES’ glReadPixels(format = GL_RGBA, type = GL_UNSIGNED_BYTE) turned out fairly friction-less for RGBA8 internal formats, and matched perfectly to one of wayland’s SHM buffer formats ‒ WL_SHM_FORMAT_ABGR8888 ‒ spending precious CPU cycles in conversions avoided. The fact the GLES framebuffer showed up upside-down in the wayland SHM buffer was but a minor detail that could be fixed at no-cost in the GLES code (by flipping a sign in the projection matrix along with a GLES tri-winding control bit). Alternatively, I could simply show scenes without apparent up/down orientation ; ]
Another tricky point was coming up with a frameloop order of doing things that does not totally suck (™) but there I just had to follow the rule of thumb that any access to a framebuffer should not immediately follow the preceding drawing to that framebuffer. There also helped the fact I didn’t need to double-buffer EGL-side, as wayland’s surfaces are already double-buffered server-side.
That said, this frameloop is still extremely sensitive to CPU performance, so making more than a couple of draw calls (i.e. the major CPU cycle eaters in most GLES scenarios) per frame could result in notable diminishes in framerate.
But most importantly I can use my Chromebook for GLES development now ‒ prototyping shaders, tuning math routines ‒ all things I otherwise expect from my everyday notebooks. With some luck this quick’n’dirty EGL/wayland patchwork could develop into a proper zero-CPU-overhead frameloop suitable for more complex applications /fingers crossed
BTW, as an unexpected bonus now I can see the machine-clock estimates of GLSL shaders ‒ apparently a standard debug feature of Imagination Technologies’ shader compiler found on my Chromebook. Neat.
Jean-Luc started CNX Software in 2010 as a part-time endeavor, before quitting his job as a software engineering manager, and starting to write daily news, and reviews full time later in 2011.
3 Replies to “Self-hosted GLES on ChromeOS, part two”
Glad to see it worked out for you without having to resort to libdrm!
Indeed, thanks for the tip! The saga is far from being over, though, given I know now what needs to be done for proper zero-overhead frameloop on EGL + wayland @chromeos. After all, if my MT8163 ubuntu tablet can do the same GLES code in 1200p at 60 fps, so should my MT8173 chromebook! ; )
Update: proper dmabuf-based frameloop now: 4% cpu load on a single A72 core (it was 30+% on my chromebook before) — new frameloop is in branch ‘phase_two’. But it did take a tiny bit of libdrm usage ; )