--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Services for graphical programs with shaders, attributes and uniforms
+module programs
+
+import display
+import matrix
+
+private import more_collections
+
+# `Uniform` or `Attribute` of a `GamnitProgram`
+class ShaderVariable
+
+ # The `GamnitProgram` to which `self` belongs
+ var program: GLProgram
+
+ # Name of `self` in the `program` source
+ var name: String
+
+ # Location of `self` in the compiled `program`
+ var location: Int
+
+ # Number of elements in this array (1 for scalars and more for vectors)
+ var size: Int
+
+ # Is `self` an active uniform or attribute in the `program`?
+ #
+ # If `false`, the variable may have been optimized out by the compiler.
+ fun is_active: Bool do return true
+
+ redef fun to_s do return "<{class_name} name:{name} location:{location} size:{size}"
+end
+
+# Inactive shader variable, either optimized out or simple absent from the program
+#
+# Returned by `GamnitProgram::uniforms` or `GamnitProgram::attributes` when
+# the uniform has not been identified as active by the driver.
+# Operations on instances of this class have no effects.
+#
+# Act as a compatibility when a program expect a uniform to exist even in
+# a context where the driver's compiler may have optimized it out.
+# You must be careful when receiving an `InactiveVariable` as it may also
+# silence real program errors, such type in variable name.
+abstract class InactiveVariable
+ super ShaderVariable
+
+ redef fun is_active do return false
+end
+
+# Shader attribute
+#
+# It will use either the `uniform` value or the data at `array_pointer` if
+# and only if `array_enabled`.
+class Attribute
+ super ShaderVariable
+
+ private var array_enabled_cache = false
+
+ # Is the array attribute enabled?
+ fun array_enabled: Bool do return array_enabled_cache
+
+ # Set whether to use the data at `array_pointer` over `uniform`.
+ fun array_enabled=(value: Bool)
+ do
+ if not is_active then return
+
+ glUseProgram program
+
+ self.array_enabled_cache = value
+ if value then
+ glEnableVertexAttribArray location
+ else glDisableVertexAttribArray location
+ end
+
+ # Define the `array` of vertex data
+ fun array(array: Array[Float], data_per_vertex: Int)
+ do
+ # TODO move this and native_float_array to a subclass specific to float
+
+ if not is_active then return
+
+ var native = native_float_array
+ if native == null or array.length > native.length then
+ if native != null then native.destroy
+ native = new GLfloatArray.from(array)
+ self.native_float_array = native
+ else
+ native.fill_from(array)
+ end
+
+ glVertexAttribPointer(location, data_per_vertex, gl_FLOAT, false, 0, native.native_array)
+ end
+
+ private var native_float_array: nullable GLfloatArray = null
+end
+
+# Shader attribute of GLSL type `float`
+class AttributeFloat
+ super Attribute
+
+ # Set the uniform value to use when the vertex array is disabled
+ fun uniform(x: Float) do if is_active then glVertexAttrib1f(location, x)
+end
+
+# Shader attribute of GLSL type `vec2`
+class AttributeVec2
+ super Attribute
+
+ # Set the uniform value to use when the vertex array is disabled
+ fun uniform(x, y: Float) do if is_active then glVertexAttrib2f(location, x, y)
+end
+
+# Shader attribute of GLSL type `vec3`
+class AttributeVec3
+ super Attribute
+
+ # Set the uniform value to use when the vertex array is disabled
+ fun uniform(x, y, z: Float) do if is_active then glVertexAttrib3f(location, x, y, z)
+end
+
+# Shader attribute of GLSL type `vec4`
+class AttributeVec4
+ super Attribute
+
+ # Set the uniform value to use when the vertex array is disabled
+ fun uniform(x, y, z, w: Float) do if is_active then glVertexAttrib4f(location, x, y, z, w)
+end
+
+# `Attribute` that does not exist or that has been optimized out
+class InactiveAttribute
+ super InactiveVariable
+ super AttributeFloat
+ super AttributeVec2
+ super AttributeVec3
+ super AttributeVec4
+end
+
+# Shader uniform
+class Uniform
+ super ShaderVariable
+
+ private fun uniform_1i(index, x: Int) `{ glUniform1i(index, x); `}
+end
+
+# Shader uniform of GLSL type `bool`
+class UniformBool
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(val: Bool) do uniform_1i(location, if val then 1 else 0)
+end
+
+# Shader uniform of GLSL type `vec4`
+class UniformFloat
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(x: Float) do glUniform1f(location, x)
+end
+
+# Shader uniform of GLSL type `vec4`
+class UniformVec2
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(x, y: Float) do glUniform2f(location, x, y)
+end
+
+# Shader uniform of GLSL type `vec4`
+class UniformVec3
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(x, y, z: Float) do glUniform3f(location, x, y, z)
+end
+
+# Shader uniform of GLSL type `vec4`
+class UniformVec4
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(x, y, z, w: Float) do glUniform4f(location, x, y, z, w)
+end
+
+# Shader uniform of GLSL type `sampler2D`
+class UniformSampler2D
+ super Uniform
+
+ # Set this uniform value
+ fun uniform(val: Int) do uniform_1i(location, val)
+end
+
+# Shader uniform of GLSL type `mat4`
+class UniformMat4
+ super Uniform
+
+ private var native_matrix_cache: nullable NativeGLfloatArray = null
+
+ # Set this uniform value
+ fun uniform(matrix: Matrix)
+ do
+ var native = native_matrix_cache
+ if native == null then
+ native = new NativeGLfloatArray.matrix
+ self.native_matrix_cache = native
+ end
+
+ matrix.fill_native(native)
+ uniform_matrix_4f(location, 1, false, native)
+ end
+
+ private fun uniform_matrix_4f(index, count: Int, transpose: Bool, data: NativeGLfloatArray) `{
+ glUniformMatrix4fv(index, count, transpose, data);
+ `}
+end
+
+# `Uniform` that does not exist or that has been optimized out
+class InactiveUniform
+ super InactiveVariable
+ super UniformBool
+ super UniformFloat
+ super UniformSampler2D
+ super UniformVec2
+ super UniformVec3
+ super UniformVec4
+ super UniformMat4
+
+ redef fun is_active do return false
+end
+
+# Gamnit shader
+abstract class Shader
+
+ # TODO add alternative init to load shaders from binary
+
+ # Index of this shader in OpenGL
+ private var gl_shader: GLShader is noinit
+
+ # Latest error raised by operations of this shader
+ var error: nullable Error = null
+
+ # Source code of this shader
+ var source: Text
+
+ # Low-level type of this shader
+ private fun gl_shader_type: GLShaderType is abstract
+
+ # Compile this shader and report any errors in the attribute `error`
+ fun compile
+ do
+ # Create
+ var gl_shader = glCreateShader(gl_shader_type)
+ if not glIsShader(gl_shader) then
+ self.error = new Error("Shader creation failed: {glGetError}")
+ return
+ end
+ self.gl_shader = gl_shader
+
+ glShaderSource(gl_shader, source.to_cstring)
+
+ # Compile
+ glCompileShader gl_shader
+ if not gl_shader.is_compiled then
+ self.error = new Error("Shader compilation failed: {glGetShaderInfoLog(gl_shader)}")
+ return
+ end
+
+ error = gammit_gl_error
+ end
+
+ # Has this shader been deleted?
+ var deleted = false
+
+ # Delete this shader and free its resources
+ fun delete
+ do
+ if deleted then return
+
+ glDeleteShader gl_shader
+ deleted = true
+ end
+end
+
+# Gamnit vertex shader
+class VertexShader
+ super Shader
+
+ redef fun gl_shader_type do return gl_VERTEX_SHADER
+end
+
+# Gamnit fragment shader
+class FragmentShader
+ super Shader
+
+ redef fun gl_shader_type do return gl_FRAGMENT_SHADER
+end
+
+# Gamnit graphical program
+#
+# Subclasses should implement both `vertex_shader` and `fragment_shader`.
+abstract class GamnitProgram
+
+ # Vertex shader to attach to this program
+ fun vertex_shader: VertexShader is abstract
+
+ # Fragment shader to attach to this program
+ fun fragment_shader: FragmentShader is abstract
+
+ # Index to the OpenGL ES program, set by `compile_and_link`
+ private var gl_program: nullable GLProgram = null
+
+ # Last error raised by `compile_and_link`
+ var error: nullable Error = null is protected writable
+
+ # Compile the shaders, and this program, then link and report any errors
+ fun compile_and_link
+ do
+ # Get an index
+ var gl_program = glCreateProgram
+ if not glIsProgram(gl_program) then
+ self.error = new Error("Program creation failed: {glGetError.to_s}")
+ return
+ end
+ self.gl_program = gl_program
+
+ # Vertex shader
+ var vertex_shader = vertex_shader
+ vertex_shader.compile
+ if vertex_shader.error != null then
+ self.error = vertex_shader.error
+ return
+ end
+
+ # Fragment shader
+ var fragment_shader = fragment_shader
+ fragment_shader.compile
+ if fragment_shader.error != null then
+ self.error = fragment_shader.error
+ return
+ end
+
+ # Attach shaders
+ glAttachShader(gl_program, vertex_shader.gl_shader)
+ glAttachShader(gl_program, fragment_shader.gl_shader)
+
+ # Catch any errors up to here
+ var error = gammit_gl_error
+ if error != null then
+ self.error = error
+ return
+ end
+
+ # Link
+ glLinkProgram gl_program
+ if not gl_program.is_linked then
+ self.error = new Error("Linking failed: {glGetProgramInfoLog(gl_program)}")
+ return
+ end
+
+ # Fill the attribute and uniform lists
+ var n_attribs = glGetProgramiv(gl_program, gl_ACTIVE_ATTRIBUTES)
+ for a in [0..n_attribs[ do
+ var name = gl_program.active_attrib_name(a)
+ var size = gl_program.active_attrib_size(a)
+ var typ = gl_program.active_attrib_type(a)
+ var location = gl_program.attrib_location(name)
+
+ # FIXME location may be invalid at this point because
+ # attrib_location does not work with truncated names,
+ # as returned by `active_attrib_name`.
+
+ var attribute
+ if typ == gl_FLOAT then
+ attribute = new AttributeFloat(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC2 then
+ attribute = new AttributeVec2(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC3 then
+ attribute = new AttributeVec3(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC4 then
+ attribute = new AttributeVec4(gl_program, name, location, size)
+ else
+ attribute = new Attribute(gl_program, name, location, size)
+ end
+ # TODO missing types
+ attributes[name] = attribute
+ end
+
+ var n_uniforms = glGetProgramiv(gl_program, gl_ACTIVE_UNIFORMS)
+ for a in [0..n_uniforms[ do
+
+ var name = gl_program.active_uniform_name(a)
+ var size = gl_program.active_uniform_size(a)
+ var typ = gl_program.active_uniform_type(a)
+ var location = gl_program.uniform_location(name)
+
+ var uniform
+ if typ == gl_BOOL then
+ uniform = new UniformBool(gl_program, name, location, size)
+ else if typ == gl_SAMPLER_2D then
+ uniform = new UniformSampler2D(gl_program, name, location, size)
+ else if typ == gl_FLOAT then
+ uniform = new UniformFloat(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC2 then
+ uniform = new UniformVec2(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC3 then
+ uniform = new UniformVec3(gl_program, name, location, size)
+ else if typ == gl_FLOAT_VEC4 then
+ uniform = new UniformVec4(gl_program, name, location, size)
+ else if typ == gl_FLOAT_MAT4 then
+ uniform = new UniformMat4(gl_program, name, location, size)
+ else
+ uniform = new Uniform(gl_program, name, location, size)
+ end
+ # TODO missing types
+ uniforms[name] = uniform
+ end
+ end
+
+ # Attributes of this program organized by name
+ #
+ # Active attributes are gathered at `compile_and_link`.
+ # Upon request, inactive attributes are returned as a new `InactiveAttribute`.
+ var attributes = new AttributeMap(self)
+
+ # Uniforms of this program organized by name
+ #
+ # Active uniforms are gathered at `compile_and_link`.
+ # Upon request, inactive attributes are returned as a new `InactiveUniform`.
+ var uniforms = new UniformMap(self)
+
+ # Notify the GPU to use this program
+ fun use
+ do
+ var gl_program = gl_program
+ assert gl_program != null # TODO error not compiled, or compile it
+ glUseProgram gl_program
+ end
+
+ # Has this program been deleted?
+ var deleted = false
+
+ # Delete this program if it has not already been deleted
+ fun delete
+ do
+ if deleted then return
+
+ glDeleteProgram gl_program.as(not null)
+ deleted = true
+ end
+end
+
+# Gamnit graphical program from the shaders source code
+class GamnitProgramFromSource
+ super GamnitProgram
+
+ # Source code of the vertex shader
+ fun vertex_shader_source: Text is abstract
+
+ redef var vertex_shader = new VertexShader(vertex_shader_source) is lazy
+
+ # Source code of the fragment shader
+ fun fragment_shader_source: Text is abstract
+
+ redef var fragment_shader = new FragmentShader(fragment_shader_source) is lazy
+end
+
+# Map to organize `ShaderVariable` instances by their name
+abstract class ShaderVariableMap[A: ShaderVariable]
+ super HashMap[String, A]
+
+ private var program: GamnitProgram
+
+ redef fun [](key)
+ do
+ # Alter the user specified name to fit the truncated name
+ var max_len = max_name_length - 1
+ if key isa Text and key.length > max_len then key = key.substring(0, max_len)
+ return super(key)
+ end
+
+ private fun max_name_length: Int is abstract
+end
+
+# Map to organize `Attribute` instances by their name
+class AttributeMap
+ super ShaderVariableMap[Attribute]
+
+ redef fun provide_default_value(key) do
+ return new InactiveAttribute(program.gl_program.as(not null), "", -1, 0)
+ end
+
+ redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_ATTRIBUTE_MAX_LENGTH)
+
+ redef fun [](key)
+ do
+ # Update the location of this attribute from the user specified name
+ var item = super
+ if key isa Text then item.location = program.gl_program.attrib_location(key.to_s)
+ return item
+ end
+end
+
+# Map to organize `Uniform` instances by their name
+class UniformMap
+ super ShaderVariableMap[Uniform]
+
+ redef fun provide_default_value(key) do
+ return new InactiveUniform(program.gl_program.as(not null), "", -1, 0)
+ end
+
+ redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_UNIFORM_MAX_LENGTH)
+
+ redef fun [](key)
+ do
+ var item = super
+ if key isa Text then item.location = program.gl_program.uniform_location(key.to_s)
+ return item
+ end
+end
+
+redef extern class NativeGLfloatArray
+
+ # Allocate a new matrix
+ new matrix `{ return malloc(4*4*sizeof(GLfloat)); `}
+
+ # Overwrite this matrix with the identity matrix
+ fun set_identity
+ do
+ for i in 4.times do
+ for j in 4.times do
+ matrix_set(i, j, if i == j then 1.0 else 0.0)
+ end
+ end
+ end
+
+ # Get the element at `x, y`
+ fun matrix_get(x, y: Int): Float `{ return self[y*4+x]; `}
+
+ # Set the element at `x, y`
+ fun matrix_set(x, y: Int, val: Float) `{ self[y*4+x] = val; `}
+end
+
+redef class Matrix
+ # Copy content of this matrix to a `NativeGLfloatArray`
+ fun fill_native(native: NativeGLfloatArray)
+ do
+ for i in width.times do
+ for j in height.times do
+ native.matrix_set(i, j, self[i, j])
+ end
+ end
+ end
+end
+
+private fun gammit_gl_error: nullable Error
+do
+ var gl_error = glGetError
+ if gl_error == gl_NO_ERROR then return null
+ return new Error("GL error: {gl_error}")
+end