Skip to content

erlangsters/opengl-es-3.1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenGL ES 3.1 binding for the BEAM

Erlangsters Repository Supported Erlang/OTP Versions Current Version License Build Status Documentation Link

🚧 It's still in development and a first beta version will be available shortly. The master branch will be rewound!

This repository hosts the generated OpenGL ES 3.1 binding for the Erlang and Elixir programming language. Only the core profile functions are exposed and loading custom OpenGL functions is not supported.

Vertices = [1.0, -0.5, 42.0],

{ok, [Buffer]} = gl:gen_buffer(1).
gl:bind_buffer(array_buffer, Buffer).
gl:buffer_data(
  array_buffer,
  length(Vertices) * 4,
  <<<<Vertex:32/float-little>> || Vertex <- Vertices>>
  static_draw
).

It works exclusively with the EGL binding which will provide you with an OpenGL context and OpenGL surfaces to work with. Additionally, you might want to use the GLFW binding to provide you with a window.

Bindings for other version of OpenGL and OpenGL ES are available. Visit the Erlangsters organization page on Github to browse the list of available bindings.

Generated by the Erlangsters community and released under the MIT license.

Quick preview

All the bits put together (EGL, OpenGL and some optional GLFW because it's more fun with a window), here is how it looks like.

Display = egl:get_display(default_display).
{ok, {_, _}} = egl:initialize(Display).

egl:bind_api(opengl_es_api).

ConfigAttribs = [
    {surface_type, [window_bit]},
    {renderable_type, [opengl_bit]}
].
{ok, Configs} = egl:choose_config(Display, ConfigAttribs).

ContextAttribs = [{context_major_version, 3}].
{ok, Context} = egl:create_context(Display, Config, no_context, ContextAttribs).

{ok, Window} = glfw:create_window(640, 480, "Hello World!").
WindowHandle = glfw:window_egl_handle(Window).
{ok, Surface} = egl:create_window_surface(Display, Config, WindowHandle, []).

ok = egl:make_current(Display, Surface, Surface, Context).

% Here goes your OpenGL initialization code...
{ok, Version} = gl:get_string(?GL_VERSION).

loop_window(Display, Surface, Window, Version).

Here is how the loop function looks like.

loop_window(Display, Surface, Window) ->
  case glfw:window_should_close(Window) of
    true ->
      ok;
    false ->
      % Here goes your OpenGL rendering code...
      gl:clear(?GL_COLOR_BUFFER_BIT),

      egl:swap_buffers(Display, Surface),
      glfw:poll_events(Window),

      loop_window(Display, Surface, Window, Program, Vao, Counter+1)
  end.

Consult the OpenGL samples repository for more examples.

Working with the binding

To work with the binding, you will need to first hear about the "thread-safety" implications (coming from the underlying C language) and how the API is adjusted to match look and feel of the Erlang and Elixir languages.

Thread-safety

OpenGL is a C library that is not thread-safe -- each OpenGL function executes in a OpenGL contexts bound to a single OS thread at a time. However, the BEAM layers on top of OS threads (to provide the BEAM process primitive) and does not expose them.

How does it work then ?

The thread-safety problem is solved by the EGL binding (hence its strong dependency on it) and the solution well-explained in its documentation (which you should consult).

However, here is a short version of the story: think just like a BEAM process equates to an OS thread.

If you do that, all OpenGL code will work exactly like its counter-part in C.

Under the hood, the EGL binding executes the OpenGL calls in separate OS threads in a way that replicates the layout of the BEAM processes of your application.

API mapping

To make a low-level C API available in a higher-level Erlang/Elixir API, some transformations are unavoidable. To remain highly consistent, it strictly follows a set of mapping rules that are formally explained in the documentation of the OpenGL binding generator.

Most of the time, you will be able to intuitively understand how a piece of OpenGL code translates into its Erlang/Elixir counter-part. If not, the API reference from the generated in-source documentation also helps a great deal.

If it's still not enough, consult either the API mapping rules or the OpenGL samples.

Using the binding in your project

With the Rebar3 build system, add the following to the rebar.config file of your project.

{deps, [
  {egl, {git, "https://github.com/erlangsters/egl-1.5.git", {tag, "master"}}},
  {gl, {git, "https://github.com/erlangsters/opengl-es-3.1.git", {tag, "master"}}}
]}.

If you happen to use the Erlang.mk build system, then add the following to your Makefile.

BUILD_DEPS = egl gl
dep_egl = git https://github.com/erlangsters/egl-1.5 master
dep_gl = git https://github.com/erlangsters/opengl-es-3.1 master

In practice, you want to replace the branch "master" with a specific "tag" to avoid breaking your project if incompatible changes are made.

Generating the binding yourself

Like stated, this OpenGL binding is generated and the result is hosted in this repo for convenience of use. However, you may want to be able to re-produce the said result.

No problem, clone the OpenGL binding generator repository, compile the generator and generate the binding.

rebar3 escriptize
./bin/opengl_gen gles 3.1

It produces the gl.erl, gl.hrl and gl.c that can be directly included in your project. (Note that you'll need to configure your build system to produce a NIF library out of gl.c, of course.)