gamnit: time the performance of the core services
[nit.git] / lib / gamnit / flat.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 # Simple API for 2D games, based around `Sprite` and `App::update`
16 #
17 # Client programs should implement `App::update` to execute game logic and
18 # add instances of `Sprite` to `App::sprites` and `App::ui_sprites`.
19 # At each frame, all sprites are drawn to the screen.
20 #
21 # This system relies on two cameras `App::world_camera` and `App::ui_camera`.
22 #
23 # * `App::world_camera` applies a perspective effect to draw the game world.
24 # This camera is designed to be moved around to see the world as well as to zoom in and out.
25 # It is used to position the sprites in `App::sprites`.
26 #
27 # * `App::ui_camera` is a simple orthogonal camera to display UI objects.
28 # This camera should mostly be still, it can still move for chock effects and the like.
29 # It can be used to standardize the size of the UI across devices.
30 # It is used to position the sprites in `App::ui_sprites`.
31 #
32 # See the sample game at `contrib/asteronits/`.
33 module flat
34
35 import glesv2
36
37 import geometry::points_and_lines
38 import matrix::projection
39 import more_collections
40 import realtime
41 import performance_analysis
42
43 import gamnit
44 import gamnit::cameras
45 import gamnit::limit_fps
46
47 import android_two_fingers_motion is conditional(android)
48
49 # Image to draw on screen
50 class Sprite
51
52 # Texture drawn to screen
53 var texture: Texture is writable
54
55 # Position of this sprite in world coordinates
56 var center: Point3d[Float] is writable
57
58 # Rotation on the Z axis, where 0.0 points right and `0.5*pi` points up
59 var rotation = 0.0 is writable
60
61 # Mirror `texture` horizontally, inverting each pixel on the X axis
62 var invert_x = false is writable
63
64 # Scale applied to this sprite
65 var scale = 1.0 is writable
66
67 # Transparency applied to the texture on draw
68 fun alpha: Float do return tint[3]
69
70 # Transparency applied to the texture on draw
71 fun alpha=(value: Float) do tint[3] = value
72
73 # Tint applied to the texture on draw
74 #
75 # Require: `tint.length == 4`
76 var tint: Array[Float] = [1.0, 1.0, 1.0, 1.0] is writable
77
78 private fun draw
79 do
80 var simple_2d_program = app.simple_2d_program
81
82 glActiveTexture gl_TEXTURE0
83 glBindTexture(gl_TEXTURE_2D, texture.root.gl_texture)
84
85 simple_2d_program.translation.array_enabled = false
86 simple_2d_program.color.array_enabled = false
87 simple_2d_program.scale.array_enabled = false
88
89 simple_2d_program.translation.uniform(center.x, center.y, center.z, 0.0)
90 simple_2d_program.color.uniform(tint[0], tint[1], tint[2], tint[3])
91 simple_2d_program.scale.uniform scale
92
93 simple_2d_program.use_texture.uniform true
94 simple_2d_program.texture.uniform 0
95 simple_2d_program.tex_coord.array(
96 if invert_x then
97 texture.texture_coords_invert_x
98 else texture.texture_coords, 2)
99 simple_2d_program.coord.array(texture.vertices, 3)
100
101 simple_2d_program.rotation.uniform new Matrix.rotation(rotation, 0.0, 0.0, -1.0)
102
103 glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
104 end
105 end
106
107 redef class App
108 # Default graphic program to draw `sprites`
109 var simple_2d_program = new Simple2dProgram is lazy # TODO private
110
111 # Camera for world objects with perspective
112 #
113 # By default, the camera is configured to respect the resolution
114 # of the screen in world coordinates at `z == 0.0`.
115 var world_camera: EulerCamera is lazy do
116 var camera = new EulerCamera(app.display.as(not null))
117
118 # Aim for pixel resolution at level 0
119 camera.reset_height
120 camera.near = 100.0
121
122 return camera
123 end
124
125 # Camera for UI elements using an orthogonal view
126 var ui_camera: UICamera = new UICamera(app.display.as(not null)) is lazy
127
128 # Live sprites to draw in reference to `world_camera`
129 var sprites: Sequence[Sprite] = new List[Sprite]
130
131 # UI sprites to draw in reference to `ui_camera`, over world `sprites`
132 var ui_sprites: Sequence[Sprite] = new List[Sprite]
133
134 # Main clock used to count each frame `dt`, lapsed for `update` only
135 private var clock = new Clock is lazy
136
137 # Performance clock to for `frame_core_draw` operations
138 private var perf_clock_main = new Clock
139 redef fun on_create
140 do
141 super
142
143 var display = display
144 assert display != null
145
146 var gl_error = glGetError
147 assert gl_error == gl_NO_ERROR else print gl_error
148
149 # Prepare program
150 var program = simple_2d_program
151 program.compile_and_link
152
153 var gamnit_error = program.error
154 assert gamnit_error == null else print_error gamnit_error
155
156 # Enable blending
157 gl.capabilities.blend.enable
158 glBlendFunc(gl_SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA)
159
160 # Enable depth test
161 gl.capabilities.depth_test.enable
162 glDepthFunc gl_LEQUAL
163 glDepthMask true
164
165 # Prepare viewport and background color
166 glViewport(0, 0, display.width, display.height)
167 glClearColor(0.0, 0.0, 0.0, 1.0)
168
169 gl_error = glGetError
170 assert gl_error == gl_NO_ERROR else print gl_error
171
172 # Prepare to draw
173 for tex in all_root_textures do
174 tex.load
175 gamnit_error = tex.error
176 if gamnit_error != null then print_error gamnit_error
177
178 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
179 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
180 end
181 end
182
183 redef fun frame_core(display)
184 do
185 # Prepare to draw, clear buffers
186 glClear(gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT)
187
188 # Check errors
189 var gl_error = glGetError
190 assert gl_error == gl_NO_ERROR else print gl_error
191
192 # Update game logic and set sprites
193 perf_clock_main.lapse
194 var dt = clock.lapse.to_f
195 update dt
196 sys.perfs["gamnit flat update client"].add perf_clock_main.lapse
197
198 # Draw and flip screen
199 frame_core_draw display
200 display.flip
201
202 # Check errors
203 gl_error = glGetError
204 assert gl_error == gl_NO_ERROR else print gl_error
205 end
206
207 # Draw the whole screen, all `glDraw...` calls should be executed here
208 protected fun frame_core_draw(display: GamnitDisplay)
209 do
210 perf_clock_main.lapse
211
212 frame_core_world_sprites display
213 perfs["gamnit flat world_sprites"].add perf_clock_main.lapse
214
215 frame_core_ui_sprites display
216 perfs["gamnit flat ui_sprites"].add perf_clock_main.lapse
217 end
218
219
220 # Draw world sprites from `sprites`
221 protected fun frame_core_world_sprites(display: GamnitDisplay)
222 do
223 simple_2d_program.use
224
225 # Set constant configs
226 simple_2d_program.coord.array_enabled = true
227 simple_2d_program.tex_coord.array_enabled = true
228 simple_2d_program.color.array_enabled = false
229
230 # TODO optimize this draw to store constant values on the GPU
231 # World sprites
232 simple_2d_program.mvp.uniform world_camera.mvp_matrix
233 for sprite in sprites do sprite.draw
234 end
235
236 # Draw UI sprites from `ui_sprites`
237 protected fun frame_core_ui_sprites(display: GamnitDisplay)
238 do
239 simple_2d_program.use
240
241 # Set constant configs
242 simple_2d_program.coord.array_enabled = true
243 simple_2d_program.tex_coord.array_enabled = true
244 simple_2d_program.color.array_enabled = false
245
246 # Reset only the depth buffer
247 glClear gl_DEPTH_BUFFER_BIT
248
249 # UI sprites
250 simple_2d_program.mvp.uniform ui_camera.mvp_matrix
251 for sprite in ui_sprites do sprite.draw
252 end
253
254 # Main method to refine in clients to update game logic and `sprites`
255 fun update(dt: Float) do end
256
257 # Display `texture` as a splash screen
258 #
259 # Load `texture` if needed and resets `ui_camera` to 1080 units on the Y axis.
260 fun show_splash_screen(texture: Texture)
261 do
262 texture.load
263
264 ui_camera.reset_height 1080.0
265
266 var splash = new Sprite(texture, ui_camera.center)
267 ui_sprites.add splash
268
269 var display = display
270 assert display != null
271 glClear gl_COLOR_BUFFER_BIT
272 frame_core_ui_sprites display
273 display.flip
274
275 ui_sprites.remove splash
276 end
277
278 redef fun on_stop
279 do
280 # Clean up
281 simple_2d_program.delete
282
283 # Close gamnit
284 var display = display
285 if display != null then display.close
286 end
287 end
288
289 redef class Texture
290
291 # Vertices coordinates of the base geometry
292 private var vertices: Array[Float] is lazy do
293 var mod = 1.0
294 var w = width * mod
295 var h = height * mod
296 var a = [-0.5*w, -0.5*h, 0.0]
297 var b = [ 0.5*w, -0.5*h, 0.0]
298 var c = [-0.5*w, 0.5*h, 0.0]
299 var d = [ 0.5*w, 0.5*h, 0.0]
300
301 var vertices = new Array[Float]
302 for v in [c, d, a, b] do vertices.add_all v
303 return vertices
304 end
305
306 # Coordinates of this texture on the `root` texture, with `[0..1.0]`
307 private var texture_coords: Array[Float] is lazy do
308 var a = [offset_left, offset_bottom]
309 var b = [offset_right, offset_bottom]
310 var c = [offset_left, offset_top]
311 var d = [offset_right, offset_top]
312
313 var texture_coords = new Array[Float]
314 for v in [c, d, a, b] do texture_coords.add_all v
315 return texture_coords
316 end
317
318 # Coordinates of this texture on the `root` texture, with the X axis inverted
319 private var texture_coords_invert_x: Array[Float] is lazy do
320 var a = [offset_left, offset_bottom]
321 var b = [offset_right, offset_bottom]
322 var c = [offset_left, offset_top]
323 var d = [offset_right, offset_top]
324
325 var texture_coords = new Array[Float]
326 for v in [d, c, b, a] do texture_coords.add_all v
327 return texture_coords
328 end
329 end
330
331 # Graphic program to display simple models with a texture, translation, rotation and scale
332 class Simple2dProgram
333 super GamnitProgramFromSource
334
335 redef var vertex_shader_source = """
336 // Vertex coordinates
337 attribute vec4 coord;
338
339 // Vertex color tint
340 attribute vec4 color;
341
342 // Vertex translation
343 attribute vec4 translation;
344
345 // Vertex scaling
346 attribute float scale;
347
348 // Vertex coordinates on textures
349 attribute vec2 tex_coord;
350
351 // Model view projection matrix
352 uniform mat4 mvp;
353
354 // Rotation matrix
355 uniform mat4 rotation;
356
357 // Output for the fragment shader
358 varying vec4 v_color;
359 varying vec2 v_coord;
360
361 void main()
362 {
363 v_color = color;
364 gl_Position = (vec4(coord.xyz * scale, 1.0) * rotation + translation) * mvp;
365 v_coord = tex_coord;
366 }
367 """ @ glsl_vertex_shader
368
369 redef var fragment_shader_source = """
370 precision mediump float;
371
372 // Does this object use a texture?
373 uniform bool use_texture;
374
375 // Texture to apply on this object
376 uniform sampler2D texture0;
377
378 // Input from the vertex shader
379 varying vec4 v_color;
380 varying vec2 v_coord;
381
382 void main()
383 {
384 if(use_texture) {
385 gl_FragColor = v_color * texture2D(texture0, v_coord);
386 if (gl_FragColor.a == 0.0) discard;
387 } else {
388 gl_FragColor = v_color;
389 }
390 }
391 """ @ glsl_fragment_shader
392
393 # Vertices coordinates
394 var coord = attributes["coord"].as(AttributeVec4) is lazy
395
396 # Should this program use the texture `texture`?
397 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
398
399 # Visible texture unit
400 var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
401
402 # Coordinates on the textures, per vertex
403 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
404
405 # Color tint per vertex
406 var color = attributes["color"].as(AttributeVec4) is lazy
407
408 # Translation applied to each vertex
409 var translation = attributes["translation"].as(AttributeVec4) is lazy
410
411 # Rotation matrix
412 var rotation = uniforms["rotation"].as(UniformMat4) is lazy
413
414 # Scaling per vertex
415 var scale = attributes["scale"].as(AttributeFloat) is lazy
416
417 # Model view projection matrix
418 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
419 end
420
421 redef class Point3d[N]
422 # Get a new `Point3d[Float]` with an offset of each axis of `x, y, z`
423 fun offset(x, y, z: Numeric): Point3d[Float]
424 do
425 return new Point3d[Float](self.x.to_f+x.to_f, self.y.to_f+y.to_f, self.z.to_f+z.to_f)
426 end
427 end