1 # This file is part of NIT (http://www.nitlanguage.org).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Services for graphical programs with shaders, attributes and uniforms
21 private import more_collections
23 # `Uniform` or `Attribute` of a `GamnitProgram`
26 # The `GamnitProgram` to which `self` belongs
27 var program
: GLProgram
29 # Name of `self` in the `program` source
32 # Location of `self` in the compiled `program`
35 # Number of elements in this array (1 for scalars and more for vectors)
38 # Is `self` an active uniform or attribute in the `program`?
40 # If `false`, the variable may have been optimized out by the compiler.
41 fun is_active
: Bool do return true
43 redef fun to_s
do return "<{class_name} name:{name} location:{location} size:{size}"
46 # Inactive shader variable, either optimized out or simple absent from the program
48 # Returned by `GamnitProgram::uniforms` or `GamnitProgram::attributes` when
49 # the uniform has not been identified as active by the driver.
50 # Operations on instances of this class have no effects.
52 # Act as a compatibility when a program expect a uniform to exist even in
53 # a context where the driver's compiler may have optimized it out.
54 # You must be careful when receiving an `InactiveVariable` as it may also
55 # silence real program errors, such type in variable name.
56 abstract class InactiveVariable
59 redef fun is_active
do return false
64 # It will use either the `uniform` value or the data at `array_pointer` if
65 # and only if `array_enabled`.
69 private var array_enabled_cache
= false
71 # Is the array attribute enabled?
72 fun array_enabled
: Bool do return array_enabled_cache
74 # Set whether to use the data at `array_pointer` over `uniform`.
75 fun array_enabled
=(value
: Bool)
77 if not is_active
then return
81 self.array_enabled_cache
= value
83 glEnableVertexAttribArray location
84 else glDisableVertexAttribArray location
87 # Define the `array` of vertex data
88 fun array
(array
: Array[Float], data_per_vertex
: Int)
90 # TODO move this and native_float_array to a subclass specific to float
92 if not is_active
then return
94 var native
= native_float_array
95 if native
== null or array
.length
> native
.length
then
96 if native
!= null then native
.destroy
97 native
= new GLfloatArray.from
(array
)
98 self.native_float_array
= native
100 native
.fill_from
(array
)
103 glVertexAttribPointer
(location
, data_per_vertex
, gl_FLOAT
, false, 0, native
.native_array
)
106 private var native_float_array
: nullable GLfloatArray = null
109 # Shader attribute of GLSL type `float`
113 # Set the uniform value to use when the vertex array is disabled
114 fun uniform
(x
: Float) do if is_active
then glVertexAttrib1f
(location
, x
)
117 # Shader attribute of GLSL type `vec2`
121 # Set the uniform value to use when the vertex array is disabled
122 fun uniform
(x
, y
: Float) do if is_active
then glVertexAttrib2f
(location
, x
, y
)
125 # Shader attribute of GLSL type `vec3`
129 # Set the uniform value to use when the vertex array is disabled
130 fun uniform
(x
, y
, z
: Float) do if is_active
then glVertexAttrib3f
(location
, x
, y
, z
)
133 # Shader attribute of GLSL type `vec4`
137 # Set the uniform value to use when the vertex array is disabled
138 fun uniform
(x
, y
, z
, w
: Float) do if is_active
then glVertexAttrib4f
(location
, x
, y
, z
, w
)
141 # `Attribute` that does not exist or that has been optimized out
142 class InactiveAttribute
143 super InactiveVariable
154 private fun uniform_1i
(index
, x
: Int) `{ glUniform1i(index, x); `}
157 # Shader uniform of GLSL type `bool
`
161 # Set this uniform value
162 fun uniform(val: Bool) do uniform_1i(location, if val then 1 else 0)
165 # Shader uniform of GLSL type `vec4
`
169 # Set this uniform value
170 fun uniform(x: Float) do glUniform1f(location, x)
173 # Shader uniform of GLSL type `vec4
`
177 # Set this uniform value
178 fun uniform(x, y: Float) do glUniform2f(location, x, y)
181 # Shader uniform of GLSL type `vec4
`
185 # Set this uniform value
186 fun uniform(x, y, z: Float) do glUniform3f(location, x, y, z)
189 # Shader uniform of GLSL type `vec4
`
193 # Set this uniform value
194 fun uniform(x, y, z, w: Float) do glUniform4f(location, x, y, z, w)
197 # Shader uniform of GLSL type `sampler2D
`
198 class UniformSampler2D
201 # Set this uniform value
202 fun uniform(val: Int) do uniform_1i(location, val)
205 # Shader uniform of GLSL type `mat4
`
209 private var native_matrix_cache: nullable NativeGLfloatArray = null
211 # Set this uniform value
212 fun uniform(matrix: Matrix)
214 var native = native_matrix_cache
215 if native == null then
216 native = new NativeGLfloatArray.matrix
217 self.native_matrix_cache = native
220 matrix.fill_native(native)
221 uniform_matrix_4f(location, 1, false, native)
224 private fun uniform_matrix_4f(index, count: Int, transpose: Bool, data: NativeGLfloatArray) `{
225 glUniformMatrix4fv
(index
, count
, transpose
, data
);
229 # `Uniform` that does not exist or that has been optimized out
230 class InactiveUniform
231 super InactiveVariable
234 super UniformSampler2D
240 redef fun is_active do return false
244 abstract class Shader
246 # TODO add alternative init to load shaders from binary
248 # Index of this shader in OpenGL
249 private var gl_shader: GLShader is noinit
251 # Latest error raised by operations of this shader
252 var error: nullable Error = null
254 # Source code of this shader
257 # Low-level type of this shader
258 private fun gl_shader_type: GLShaderType is abstract
260 # Compile this shader and report any errors in the attribute `error
`
264 var gl_shader = glCreateShader(gl_shader_type)
265 if not glIsShader(gl_shader) then
266 self.error = new Error("Shader creation failed: {glGetError}")
269 self.gl_shader = gl_shader
271 glShaderSource(gl_shader, source.to_cstring)
274 glCompileShader gl_shader
275 if not gl_shader.is_compiled then
276 self.error = new Error("Shader compilation failed: {glGetShaderInfoLog(gl_shader)}")
280 error = gammit_gl_error
283 # Has this shader been deleted?
286 # Delete this shader and free its resources
289 if deleted then return
291 glDeleteShader gl_shader
296 # Gamnit vertex shader
300 redef fun gl_shader_type do return gl_VERTEX_SHADER
303 # Gamnit fragment shader
307 redef fun gl_shader_type do return gl_FRAGMENT_SHADER
310 # Gamnit graphical program
312 # Subclasses should implement both `vertex_shader
` and `fragment_shader
`.
313 abstract class GamnitProgram
315 # Vertex shader to attach to this program
316 fun vertex_shader: VertexShader is abstract
318 # Fragment shader to attach to this program
319 fun fragment_shader: FragmentShader is abstract
321 # Index to the OpenGL ES program, set by `compile_and_link
`
322 private var gl_program: nullable GLProgram = null
324 # Last error raised by `compile_and_link
`
325 var error: nullable Error = null is protected writable
327 # Compile the shaders, and this program, then link and report any errors
331 var gl_program = glCreateProgram
332 if not glIsProgram(gl_program) then
333 self.error = new Error("Program creation failed: {glGetError.to_s}")
336 self.gl_program = gl_program
339 var vertex_shader = vertex_shader
340 vertex_shader.compile
341 if vertex_shader.error != null then
342 self.error = vertex_shader.error
347 var fragment_shader = fragment_shader
348 fragment_shader.compile
349 if fragment_shader.error != null then
350 self.error = fragment_shader.error
355 glAttachShader(gl_program, vertex_shader.gl_shader)
356 glAttachShader(gl_program, fragment_shader.gl_shader)
358 # Catch any errors up to here
359 var error = gammit_gl_error
360 if error != null then
366 glLinkProgram gl_program
367 if not gl_program.is_linked then
368 self.error = new Error("Linking failed: {glGetProgramInfoLog(gl_program)}")
372 # Fill the attribute and uniform lists
373 var n_attribs = glGetProgramiv(gl_program, gl_ACTIVE_ATTRIBUTES)
374 for a in [0..n_attribs[ do
375 var name = gl_program.active_attrib_name(a)
376 var size = gl_program.active_attrib_size(a)
377 var typ = gl_program.active_attrib_type(a)
378 var location = gl_program.attrib_location(name)
380 # FIXME location may be invalid at this point because
381 # attrib_location does not work with truncated names,
382 # as returned by `active_attrib_name
`.
385 if typ == gl_FLOAT then
386 attribute = new AttributeFloat(gl_program, name, location, size)
387 else if typ == gl_FLOAT_VEC2 then
388 attribute = new AttributeVec2(gl_program, name, location, size)
389 else if typ == gl_FLOAT_VEC3 then
390 attribute = new AttributeVec3(gl_program, name, location, size)
391 else if typ == gl_FLOAT_VEC4 then
392 attribute = new AttributeVec4(gl_program, name, location, size)
394 attribute = new Attribute(gl_program, name, location, size)
397 attributes[name] = attribute
400 var n_uniforms = glGetProgramiv(gl_program, gl_ACTIVE_UNIFORMS)
401 for a in [0..n_uniforms[ do
403 var name = gl_program.active_uniform_name(a)
404 var size = gl_program.active_uniform_size(a)
405 var typ = gl_program.active_uniform_type(a)
406 var location = gl_program.uniform_location(name)
409 if typ == gl_BOOL then
410 uniform = new UniformBool(gl_program, name, location, size)
411 else if typ == gl_SAMPLER_2D then
412 uniform = new UniformSampler2D(gl_program, name, location, size)
413 else if typ == gl_FLOAT then
414 uniform = new UniformFloat(gl_program, name, location, size)
415 else if typ == gl_FLOAT_VEC2 then
416 uniform = new UniformVec2(gl_program, name, location, size)
417 else if typ == gl_FLOAT_VEC3 then
418 uniform = new UniformVec3(gl_program, name, location, size)
419 else if typ == gl_FLOAT_VEC4 then
420 uniform = new UniformVec4(gl_program, name, location, size)
421 else if typ == gl_FLOAT_MAT4 then
422 uniform = new UniformMat4(gl_program, name, location, size)
424 uniform = new Uniform(gl_program, name, location, size)
427 uniforms[name] = uniform
431 # Diagnose possible problems with the shaders of the program
433 # Lists to the console inactive uniforms and attributes.
434 # These may not be problematic but they can help to debug the program.
437 if gl_program == null then compile_and_link
439 print "# Diagnose {class_name}"
440 for k,v in uniforms do
441 if not v.is_active then print "* Uniform {v.name} is inactive"
443 for k,v in attributes do
444 if not v.is_active then print "* Attribute {v.name} is inactive"
448 # Attributes of this program organized by name
450 # Active attributes are gathered at `compile_and_link
`.
451 # Upon request, inactive attributes are returned as a new `InactiveAttribute`.
452 var attributes = new AttributeMap(self)
454 # Uniforms of this program organized by name
456 # Active uniforms are gathered at `compile_and_link
`.
457 # Upon request, inactive attributes are returned as a new `InactiveUniform`.
458 var uniforms = new UniformMap(self)
460 # Notify the GPU to use this program
463 var gl_program = gl_program
464 assert gl_program != null # TODO error not compiled, or compile it
465 glUseProgram gl_program
468 # Has this program been deleted?
471 # Delete this program if it has not already been deleted
474 if deleted then return
476 glDeleteProgram gl_program.as(not null)
481 # Gamnit graphical program from the shaders source code
482 class GamnitProgramFromSource
485 # Source code of the vertex shader
486 fun vertex_shader_source: Text is abstract
488 redef var vertex_shader = new VertexShader(vertex_shader_source) is lazy
490 # Source code of the fragment shader
491 fun fragment_shader_source: Text is abstract
493 redef var fragment_shader = new FragmentShader(fragment_shader_source) is lazy
496 # Map to organize `ShaderVariable` instances by their name
497 abstract class ShaderVariableMap[A: ShaderVariable]
498 super HashMap[String, A]
500 private var program: GamnitProgram
504 # Alter the user specified name to fit the truncated name
505 var max_len = max_name_length - 1
506 if key isa Text and key.length > max_len then key = key.substring(0, max_len)
510 private fun max_name_length: Int is abstract
513 # Map to organize `Attribute` instances by their name
515 super ShaderVariableMap[Attribute]
517 redef fun provide_default_value(key) do
518 return new InactiveAttribute(program.gl_program.as(not null), "", -1, 0)
521 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_ATTRIBUTE_MAX_LENGTH)
525 # Update the location of this attribute from the user specified name
527 if key isa Text then item.location = program.gl_program.attrib_location(key.to_s)
532 # Map to organize `Uniform` instances by their name
534 super ShaderVariableMap[Uniform]
536 redef fun provide_default_value(key) do
537 return new InactiveUniform(program.gl_program.as(not null), "", -1, 0)
540 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_UNIFORM_MAX_LENGTH)
545 if key isa Text then item.location = program.gl_program.uniform_location(key.to_s)
550 redef extern class NativeGLfloatArray
552 # Allocate a new matrix
553 new matrix `{ return malloc(4*4*sizeof(GLfloat)); `}
555 # Overwrite this matrix with the identity matrix
560 matrix_set
(i
, j
, if i
== j
then 1.0 else 0.0)
565 # Get the element at `x, y`
566 fun matrix_get
(x
, y
: Int): Float `{ return self[y*4+x]; `}
568 # Set the element at `x
, y
`
569 fun matrix_set(x, y: Int, val: Float) `{ self[y*4+x] = val; `}
573 # Copy content of this matrix to a `NativeGLfloatArray`
574 fun fill_native
(native
: NativeGLfloatArray)
576 for i
in width
.times
do
577 for j
in height
.times
do
578 native
.matrix_set
(i
, j
, self[i
, j
])
584 private fun gammit_gl_error
: nullable Error
586 var gl_error
= glGetError
587 if gl_error
== gl_NO_ERROR
then return null
588 return new Error("GL error: {gl_error}")