glesv2: optimize `GLfloatArray` by a custom implementation and intro `add`
[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 glDeleteProgram gl_program.as(not null)
488 deleted = true
489 end
490 end
491
492 # Gamnit graphical program from the shaders source code
493 class GamnitProgramFromSource
494 super GamnitProgram
495
496 # Source code of the vertex shader
497 fun vertex_shader_source: Text is abstract
498
499 redef var vertex_shader = new VertexShader(vertex_shader_source) is lazy
500
501 # Source code of the fragment shader
502 fun fragment_shader_source: Text is abstract
503
504 redef var fragment_shader = new FragmentShader(fragment_shader_source) is lazy
505 end
506
507 # Map to organize `ShaderVariable` instances by their name
508 abstract class ShaderVariableMap[A: ShaderVariable]
509 super HashMap[String, A]
510
511 private var program: GamnitProgram
512
513 redef fun [](key)
514 do
515 # Alter the user specified name to fit the truncated name
516 var max_len = max_name_length - 1
517 if key isa Text and key.length > max_len then key = key.substring(0, max_len)
518 return super(key)
519 end
520
521 private fun max_name_length: Int is abstract
522 end
523
524 # Map to organize `Attribute` instances by their name
525 class AttributeMap
526 super ShaderVariableMap[Attribute]
527
528 redef fun provide_default_value(key) do
529 return new InactiveAttribute(program.gl_program.as(not null), "", -1, 0)
530 end
531
532 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_ATTRIBUTE_MAX_LENGTH)
533
534 redef fun [](key)
535 do
536 # Update the location of this attribute from the user specified name
537 var item = super
538 if key isa Text then item.location = program.gl_program.attrib_location(key.to_s)
539 return item
540 end
541 end
542
543 # Map to organize `Uniform` instances by their name
544 class UniformMap
545 super ShaderVariableMap[Uniform]
546
547 redef fun provide_default_value(key) do
548 return new InactiveUniform(program.gl_program.as(not null), "", -1, 0)
549 end
550
551 redef fun max_name_length do return glGetProgramiv(program.gl_program.as(not null), gl_ACTIVE_UNIFORM_MAX_LENGTH)
552
553 redef fun [](key)
554 do
555 var item = super
556 if key isa Text then item.location = program.gl_program.uniform_location(key.to_s)
557 return item
558 end
559 end
560
561 redef extern class NativeGLfloatArray
562
563 # Allocate a new matrix
564 new matrix `{ return malloc(4*4*sizeof(GLfloat)); `}
565
566 # Overwrite this matrix with the identity matrix
567 fun set_identity
568 do
569 for i in 4.times do
570 for j in 4.times do
571 matrix_set(i, j, if i == j then 1.0 else 0.0)
572 end
573 end
574 end
575
576 # Get the element at `x, y`
577 fun matrix_get(x, y: Int): Float `{ return self[y*4+x]; `}
578
579 # Set the element at `x, y`
580 fun matrix_set(x, y: Int, val: Float) `{ self[y*4+x] = val; `}
581 end
582
583 redef class Matrix
584 # Copy content of this matrix to a `NativeGLfloatArray`
585 fun fill_native(native: NativeGLfloatArray)
586 do
587 for i in width.times do
588 for j in height.times do
589 native.matrix_set(i, j, self[i, j])
590 end
591 end
592 end
593 end
594
595 private fun gammit_gl_error: nullable Error
596 do
597 var gl_error = glGetError
598 if gl_error == gl_NO_ERROR then return null
599 return new Error("GL error: {gl_error}")
600 end