Merge: doc: fixed some typos and other misc. corrections
[nit.git] / lib / gamnit / programs.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Services for graphical programs with shaders, attributes and uniforms
16 module programs
17
18 import display
19 import matrix
20
21 private import more_collections
22
23 # `Uniform` or `Attribute` of a `GamnitProgram`
24 class ShaderVariable
25
26 # The `GamnitProgram` to which `self` belongs
27 var program: GLProgram
28
29 # Name of `self` in the `program` source
30 var name: String
31
32 # Location of `self` in the compiled `program`
33 var location: Int
34
35 # Number of elements in this array (1 for scalars and more for vectors)
36 var size: Int
37
38 # Is `self` an active uniform or attribute in the `program`?
39 #
40 # If `false`, the variable may have been optimized out by the compiler.
41 fun is_active: Bool do return true
42
43 redef fun to_s do return "<{class_name} name:{name} location:{location} size:{size}"
44 end
45
46 # Inactive shader variable, either optimized out or simple absent from the program
47 #
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.
51 #
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
57 super ShaderVariable
58
59 redef fun is_active do return false
60 end
61
62 # Shader attribute
63 #
64 # It will use either the `uniform` value or the data at `array_pointer` if
65 # and only if `array_enabled`.
66 class Attribute
67 super ShaderVariable
68
69 private var array_enabled_cache = false
70
71 # Is the array attribute enabled?
72 fun array_enabled: Bool do return array_enabled_cache
73
74 # Set whether to use the data at `array_pointer` over `uniform`.
75 fun array_enabled=(value: Bool)
76 do
77 if not is_active then return
78
79 glUseProgram program
80
81 self.array_enabled_cache = value
82 if value then
83 glEnableVertexAttribArray location
84 else glDisableVertexAttribArray location
85 end
86
87 # Define the `array` of vertex data
88 fun array(array: Array[Float], data_per_vertex: Int)
89 do
90 # TODO move this and native_float_array to a subclass specific to float
91
92 if not is_active then return
93
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
99 else
100 native.fill_from(array)
101 end
102
103 glVertexAttribPointer(location, data_per_vertex, gl_FLOAT, false, 0, native.native_array)
104 end
105
106 private var native_float_array: nullable GLfloatArray = null
107 end
108
109 # Shader attribute of GLSL type `float`
110 class AttributeFloat
111 super Attribute
112
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)
115 end
116
117 # Shader attribute of GLSL type `vec2`
118 class AttributeVec2
119 super Attribute
120
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)
123 end
124
125 # Shader attribute of GLSL type `vec3`
126 class AttributeVec3
127 super Attribute
128
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)
131 end
132
133 # Shader attribute of GLSL type `vec4`
134 class AttributeVec4
135 super Attribute
136
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)
139 end
140
141 # `Attribute` that does not exist or that has been optimized out
142 class InactiveAttribute
143 super InactiveVariable
144 super AttributeFloat
145 super AttributeVec2
146 super AttributeVec3
147 super AttributeVec4
148 end
149
150 # Shader uniform
151 class Uniform
152 super ShaderVariable
153
154 private fun uniform_1i(index, x: Int) `{ glUniform1i(index, x); `}
155 end
156
157 # Shader uniform of GLSL type `bool`
158 class UniformBool
159 super Uniform
160
161 # Set this uniform value
162 fun uniform(val: Bool) do uniform_1i(location, if val then 1 else 0)
163 end
164
165 # Shader uniform of GLSL type `int`
166 class UniformInt
167 super Uniform
168
169 # Set this uniform value
170 fun uniform(val: Int) do uniform_1i(location, val)
171 end
172
173 # Shader uniform of GLSL type `vec4`
174 class UniformFloat
175 super Uniform
176
177 # Set this uniform value
178 fun uniform(x: Float) do glUniform1f(location, x)
179 end
180
181 # Shader uniform of GLSL type `vec4`
182 class UniformVec2
183 super Uniform
184
185 # Set this uniform value
186 fun uniform(x, y: Float) do glUniform2f(location, x, y)
187 end
188
189 # Shader uniform of GLSL type `vec4`
190 class UniformVec3
191 super Uniform
192
193 # Set this uniform value
194 fun uniform(x, y, z: Float) do glUniform3f(location, x, y, z)
195 end
196
197 # Shader uniform of GLSL type `vec4`
198 class UniformVec4
199 super Uniform
200
201 # Set this uniform value
202 fun uniform(x, y, z, w: Float) do glUniform4f(location, x, y, z, w)
203 end
204
205 # Shader uniform of GLSL type `sampler2D`
206 class UniformSampler2D
207 super Uniform
208
209 # Set this uniform value
210 fun uniform(val: Int) do uniform_1i(location, val)
211 end
212
213 # Shader uniform of GLSL type `mat4`
214 class UniformMat4
215 super Uniform
216
217 private var native_matrix_cache: nullable NativeGLfloatArray = null
218
219 # Set this uniform value
220 fun uniform(matrix: Matrix)
221 do
222 var native = native_matrix_cache
223 if native == null then
224 native = new NativeGLfloatArray.matrix
225 self.native_matrix_cache = native
226 end
227
228 matrix.fill_native(native)
229 uniform_matrix_4f(location, 1, false, native)
230 end
231
232 private fun uniform_matrix_4f(index, count: Int, transpose: Bool, data: NativeGLfloatArray) `{
233 glUniformMatrix4fv(index, count, transpose, data);
234 `}
235 end
236
237 # `Uniform` that does not exist or that has been optimized out
238 class InactiveUniform
239 super InactiveVariable
240 super UniformBool
241 super UniformInt
242 super UniformFloat
243 super UniformSampler2D
244 super UniformVec2
245 super UniformVec3
246 super UniformVec4
247 super UniformMat4
248
249 redef fun is_active do return false
250 end
251
252 # Gamnit shader
253 abstract class Shader
254
255 # TODO add alternative init to load shaders from binary
256
257 # Index of this shader in OpenGL
258 private var gl_shader: GLShader is noinit
259
260 # Latest error raised by operations of this shader
261 var error: nullable Error = null
262
263 # Source code of this shader
264 var source: Text
265
266 # Low-level type of this shader
267 private fun gl_shader_type: GLShaderType is abstract
268
269 # Compile this shader and report any errors in the attribute `error`
270 fun compile
271 do
272 # Create
273 var gl_shader = glCreateShader(gl_shader_type)
274 if not glIsShader(gl_shader) then
275 self.error = new Error("Shader creation failed: {glGetError}")
276 return
277 end
278 self.gl_shader = gl_shader
279
280 glShaderSource(gl_shader, source.to_cstring)
281
282 # Compile
283 glCompileShader gl_shader
284 if not gl_shader.is_compiled then
285 self.error = new Error("Shader compilation failed: {glGetShaderInfoLog(gl_shader)}")
286 return
287 end
288
289 error = gammit_gl_error
290 end
291
292 # Has this shader been deleted?
293 var deleted = false
294
295 # Delete this shader and free its resources
296 fun delete
297 do
298 if deleted then return
299
300 glDeleteShader gl_shader
301 deleted = true
302 end
303 end
304
305 # Gamnit vertex shader
306 class VertexShader
307 super Shader
308
309 redef fun gl_shader_type do return gl_VERTEX_SHADER
310 end
311
312 # Gamnit fragment shader
313 class FragmentShader
314 super Shader
315
316 redef fun gl_shader_type do return gl_FRAGMENT_SHADER
317 end
318
319 # Gamnit graphical program
320 #
321 # Subclasses should implement both `vertex_shader` and `fragment_shader`.
322 abstract class GamnitProgram
323
324 # Vertex shader to attach to this program
325 fun vertex_shader: VertexShader is abstract
326
327 # Fragment shader to attach to this program
328 fun fragment_shader: FragmentShader is abstract
329
330 # Index to the OpenGL ES program, set by `compile_and_link`
331 private var gl_program: nullable GLProgram = null
332
333 # Last error raised by `compile_and_link`
334 var error: nullable Error = null is protected writable
335
336 # Compile the shaders, and this program, then link and report any errors
337 fun compile_and_link
338 do
339 # Get an index
340 var gl_program = glCreateProgram
341 if not glIsProgram(gl_program) then
342 self.error = new Error("Program creation failed: {glGetError.to_s}")
343 return
344 end
345 self.gl_program = gl_program
346
347 # Vertex shader
348 var vertex_shader = vertex_shader
349 vertex_shader.compile
350 if vertex_shader.error != null then
351 self.error = vertex_shader.error
352 return
353 end
354
355 # Fragment shader
356 var fragment_shader = fragment_shader
357 fragment_shader.compile
358 if fragment_shader.error != null then
359 self.error = fragment_shader.error
360 return
361 end
362
363 # Attach shaders
364 glAttachShader(gl_program, vertex_shader.gl_shader)
365 glAttachShader(gl_program, fragment_shader.gl_shader)
366
367 # Catch any errors up to here
368 var error = gammit_gl_error
369 if error != null then
370 self.error = error
371 return
372 end
373
374 # Link
375 glLinkProgram gl_program
376 if not gl_program.is_linked then
377 self.error = new Error("Linking failed: {glGetProgramInfoLog(gl_program)}")
378 return
379 end
380
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)
388
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`.
392
393 var attribute
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)
402 else
403 attribute = new Attribute(gl_program, name, location, size)
404 end
405 # TODO missing types
406 attributes[name] = attribute
407 end
408
409 var n_uniforms = glGetProgramiv(gl_program, gl_ACTIVE_UNIFORMS)
410 for a in [0..n_uniforms[ do
411
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)
416
417 var uniform
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)
434 else
435 uniform = new Uniform(gl_program, name, location, size)
436 end
437 # TODO missing types
438 uniforms[name] = uniform
439 end
440 end
441
442 # Diagnose possible problems with the shaders of the program
443 #
444 # Lists to the console inactive uniforms and attributes.
445 # These may not be problematic but they can help to debug the program.
446 fun diagnose
447 do
448 if gl_program == null then compile_and_link
449
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"
453 end
454 for k,v in attributes do
455 if not v.is_active then print "* Attribute {v.name} is inactive"
456 end
457 end
458
459 # Attributes of this program organized by name
460 #
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)
464
465 # Uniforms of this program organized by name
466 #
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)
470
471 # Notify the GPU to use this program
472 fun use
473 do
474 var gl_program = gl_program
475 assert gl_program != null # TODO error not compiled, or compile it
476 glUseProgram gl_program
477 end
478
479 # Has this program been deleted?
480 var deleted = false
481
482 # Delete this program if it has not already been deleted
483 fun delete
484 do
485 if deleted then return
486
487 var gl_program = gl_program
488 if gl_program != null then glDeleteProgram gl_program
489
490 deleted = true
491 end
492 end
493
494 # Gamnit graphical program from the shaders source code
495 class GamnitProgramFromSource
496 super GamnitProgram
497
498 # Source code of the vertex shader
499 fun vertex_shader_source: Text is abstract
500
501 redef var vertex_shader = new VertexShader(vertex_shader_source) is lazy
502
503 # Source code of the fragment shader
504 fun fragment_shader_source: Text is abstract
505
506 redef var fragment_shader = new FragmentShader(fragment_shader_source) is lazy
507 end
508
509 # Map to organize `ShaderVariable` instances by their name
510 abstract class ShaderVariableMap[A: ShaderVariable]
511 super HashMap[String, A]
512
513 private var program: GamnitProgram
514
515 redef fun [](key)
516 do
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)
520 return super(key)
521 end
522
523 private fun max_name_length: Int is abstract
524 end
525
526 # Map to organize `Attribute` instances by their name
527 class AttributeMap
528 super ShaderVariableMap[Attribute]
529
530 redef fun provide_default_value(key) do
531 return new InactiveAttribute(program.gl_program.as(not null), "", -1, 0)
532 end
533
534 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_ATTRIBUTE_MAX_LENGTH)
535
536 redef fun [](key)
537 do
538 # Update the location of this attribute from the user specified name
539 var item = super
540 if key isa Text then item.location = program.gl_program.attrib_location(key.to_s)
541 return item
542 end
543 end
544
545 # Map to organize `Uniform` instances by their name
546 class UniformMap
547 super ShaderVariableMap[Uniform]
548
549 redef fun provide_default_value(key) do
550 return new InactiveUniform(program.gl_program.as(not null), "", -1, 0)
551 end
552
553 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_UNIFORM_MAX_LENGTH)
554
555 redef fun [](key)
556 do
557 var item = super
558 if key isa Text then item.location = program.gl_program.uniform_location(key.to_s)
559 return item
560 end
561 end
562
563 redef extern class NativeGLfloatArray
564
565 # Allocate a new matrix
566 new matrix `{ return malloc(4*4*sizeof(GLfloat)); `}
567
568 # Overwrite this matrix with the identity matrix
569 fun set_identity
570 do
571 for i in [0..4[ do
572 for j in [0..4[ do
573 matrix_set(i, j, if i == j then 1.0 else 0.0)
574 end
575 end
576 end
577
578 # Get the element at `x, y`
579 fun matrix_get(x, y: Int): Float `{ return self[y*4+x]; `}
580
581 # Set the element at `x, y`
582 fun matrix_set(x, y: Int, val: Float) `{ self[y*4+x] = val; `}
583 end
584
585 redef class Matrix
586 # Copy content of this matrix to a `NativeGLfloatArray`
587 fun fill_native(native: NativeGLfloatArray)
588 do
589 for i in [0..width[ do
590 for j in [0..height[ do
591 native.matrix_set(i, j, self[i, j])
592 end
593 end
594 end
595 end
596
597 private fun gammit_gl_error: nullable Error
598 do
599 var gl_error = glGetError
600 if gl_error == gl_NO_ERROR then return null
601 return new Error("GL error: {gl_error}")
602 end