Warning
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 4.6 binding for the Erlang and Elixir programming language. Only the core profile functions are exposed and loading custom OpenGL functions is not supported.
{ok, Version} = gl:get_string(version).
io:format("OpenGL version: ~p~n", [Version]).
{:ok, version} = :gl.get_string(:version)
IO.puts("OpenGL version: #{version}")
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 organization page of the Erlangsters community on Github to browse the list of available bindings.
Generated by the Erlangsters community and released under the MIT license.
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).
ConfigAttribs = [
{surface_type, [window_bit]},
{renderable_type, [opengl_bit]}
].
{ok, Configs} = egl:choose_config(Display, ConfigAttribs).
ContextAttribs = [
{context_major_version, 4},
{context_minor_version, 6}
],
egl:bind_api(opengl_api).
{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...
Vertices = [
0.0, 0.5, 0.0, 1.0, 0.0, 0.0,
-0.5, -0.5, 0.0, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 0.0, 1.0
].
{ok, [Buffer]} = gl:gen_buffers(1),
gl:bind_buffer(array_buffer, Buffer),
gl:buffer_data(
array_buffer,
length(Vertices) * 4,
<<<<Vertex:32/float-little>> || Vertex <- Vertices>>,
static_draw
),
% ...
loop_window(Display, Surface, Window).
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([color_buffer_bit]),
% ...
egl:swap_buffers(Display, Surface),
glfw:poll_events(Window),
handle_events(Window),
loop_window(Display, Surface, Window)
end.
Here is how the event handling function looks like.
handle_events(Window) ->
receive
#glfw_window_size{size={Width, Height}} ->
gl:viewport(0, 0, Width, Height),
handle_events(Window)
after 0 ->
ok
end.
Fore more examples, consult the OpenGL samples repository which contains several mini-programs written in C, Erlang and Elixir.
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 is well-explained in its documentation (which you should consult).
However, here is a short version of the story: it works just like if a BEAM process is equal to an OS thread.
If you think like this, 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 will clarify the rest.
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-4.6.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-4.6 master
In practice, you want to replace the branch "master" with a specific "tag" to avoid breaking your project if incompatible changes are made.
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 gl 4.6
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.)