First off, thank you for creating this incredible Go library! I've recently discovered it and I'm a huge fan! I really like the approach of using OpenGL for platform-independent, high performance UI rendering, since it allows one to stay general and not to be constrained by each platform's UI toolkits. I've experimented with OpenGL UIs here.
Introduction
Now, the description of this project says:
GXUI - A Go cross platform UI library.
Currently, it is cross platform in that it will run and look/behave the same on OS X, Linux and Windows. However, with relatively little work, it seems it could be expanded to support more platforms: mobile and perhaps even web.
First, let me be a little imprecise and describe OpenGL APIs in this way:
- OpenGL 3.2 Core - "Largest" OpenGL API available. Available on most desktop platforms (OS X, Linux, Windows).
- OpenGL ES 2.0 - Roughly speaking, a subset of OpenGL API (with minor deviations). Available on most mobile platforms (iOS, Android).
- WebGL 1.0 - Roughly speaking, a subset of OpenGL ES 2.0 (with minor deviations). Available in all major browsers (Chrome, Safari, Firefox, Opera, IE).
OpenGL 3.2 Core API is currently used in gxui
[1]. But with a few changes (outlined in considerations section), it's possible to reduce what it used so that it can mapped to WebGL API.
I wanted to see what it would take to get gxui
to run in browsers (and see how it'd perform), so I made a quick little prototype tonight in just a few hours. Some screenshots:



Demo
Here's a demo you can try in your browser (no gzip compression on served assets, sorry):
http://dmitri.shuralyov.com/projects/gxui-tree/index.html
Approaches
There are two different approaches to supporting additional platforms that I can see.
First approach is to create additional drivers, similar to the existing gl
one. This seems to be suggested in related issue #45.
Another approach, which is the one I've used so far, is a little different.
Since the three OpenGL APIs are similar enough, it's possible to create a Go package that has a single OpenGL-like API, but using build tags, it can have three different backend implementations depending on what is available on the given platform:
- OpenGL 2.1 or 3.2 Core, etc., backend on desktops.
- OpenGL ES backend on mobile devices.
- WebGL backend in browser.
The github.com/shurcooL/gogl
package used in my quick prototype is an example of that. It only has OpenGL 2.1 and WebGL backend implementations (incomplete), but adding OpenGL ES one would be trivial since it's a superset of WebGL. It's a WIP package with unpolished API, but I think the general approach is solid and can be turned into a production ready package.
Considerations
Here are some implementation details of the changes I needed to do to make gxui
samples run in the browser.
-
glPolygonMode
is not available in WebGL (since its implementation is very inefficient; a shame because it's useful for debugging). I commented it out since it was just a debugging feature.
-
Index buffers with uint32
types were used. It seems my WebGL implementation (latest stable Chrome on OS X) did not support that type, so I dropped it down to uint16
which worked well.
-
My WebGL implementation seemed not to support gl.UniformMatrix3fv
calls with transpose equal to true
, so I had to transpose the matrix before calling the gl func. I'm guessing the WebGL implementation opts to support fewer choices for performance reasons. It'd be better to avoid needless conversions between one format to the other when rendering.
-
WebGL fragment shaders require precision to be set, so I added this to all fragment shaders to allow them to compile under all OpenGL/WebGL versions:
#ifdef GL_ES
precision lowp float;
#endif
-
Previous commits read the font directly from disk via ioutil.ReadFile
. This can't work on all other platforms, so it's better to read from a virtual filesystem and each platform can provide that. It's no longer a problem since latest version embeds the font in code.
-
My existing gogl
package happens to use a higher level-style API (similar to x/mobile/gl
) with types like *Texture
instead of uint32
, and it favors using int
over int32
or uint32
the way the low-level C-style github.com/go-gl/gl/...
packages do. This is a design decision that is orthogonal to all other API changes, but it required me to make some changes like:
-framebuffer uint32
-texture uint32
+framebuffer *gogl.Framebuffer
+texture *gogl.Texture
-gl.DeleteFramebuffers(1, &f.framebuffer)
-gl.DeleteTextures(1, &f.texture)
-f.framebuffer = 0
-f.texture = 0
+gl.DeleteFramebuffer(f.framebuffer)
+gl.DeleteTexture(f.texture)
+f.framebuffer = nil
+f.texture = nil
-gl.Viewport(0, 0, int32(fw), int32(fh))
-gl.Scissor(0, 0, int32(fw), int32(fh))
+gl.Viewport(0, 0, fw, fh)
+gl.Scissor(0, 0, fw, fh)
-
Consts like gl.TRIANGLE_STRIP
are not const on all platforms. For example, to get that value in a browser, you either need to create a WebGLContext first (which can't be done at const-time), or just hardcode the const value from spec. I went with the latter for now.
You can see the entire code change I made at https://github.com/google/gxui/commit/469940010c8be6abd4feb535ba14a8b4e707720d. Keep in mind it's a quick prototype to test what it would take, and not a fully finished and polished version.
The performance of the web version (with Go compiled to JavaScript via GopherJS) is not as good as the desktop version with native Go. I am seeing around 20 fps. This is the result I got after doing the minimum work required to get samples to run without any profiling or optimizations. I am sure the performance can be improved if time is spent on it.
Conclusion
With just a few changes (do not use transpose true
, use uint16
type for index buffers, etc.) it is possible to avoid using OpenGL-only features that are not available in OpenGL ES and WebGL. Doing that seems like a good idea as it enables gxui
to be more cross platform.
This issue is to demonstrate what I've created. I'd love to hear feedback and thoughts. If you are interested, I'd love to help out with adding support for mobile and/or web and taking this prototype further.
[1] The imported package is v3.2-core/gl
, so that's the API used, but since context version glfw hints are not set, it creates an OpenGL 2.1 context. Add this snippet after gl.Init()
call to confirm:
{
var samples int32
gl.GetIntegerv(gl.SAMPLES, &samples)
fmt.Printf("OpenGL %s %s %s; %s; %v samples.\n", gl.GoStr(gl.GetString(gl.VENDOR)), gl.GoStr(gl.GetString(gl.RENDERER)), gl.GoStr(gl.GetString(gl.VERSION)),
gl.GoStr(gl.GetString(gl.SHADING_LANGUAGE_VERSION)), samples)
}
On my Mac, I got OpenGL ATI Technologies Inc. AMD Radeon HD 6770M OpenGL Engine 2.1 ATI-1.30.5; 1.20; 4 samples.
. OpenGL 2.1.