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
.finalize
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 `int
`
169 # Set this uniform value
170 fun uniform(val: Int) do uniform_1i(location, val)
173 # Shader uniform of GLSL type `vec4
`
177 # Set this uniform value
178 fun uniform(x: Float) do glUniform1f(location, x)
181 # Shader uniform of GLSL type `vec4
`
185 # Set this uniform value
186 fun uniform(x, y: Float) do glUniform2f(location, x, y)
189 # Shader uniform of GLSL type `vec4
`
193 # Set this uniform value
194 fun uniform(x, y, z: Float) do glUniform3f(location, x, y, z)
197 # Shader uniform of GLSL type `vec4
`
201 # Set this uniform value
202 fun uniform(x, y, z, w: Float) do glUniform4f(location, x, y, z, w)
205 # Shader uniform of GLSL type `sampler2D
`
206 class UniformSampler2D
209 # Set this uniform value
210 fun uniform(val: Int) do uniform_1i(location, val)
213 # Shader uniform of GLSL type `mat4
`
217 private var native_matrix_cache: nullable NativeGLfloatArray = null
219 # Set this uniform value
220 fun uniform(matrix: Matrix)
222 var native = native_matrix_cache
223 if native == null then
224 native = new NativeGLfloatArray.matrix
225 self.native_matrix_cache = native
228 matrix.fill_native(native)
229 uniform_matrix_4f(location, 1, false, native)
232 private fun uniform_matrix_4f(index, count: Int, transpose: Bool, data: NativeGLfloatArray) `{
233 glUniformMatrix4fv
(index
, count
, transpose
, data
);
237 # `Uniform` that does not exist or that has been optimized out
238 class InactiveUniform
239 super InactiveVariable
243 super UniformSampler2D
249 redef fun is_active do return false
253 abstract class Shader
255 # TODO add alternative init to load shaders from binary
257 # Index of this shader in OpenGL
258 private var gl_shader: GLShader is noinit
260 # Latest error raised by operations of this shader
261 var error: nullable Error = null
263 # Source code of this shader
266 # Low-level type of this shader
267 private fun gl_shader_type: GLShaderType is abstract
269 # Compile this shader and report any errors in the attribute `error
`
273 var gl_shader = glCreateShader(gl_shader_type)
274 if not glIsShader(gl_shader) then
275 self.error = new Error("Shader creation failed: {glGetError}")
278 self.gl_shader = gl_shader
280 glShaderSource(gl_shader, source.to_cstring)
283 glCompileShader gl_shader
284 if not gl_shader.is_compiled then
285 self.error = new Error("Shader compilation failed: {glGetShaderInfoLog(gl_shader)}")
289 error = gammit_gl_error
292 # Has this shader been deleted?
295 # Delete this shader and free its resources
298 if deleted then return
300 glDeleteShader gl_shader
305 # Gamnit vertex shader
309 redef fun gl_shader_type do return gl_VERTEX_SHADER
312 # Gamnit fragment shader
316 redef fun gl_shader_type do return gl_FRAGMENT_SHADER
319 # Gamnit graphical program
321 # Subclasses should implement both `vertex_shader
` and `fragment_shader
`.
322 abstract class GamnitProgram
324 # Vertex shader to attach to this program
325 fun vertex_shader: VertexShader is abstract
327 # Fragment shader to attach to this program
328 fun fragment_shader: FragmentShader is abstract
330 # Index to the OpenGL ES program, set by `compile_and_link
`
331 private var gl_program: nullable GLProgram = null
333 # Last error raised by `compile_and_link
`
334 var error: nullable Error = null is protected writable
336 # Compile the shaders, and this program, then link and report any errors
340 var gl_program = glCreateProgram
341 if not glIsProgram(gl_program) then
342 self.error = new Error("Program creation failed: {glGetError.to_s}")
345 self.gl_program = gl_program
348 var vertex_shader = vertex_shader
349 vertex_shader.compile
350 if vertex_shader.error != null then
351 self.error = vertex_shader.error
356 var fragment_shader = fragment_shader
357 fragment_shader.compile
358 if fragment_shader.error != null then
359 self.error = fragment_shader.error
364 glAttachShader(gl_program, vertex_shader.gl_shader)
365 glAttachShader(gl_program, fragment_shader.gl_shader)
367 # Catch any errors up to here
368 var error = gammit_gl_error
369 if error != null then
375 glLinkProgram gl_program
376 if not gl_program.is_linked then
377 self.error = new Error("Linking failed: {glGetProgramInfoLog(gl_program)}")
381 # Fill the attribute and uniform lists
382 var n_attribs = glGetProgramiv(gl_program, gl_ACTIVE_ATTRIBUTES)
383 for a in [0..n_attribs[ do
384 var name = gl_program.active_attrib_name(a)
385 var size = gl_program.active_attrib_size(a)
386 var typ = gl_program.active_attrib_type(a)
387 var location = gl_program.attrib_location(name)
389 # FIXME location may be invalid at this point because
390 # attrib_location does not work with truncated names,
391 # as returned by `active_attrib_name
`.
394 if typ == gl_FLOAT then
395 attribute = new AttributeFloat(gl_program, name, location, size)
396 else if typ == gl_FLOAT_VEC2 then
397 attribute = new AttributeVec2(gl_program, name, location, size)
398 else if typ == gl_FLOAT_VEC3 then
399 attribute = new AttributeVec3(gl_program, name, location, size)
400 else if typ == gl_FLOAT_VEC4 then
401 attribute = new AttributeVec4(gl_program, name, location, size)
403 attribute = new Attribute(gl_program, name, location, size)
406 attributes[name] = attribute
409 var n_uniforms = glGetProgramiv(gl_program, gl_ACTIVE_UNIFORMS)
410 for a in [0..n_uniforms[ do
412 var name = gl_program.active_uniform_name(a)
413 var size = gl_program.active_uniform_size(a)
414 var typ = gl_program.active_uniform_type(a)
415 var location = gl_program.uniform_location(name)
418 if typ == gl_BOOL then
419 uniform = new UniformBool(gl_program, name, location, size)
420 else if typ == gl_INT then
421 uniform = new UniformInt(gl_program, name, location, size)
422 else if typ == gl_SAMPLER_2D then
423 uniform = new UniformSampler2D(gl_program, name, location, size)
424 else if typ == gl_FLOAT then
425 uniform = new UniformFloat(gl_program, name, location, size)
426 else if typ == gl_FLOAT_VEC2 then
427 uniform = new UniformVec2(gl_program, name, location, size)
428 else if typ == gl_FLOAT_VEC3 then
429 uniform = new UniformVec3(gl_program, name, location, size)
430 else if typ == gl_FLOAT_VEC4 then
431 uniform = new UniformVec4(gl_program, name, location, size)
432 else if typ == gl_FLOAT_MAT4 then
433 uniform = new UniformMat4(gl_program, name, location, size)
435 uniform = new Uniform(gl_program, name, location, size)
438 uniforms[name] = uniform
442 # Diagnose possible problems with the shaders of the program
444 # Lists to the console inactive uniforms and attributes.
445 # These may not be problematic but they can help to debug the program.
448 if gl_program == null then compile_and_link
450 print "# Diagnose {class_name}"
451 for k,v in uniforms do
452 if not v.is_active then print "* Uniform {v.name} is inactive"
454 for k,v in attributes do
455 if not v.is_active then print "* Attribute {v.name} is inactive"
459 # Attributes of this program organized by name
461 # Active attributes are gathered at `compile_and_link
`.
462 # Upon request, inactive attributes are returned as a new `InactiveAttribute`.
463 var attributes = new AttributeMap(self)
465 # Uniforms of this program organized by name
467 # Active uniforms are gathered at `compile_and_link
`.
468 # Upon request, inactive attributes are returned as a new `InactiveUniform`.
469 var uniforms = new UniformMap(self)
471 # Notify the GPU to use this program
474 var gl_program = gl_program
475 assert gl_program != null # TODO error not compiled, or compile it
476 glUseProgram gl_program
479 # Has this program been deleted?
482 # Delete this program if it has not already been deleted
485 if deleted then return
487 var gl_program = gl_program
488 if gl_program != null then glDeleteProgram gl_program
494 # Gamnit graphical program from the shaders source code
495 class GamnitProgramFromSource
498 # Source code of the vertex shader
499 fun vertex_shader_source: Text is abstract
501 redef var vertex_shader = new VertexShader(vertex_shader_source) is lazy
503 # Source code of the fragment shader
504 fun fragment_shader_source: Text is abstract
506 redef var fragment_shader = new FragmentShader(fragment_shader_source) is lazy
509 # Map to organize `ShaderVariable` instances by their name
510 abstract class ShaderVariableMap[A: ShaderVariable]
511 super HashMap[String, A]
513 private var program: GamnitProgram
517 # Alter the user specified name to fit the truncated name
518 var max_len = max_name_length - 1
519 if key isa Text and key.length > max_len then key = key.substring(0, max_len)
523 private fun max_name_length: Int is abstract
526 # Map to organize `Attribute` instances by their name
528 super ShaderVariableMap[Attribute]
530 redef fun provide_default_value(key) do
531 return new InactiveAttribute(program.gl_program.as(not null), "", -1, 0)
534 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_ATTRIBUTE_MAX_LENGTH)
538 # Update the location of this attribute from the user specified name
540 if key isa Text then item.location = program.gl_program.attrib_location(key.to_s)
545 # Map to organize `Uniform` instances by their name
547 super ShaderVariableMap[Uniform]
549 redef fun provide_default_value(key) do
550 return new InactiveUniform(program.gl_program.as(not null), "", -1, 0)
553 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_UNIFORM_MAX_LENGTH)
558 if key isa Text then item.location = program.gl_program.uniform_location(key.to_s)
563 redef extern class NativeGLfloatArray
565 # Allocate a new matrix
566 new matrix `{ return malloc(4*4*sizeof(GLfloat)); `}
568 # Overwrite this matrix with the identity matrix
573 matrix_set
(i
, j
, if i
== j
then 1.0 else 0.0)
578 # Get the element at `x, y`
579 fun matrix_get
(x
, y
: Int): Float `{ return self[y*4+x]; `}
581 # Set the element at `x
, y
`
582 fun matrix_set(x, y: Int, val: Float) `{ self[y*4+x] = val; `}
586 # Copy content of this matrix to a `NativeGLfloatArray`
587 fun fill_native
(native
: NativeGLfloatArray)
589 for i
in [0..width
[ do
590 for j
in [0..height
[ do
591 native
.matrix_set
(i
, j
, self[i
, j
])
597 private fun gammit_gl_error
: nullable Error
599 var gl_error
= glGetError
600 if gl_error
== gl_NO_ERROR
then return null
601 return new Error("GL error: {gl_error}")