There are a few things that would make our lives easier when working
with OpenGL in Elixir. I’m not going to bother wrapping the
wx
and gl
modules just to avoid using the
:atom
syntax, but there are a few things that will make our
lives easier.
Writing shaders in charlists embedded in Elixir is awkward and error prone, so let’s read them from files.
defmodule Shader do
defstruct id: nil
@type t :: %__MODULE__{
id: non_neg_integer(),
}
@spec new(Path.t(), Path.t()) :: Shader.t()
def new(vertex_path, fragment_path) do
= File.read!(vertex_path) <> <<0>>
vertex_source = File.read!(fragment_path) <> <<0>>
fragment_source
= :gl.createShader(:gl_const.gl_vertex_shader)
vertex_shader = :gl.createShader(:gl_const.gl_fragment_shader)
fragment_shader
:gl.shaderSource(vertex_shader, [vertex_source])
:gl.compileShader(vertex_shader)
= :gl.getShaderiv(vertex_shader, :gl_const.gl_compile_status)
success
unless success do
= :gl.getShaderInfoLog(vertex_shader, 512)
res IO.puts("vs_res: #{res}")
end
:gl.shaderSource(fragment_shader, [fragment_source])
:gl.compileShader(fragment_shader)
= :gl.getShaderiv(fragment_shader, :gl_const.gl_compile_status)
success
unless success do
= :gl.getShaderInfoLog(fragment_shader, 512)
res IO.puts("fs_res: #{res}")
end
= :gl.createProgram()
id :gl.attachShader(id, vertex_shader)
:gl.attachShader(id, fragment_shader)
:gl.linkProgram(id)
= :gl.getProgramiv(id, :gl_const.gl_link_status)
success
unless success do
= :gl.getProgramInfoLog(id, 512)
res IO.puts("link res: #{res}")
end
:gl.deleteShader(vertex_shader)
:gl.deleteShader(fragment_shader)
Shader{
%id: id,
}
end
end
This is all that you really need, but I’m going to make it a little
more Elixiry by adding use/1
and
uniform/3
:
def use(%Shader{id: id}) do
:gl.useProgram(id)
end
def uniform(%Shader{id: id}, name, true) do
= :gl.getUniformLocation(id, name)
location :gl.uniform1i(location, :gl_const.gl_true)
end
def uniform(%Shader{id: id}, name, false) do
= :gl.getUniformLocation(id, name)
location :gl.uniform1i(location, :gl_const.gl_false)
end
def uniform(%Shader{id: id}, name, val) when is_integer(val) do
= :gl.getUniformLocation(id, name)
location :gl.uniform1i(location, val)
end
def uniform(%Shader{id: id}, name, val) when is_float(val) do
= :gl.getUniformLocation(id, name)
location :gl.uniform1f(location, val)
end
We can add the rest of the functions, but we need to make some decisions about what the default number types should be (easy enough), and figure out how we’re going to deal with matrices and vectors. 1
One of the awkward things in the triangle example is this:
vertices = [
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0,
] |> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::float-native-size(32)>> end)
I haven’t decided the best way to deal with this. I think a macro is
nice, since the type (and size) will change, but then we can break
things a little too easily. We’ll use a function,
make_bits/3
for this:
defp make_bits(list, :float, bits \\ 32) do
list|> Enum.reduce(<<>>, fn el, acc -> acc <> <<el::float-native-size(bits)>> end)
end
For now, this is the only way we’re using it, so a shorter helper would be nice:
defp make_bits(list) do
(list, :float)
make_bitsend
foo↩︎