I was playing with OpenGL on my PC. After reaching a certain level, I wanted to see the same on my Android phone. Here are the insights.
I started my OpenGL journey by implementing simple applications. Here you can enjoy my first demo (;
That was pretty good for a start. If you would like something similar, then there are plenty of good educational materials. Some are free, others cost a few protein shakes. I like these two.
- V. Scott Gordon – Computer Graphics Programming in OpenGL with JAVA Second Edition
You might prefer a different one, depending on your language and particular library.
After having the basics done, I decided to try to run this on my Android phone. It’s Java, “write once, run everywhere”, right? My #1 wish was that this stays true for the application code. Let’s have a look on how well it went.
Eventually, it worked out, mostly. And I was able to satisfy my #1 wish, having single source code across all platforms. But it took me more time than I originally thought.
Note that it was much harder to find tutorials and good reading. The most useful were these two sources.
- Official Android OpenGL ES documentation.
- Dan Ginsburg – OpenGL ES 3.0 Programming Guide 2nd Edition.
Interesting issues appeared during the journey. I named put them into two categories: "Solvables" and "Careables". Solvables are the ones, which can be solved by the system architecture. On the other hand, careables can’t be entirely solved. You need to care well enough so your user won’t notice them.
JOGL VS Android OpenGL ES. These are two Java Open GL wrappers. They are very similar, but not exactly the same. So I ended up abstracting them and creating separate Tyracorn application containers. The application can then be launched in any container. This allows me to support Nintendo (make it fun for my lovely kids (; ) and other platforms later on.
The container pattern also turned out to be a great solution for unified handling application lifecycle, various input sources, and assets. And the best is that I can easily add drivers for various sensors, cameras, or robotic pizza restaurants.
There is an interesting difference inside the shader language. OpenGL ES doesn’t allow you to define a sampler array and then refer to its elements by anything which isn’t a constant. This effectively means that it’s not possible to write a loop to apply multiple textures. This led to a number of if statements, where each branch has the same code with a different texture.
Working across different screens. The main thing is that aspect ratio dramatically changes. If you tune everything for the landscape orientation, then the portrait one won’t work. I implemented a way that things like camera or UI components have the ability to decide their properties based on the display size. And everything can change during the runtime. This is not difficult to do, but it eats away a good chunk of your time.
Finally, let me mention the nuances of Java. As of today, Android doesn’t support all Java 8 features out of the box. There are two choices on how to deal with that. The first way is to introduce additional tools that can pre-process your Java 8+ code to work on Android.
The second way is to simply accept the Android limitations and write code according to that. I picked up the second way because I don’t like messing around with tools. At the same time, I really love easy to read standardized code. Therefore, I spend a lot of time to finding the code form which I like the best.
There are two major careables – precision and performance.
Precision is related to the z-buffer and floating-point numbers. When I first run my application, I discovered that my mobile phone uses lower precision than my PC. Interesting. The result is more noticeable z-fighting and shadow artifacts.
You might see other artifacts as well. In any case, you render objects close to each other. Typical advice on how to deal with these is about offsets and bias values. More advanced ones go into a way how to fake a higher precision. For example, split the scene into parts and render each of them with a separate z-buffer.
The first time tried to run the shadow light example, I was shocked by seeing something like 3fps while rendering a simple cube with shadow.
It took me a few days to discover that it was caused by two problems. The first problem was that I couldn’t specify empty color attachment during frame buffer creation in the OpenGL ES.
This didn’t make the program fail, but it made it run terribly slow and keep showing some error in the log. The second problem was related to mobile phone architecture.
Apparently battery life length is important, so they decided to use a different architecture to save some power. This lead to an extra shuffling in between memories which took time. The solution for this was to explicitly clear the shadow buffer right after binding, so the copying didn’t need to take place.
Although having all this in place, the performance on mobile is still much lower than on PC. For example, 3 shadow lights already cause noticeable slowness on the mobile. And when I implemented PCF to make smooth shadows, it was a disaster. So I decided to keep shadows ugly on mobile phones.
As a lesson, I plan to do these two things in the feature.
- Create separate versions of assets for different devices.
- Adjust scene funkiness based on the device.
Hope you enjoyed this one. If you would like to get the source code for all this and more, then please join me on Patreon.
I am already thinking about the next step. If you have anything you would like to see, then just let me know.
See you next time!