gamnit: move `flat` to a group
[nit.git] / lib / gamnit / flat / flat_core.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 # Core services for the `flat` API for 2D games
16 module flat_core
17
18 import glesv2
19 intrude import geometry::points_and_lines # For _x, _y and _z
20 intrude import matrix
21 import matrix::projection
22 import more_collections
23 import performance_analysis
24
25 import gamnit
26 intrude import gamnit::cameras
27 intrude import gamnit::cameras_cache
28 import gamnit::dynamic_resolution
29 import gamnit::limit_fps
30 import gamnit::camera_control
31
32 # Visible 2D entity in the game world or UI
33 #
34 # Similar to `gamnit::Actor` which is in 3D.
35 #
36 # Each sprite associates a `texture` to the position `center`.
37 # The appearance is modified by `rotation`, `invert_x`,
38 # `scale`, `red`, `green`, `blue` and `alpha`.
39 # These values can be changed at any time and will trigger an update
40 # of the data on the GPU side, having a small performance cost.
41 #
42 # For a sprite to be visible, it must be added to either the world `sprites`
43 # or the `ui_sprites`.
44 # However, an instance of `Sprite` can only belong to a single `SpriteSet`
45 # at a time. The final on-screen position depends on the camera associated
46 # to the `SpriteSet`.
47 #
48 # ~~~
49 # # Load texture and create sprite
50 # var texture = new Texture("path/in/assets.png")
51 # var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0))
52 #
53 # # Add sprite to the visible game world
54 # app.sprites.add sprite
55 #
56 # # Extra configuration of the sprite
57 # sprite.rotation = pi/2.0
58 # sprite.scale = 2.0
59 #
60 # # Show only the blue colors
61 # sprite.red = 0.0
62 # sprite.green = 0.0
63 # ~~~
64 #
65 # To add a sprite to the UI it can be anchored to screen borders
66 # with `ui_camera.top_left` and the likes.
67 #
68 # ~~~nitish
69 # # Place it a bit off the top left of the screen
70 # var pos = app.ui_camera.top_left.offset(128.0, -128.0, 0)
71 #
72 # # Load texture and create sprite
73 # var texture = new Texture("path/in/assets.png")
74 # var sprite = new Sprite(texture, pos)
75 #
76 # # Add it to the UI (above world sprites)
77 # app.ui_sprites.add sprite
78 # ~~~
79 class Sprite
80
81 # Texture drawn to screen
82 var texture: Texture is writable(texture_direct=)
83
84 # Texture drawn to screen
85 fun texture=(value: Texture)
86 do
87 if isset _texture and value != texture then
88 needs_update
89 if value.root != texture.root then needs_remap
90 end
91 texture_direct = value
92 end
93
94 # Center position of this sprite in world coordinates
95 var center: Point3d[Float] is writable(center_direct=), noautoinit
96
97 # Center position of this sprite in world coordinates
98 fun center=(value: Point3d[Float]) is autoinit do
99 if isset _center and value != center then
100 needs_update
101 center.sprites_remove self
102 end
103
104 value.sprites_add self
105 center_direct = value
106 end
107
108 # Last animation set with `animate`
109 var animation: nullable Animation = null
110
111 # Animation on the shader, if this changes it `needs_remap`
112 private var shader_animation: nullable Animation = null
113
114 # Animation start time, relative to `sprite_set.time`
115 #
116 # At -1.0 if animation started before being assigned a `sprite_set`.
117 private var animation_start = 0.0
118
119 # Number of loops to show `animation`
120 private var animation_loops = 0.0
121
122 # Start the `animation` for `n_loops`, replacing the static `texture`
123 #
124 # By default, if `n_loops` is not set, the animation plays once.
125 # If `n_loops == -1.0` then the animation loops infinitely.
126 # Otherwise, the animation repeats, e.g. it repeats twice and a half
127 # if `n_loops == 2.5`.
128 #
129 # The animation can be stopped using `animate_stop`.
130 fun animate(animation: Animation, n_loops: nullable Float)
131 do
132 if not animation.valid then print_error "{class_name}::animate: invalid animation {animation}"
133
134 var shader_animation = shader_animation
135 if shader_animation == null or animation.frames.first.root != shader_animation.frames.first.root then
136 # Resort with the new animation texture
137 needs_remap
138 else
139 needs_update
140 end
141
142 var sprite_set = sprite_set
143 animation_start = if sprite_set != null then sprite_set.time else -1.0
144 animation_loops = n_loops or else 1.0
145 self.shader_animation = animation
146 self.animation = animation
147 end
148
149 # Stop any active `animation` to display the static `texture`
150 fun animate_stop
151 do
152 if animation == null then return
153 needs_update
154 animation = null
155 end
156
157 # Rotation on the Z axis, positive values turn counterclockwise
158 var rotation = 0.0 is writable(rotation_direct=)
159
160 # Rotation on the Z axis, positive values turn counterclockwise
161 fun rotation=(value: Float)
162 do
163 if isset _rotation and value != rotation then needs_update
164 rotation_direct = value
165 end
166
167 # Mirror `texture` horizontally, inverting each pixel on the X axis
168 var invert_x = false is writable(invert_x_direct=)
169
170 # Mirror `texture` horizontally, inverting each pixel on the X axis
171 fun invert_x=(value: Bool)
172 do
173 if isset _invert_x and value != invert_x then needs_update
174 invert_x_direct = value
175 end
176
177 # Scale applied to this sprite
178 #
179 # The basic size of `self` depends on the size in pixels of `texture`.
180 var scale = 1.0 is writable(scale_direct=)
181
182 # Scale applied to this sprite
183 #
184 # The basic size of `self` depends on the size in pixels of `texture`.
185 fun scale=(value: Float)
186 do
187 if isset _scale and value != scale then needs_update
188 scale_direct = value
189 end
190
191 # Red tint applied to `texture` on draw
192 fun red: Float do return tint[0]
193
194 # Red tint applied to `texture` on draw
195 fun red=(value: Float)
196 do
197 if isset _tint and value != red then needs_update
198 tint[0] = value
199 end
200
201 # Green tint applied to `texture` on draw
202 fun green: Float do return tint[1]
203
204 # Green tint applied to `texture` on draw
205 fun green=(value: Float)
206 do
207 if isset _tint and value != green then needs_update
208 tint[1] = value
209 end
210
211 # Blue tint applied to `texture` on draw
212 fun blue: Float do return tint[2]
213
214 # Blue tint applied to `texture` on draw
215 fun blue=(value: Float)
216 do
217 if isset _tint and value != blue then needs_update
218 tint[2] = value
219 end
220
221 # Transparency applied to `texture` on draw
222 fun alpha: Float do return tint[3]
223
224 # Transparency applied to `texture` on draw
225 fun alpha=(value: Float)
226 do
227 if isset _tint and value != alpha then needs_update
228 tint[3] = value
229 end
230
231 # Tint applied to `texture` on draw
232 #
233 # Alternative to the accessors `red, green, blue & alpha`.
234 # Changes inside the array do not automatically set `needs_update`.
235 #
236 # Require: `tint.length == 4`
237 var tint: Array[Float] = [1.0, 1.0, 1.0, 1.0] is writable(tint_direct=)
238
239 # Tint applied to `texture` on draw, see `tint`
240 fun tint=(value: Array[Float])
241 do
242 if isset _tint and value != tint then needs_update
243 tint_direct = value
244 end
245
246 # Is this sprite static and added in bulk?
247 #
248 # Set to `true` to give a hint to the framework that this sprite won't
249 # change often and that it is added in bulk with other static sprites.
250 # This value can be ignored in the prototyping phase of a game and
251 # added only when better performance are needed.
252 var static = false is writable(static_direct=)
253
254 # Is this sprite static and added in bulk? see `static`
255 fun static=(value: Bool)
256 do
257 if isset _static and value != static then needs_remap
258 static_direct = value
259 end
260
261 # Request an update on the CPU
262 #
263 # This is called automatically on modification of any value of `Sprite`.
264 # However, it can still be set manually if a modification can't be
265 # detected or by subclasses.
266 fun needs_update
267 do
268 var c = context
269 if c == null then return
270 if c.last_sprite_to_update == self then return
271 c.sprites_to_update.add self
272 c.last_sprite_to_update = self
273 end
274
275 # Request a resorting of this sprite in its sprite list
276 #
277 # Resorting is required when `static` or the root of `texture` changes.
278 # This is called automatically when such changes are detected.
279 # However, it can still be set manually if a modification can't be
280 # detected or by subclasses.
281 fun needs_remap
282 do
283 var l = sprite_set
284 if l != null then l.sprites_to_remap.add self
285 end
286
287 # Current context to which `self` was sorted
288 private var context: nullable SpriteContext = null
289
290 # Index in `context`
291 private var context_index: Int = -1
292
293 # Current context to which `self` belongs
294 private var sprite_set: nullable SpriteSet = null
295 end
296
297 # Animation for sprites, set with `Sprite.animate`
298 #
299 # Two main services create animations:
300 # * The constructors accepts an array of textures and the number of frames per
301 # seconds: `new Animation(array_of_subtextures, 10.0)`
302 # * The method `Texture::to_animation` uses the whole texture
303 # dividing it in frames either on X or Y:
304 # `new Texture("path/in/assets.png").to_animation(30.0, 0, 12)`
305 class Animation
306
307 # Frames composing this animation
308 #
309 # All frames must share the same `Texture::root`, be on a vertical or
310 # horizontal line, be spaced equally and share the same dimensions.
311 var frames: SequenceRead[Texture]
312
313 # Frames per seconds, a higher value makes this animation faster
314 #
315 # The animation speed is also affected by `SpriteSet::time_mod`.
316 var fps: Float
317
318 # Are the `frames` valid for an animation? (see the requirements in `frames`)
319 var valid: Bool is lazy do
320 var r: nullable RootTexture = null
321 for f in frames do
322 if r == null then
323 r = f.root
324 else
325 if r != f.root then return false
326 end
327 end
328
329 # TODO check for line, constant distance, and same aspect ratio.
330
331 return true
332 end
333 end
334
335 redef class App
336 # Default graphic program to draw `sprites`
337 private var simple_2d_program = new Simple2dProgram is lazy
338
339 # Camera for world `sprites` and `depth::actors` with perspective
340 #
341 # By default, the camera is configured to a height of 1080 units
342 # of world coordinates at `z == 0.0`.
343 var world_camera: EulerCamera is lazy do
344 var camera = new EulerCamera(app.display.as(not null))
345
346 # Aim for full HD pixel resolution at level 0
347 camera.reset_height 1080.0
348 camera.near = 10.0
349
350 return camera
351 end
352
353 # Camera for `ui_sprites` using an orthogonal view
354 var ui_camera = new UICamera(app.display.as(not null)) is lazy
355
356 # World sprites drawn as seen by `world_camera`
357 var sprites: Set[Sprite] = new SpriteSet
358
359 # UI sprites drawn as seen by `ui_camera`, over world `sprites`
360 var ui_sprites: Set[Sprite] = new SpriteSet
361
362 # Main method to refine in clients to update game logic and `sprites`
363 fun update(dt: Float) do end
364
365 # Display `texture` as a splash screen
366 #
367 # Load `texture` if needed and resets `ui_camera` to 1080 units on the Y axis.
368 fun show_splash_screen(texture: Texture)
369 do
370 texture.load
371
372 var splash = new Sprite(texture, ui_camera.center.offset(0.0, 0.0, 0.0))
373 ui_sprites.add splash
374
375 var display = display
376 assert display != null
377 glClear gl_COLOR_BUFFER_BIT
378 frame_core_ui_sprites display
379 display.flip
380
381 ui_sprites.remove splash
382 end
383
384 # ---
385 # Support and implementation
386
387 # Main clock used to count each frame `dt`, lapsed for `update` only
388 private var clock = new Clock is lazy
389
390 # Performance clock to for `frame_core_draw` operations
391 private var perf_clock_main = new Clock
392
393 # Second performance clock for smaller operations
394 private var perf_clock_sprites = new Clock is lazy
395
396 redef fun on_create
397 do
398 super
399
400 var display = display
401 assert display != null
402
403 var gl_error = glGetError
404 assert gl_error == gl_NO_ERROR else print_error gl_error
405
406 # Prepare program
407 var program = simple_2d_program
408 program.compile_and_link
409
410 var gamnit_error = program.error
411 assert gamnit_error == null else print_error gamnit_error
412
413 # Enable blending
414 gl.capabilities.blend.enable
415 glBlendFunc(gl_ONE, gl_ONE_MINUS_SRC_ALPHA)
416
417 # Enable depth test
418 gl.capabilities.depth_test.enable
419 glDepthFunc gl_LEQUAL
420 glDepthMask true
421
422 # Prepare viewport and background color
423 glViewport(0, 0, display.width, display.height)
424 glClearColor(0.0, 0.0, 0.0, 1.0)
425
426 gl_error = glGetError
427 assert gl_error == gl_NO_ERROR else print_error gl_error
428
429 # Prepare to draw
430 for tex in all_root_textures do
431 tex.load
432 gamnit_error = tex.error
433 if gamnit_error != null then print_error gamnit_error
434
435 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
436 glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
437 end
438 end
439
440 redef fun on_stop
441 do
442 # Clean up
443 simple_2d_program.delete
444
445 # Close gamnit
446 var display = display
447 if display != null then display.close
448 end
449
450 redef fun on_resize(display)
451 do
452 super
453
454 world_camera.mvp_matrix_cache = null
455 ui_camera.mvp_matrix_cache = null
456
457 # Update all sprites in the UI
458 for sprite in ui_sprites do sprite.needs_update
459 end
460
461 redef fun frame_core(display)
462 do
463 # Check errors
464 var gl_error = glGetError
465 assert gl_error == gl_NO_ERROR else print_error gl_error
466
467 # Update game logic and set sprites
468 perf_clock_main.lapse
469 var dt = clock.lapse.to_f
470 update dt
471 frame_dt = dt
472 sys.perfs["gamnit flat update client"].add perf_clock_main.lapse
473
474 # Draw and flip screen
475 frame_core_draw display
476 display.flip
477
478 # Check errors
479 gl_error = glGetError
480 assert gl_error == gl_NO_ERROR else print_error gl_error
481 end
482
483 private var frame_dt = 0.0
484
485 # Draw the whole screen, all `glDraw...` calls should be executed here
486 protected fun frame_core_draw(display: GamnitDisplay)
487 do
488 frame_core_dynamic_resolution_before display
489
490 perf_clock_main.lapse
491 frame_core_world_sprites display
492 perfs["gamnit flat world_sprites"].add perf_clock_main.lapse
493
494 frame_core_ui_sprites display
495 perfs["gamnit flat ui_sprites"].add perf_clock_main.lapse
496
497 frame_core_dynamic_resolution_after display
498 end
499
500 private fun frame_core_sprites(display: GamnitDisplay, sprite_set: SpriteSet, camera: Camera)
501 do
502 var simple_2d_program = app.simple_2d_program
503 simple_2d_program.use
504 simple_2d_program.mvp.uniform camera.mvp_matrix
505
506 sprite_set.time += frame_dt*sprite_set.time_mod
507 simple_2d_program.time.uniform sprite_set.time
508
509 # draw
510 sprite_set.draw
511 end
512
513 # Draw world sprites from `sprites`
514 protected fun frame_core_world_sprites(display: GamnitDisplay)
515 do
516 frame_core_sprites(display, sprites.as(SpriteSet), world_camera)
517 end
518
519 # Draw UI sprites from `ui_sprites`
520 protected fun frame_core_ui_sprites(display: GamnitDisplay)
521 do
522 # Reset only the depth buffer
523 glClear gl_DEPTH_BUFFER_BIT
524
525 frame_core_sprites(display, ui_sprites.as(SpriteSet), ui_camera)
526 end
527 end
528
529 redef class Texture
530
531 # Vertices coordinates of the base geometry
532 #
533 # Defines the default width and height of related sprites.
534 private var vertices: Array[Float] is lazy do
535 var w = width
536 var h = height
537 return [-0.5*w, 0.5*h, 0.0,
538 0.5*w, 0.5*h, 0.0,
539 -0.5*w, -0.5*h, 0.0,
540 0.5*w, -0.5*h, 0.0]
541 end
542
543 # Coordinates of this texture on the `root` texture, in `[0..1.0]`
544 private var texture_coords: Array[Float] is lazy do
545 var l = offset_left
546 var r = offset_right
547 var b = offset_bottom
548 var t = offset_top
549 return [l, t,
550 r, t,
551 l, b,
552 r, b]
553 end
554
555 # Coordinates of this texture on the `root` texture, inverting the X axis
556 private var texture_coords_invert_x: Array[Float] is lazy do
557 var l = offset_left
558 var r = offset_right
559 var b = offset_bottom
560 var t = offset_top
561 return [r, t,
562 l, t,
563 r, b,
564 l, b]
565 end
566
567 # Convert to a sprite animation at `fps` speed with `x` or `y` frames
568 #
569 # The arguments `x` and `y` set the number of frames in the texture.
570 # Use `x` for an horizontal arrangement or `y` for vertical.
571 # One and only one of the arguments must be different than 0,
572 # as an animation can only be on a line and cannot wrap.
573 fun to_animation(fps: Float, x, y: Int): Animation
574 do
575 assert (x == 0) != (y == 0)
576
577 var n_frames = x.max(y)
578 var frames = new Array[Texture]
579
580 var dx = (x/n_frames).to_f/n_frames.to_f
581 var dy = (y/n_frames).to_f/n_frames.to_f
582 var w = if x == 0 then 1.0 else dx
583 var h = if y == 0 then 1.0 else dy
584 var left = 0.0
585 var top = 0.0
586 for i in n_frames.times do
587 frames.add new RelativeSubtexture(root, left, top, left+w, top+h)
588 left += dx
589 top += dy
590 end
591
592 return new Animation(frames, fps)
593 end
594 end
595
596 # Graphic program to display simple models with a texture, translation, rotation and scale
597 private class Simple2dProgram
598 super GamnitProgramFromSource
599
600 redef var vertex_shader_source = """
601 // Vertex coordinates
602 attribute vec4 coord;
603
604 // Vertex color tint
605 attribute vec4 color;
606
607 // Vertex translation
608 attribute vec4 translation;
609
610 // Vertex scaling
611 attribute float scale;
612
613 // Vertex coordinates on textures
614 attribute vec2 tex_coord;
615
616 // Model view projection matrix
617 uniform mat4 mvp;
618
619 // Current world time, in seconds
620 uniform float time;
621
622 // Rotation matrix
623 attribute vec4 rotation_row0;
624 attribute vec4 rotation_row1;
625 attribute vec4 rotation_row2;
626 attribute vec4 rotation_row3;
627
628 // Animation speed, frames per seconds
629 attribute float a_fps;
630
631 // Number of frames in the animation
632 attribute float a_n_frames;
633
634 // World coordinate of the animation (for aspect ratio)
635 attribute vec2 a_coord;
636
637 // Animation texture coordinates of the first frame
638 attribute vec2 a_tex_coord;
639
640 // Animation texture coordinates difference between frames
641 attribute vec2 a_tex_diff;
642
643 // Animation start time, in reference to `time`
644 attribute float a_start;
645
646 // Number of loops to play of the animation
647 attribute float a_loops;
648
649 mat4 rotation()
650 {
651 return mat4(rotation_row0, rotation_row1, rotation_row2, rotation_row3);
652 }
653
654 // Output to the fragment shader
655 varying vec4 v_color;
656 varying vec2 v_coord;
657
658 // Is there an active animation?
659 varying float v_animated;
660
661 void main()
662 {
663 vec3 c; // coords
664
665 float end = a_start + a_loops/a_fps*a_n_frames;
666 if (a_loops == -1.0 || time < end) {
667 // in animation
668 float frame = mod(floor((time-a_start)*a_fps), a_n_frames);
669 v_coord = a_tex_coord + a_tex_diff*frame;
670 c = vec3(a_coord, coord.z);
671 v_animated = 1.0;
672 } else {
673 // static
674 v_coord = tex_coord;
675 c = coord.xyz;
676 v_animated = 0.0;
677 }
678
679 gl_Position = (vec4(c * scale, 1.0) * rotation() + translation)* mvp;
680 v_color = vec4(color.rgb*color.a, color.a);
681 }
682 """ @ glsl_vertex_shader
683
684 redef var fragment_shader_source = """
685 precision mediump float;
686
687 // Does this object use a texture?
688 uniform bool use_texture;
689
690 // Texture to apply on this object
691 uniform sampler2D texture0;
692
693 // Texture to apply on this object
694 uniform sampler2D animation;
695
696 // Input from the vertex shader
697 varying vec4 v_color;
698 varying vec2 v_coord;
699 varying float v_animated;
700
701 void main()
702 {
703 if (v_animated > 0.5) {
704 gl_FragColor = v_color * texture2D(animation, v_coord);
705 if (gl_FragColor.a <= 0.01) discard;
706 } else if (use_texture) {
707 gl_FragColor = v_color * texture2D(texture0, v_coord);
708 if (gl_FragColor.a <= 0.01) discard;
709 } else {
710 gl_FragColor = v_color;
711 }
712 }
713 """ @ glsl_fragment_shader
714
715 # Vertices coordinates
716 var coord = attributes["coord"].as(AttributeVec4) is lazy
717
718 # Should this program use the texture `texture`?
719 var use_texture = uniforms["use_texture"].as(UniformBool) is lazy
720
721 # Visible texture unit
722 var texture = uniforms["texture0"].as(UniformSampler2D) is lazy
723
724 # Coordinates on the textures, per vertex
725 var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy
726
727 # Color tint per vertex
728 var color = attributes["color"].as(AttributeVec4) is lazy
729
730 # Translation applied to each vertex
731 var translation = attributes["translation"].as(AttributeVec4) is lazy
732
733 # Rotation matrix, row 0
734 var rotation_row0 = attributes["rotation_row0"].as(AttributeVec4) is lazy
735
736 # Rotation matrix, row 1
737 var rotation_row1 = attributes["rotation_row1"].as(AttributeVec4) is lazy
738
739 # Rotation matrix, row 2
740 var rotation_row2 = attributes["rotation_row2"].as(AttributeVec4) is lazy
741
742 # Rotation matrix, row 3
743 var rotation_row3 = attributes["rotation_row3"].as(AttributeVec4) is lazy
744
745 # Scaling per vertex
746 var scale = attributes["scale"].as(AttributeFloat) is lazy
747
748 # Model view projection matrix
749 var mvp = uniforms["mvp"].as(UniformMat4) is lazy
750
751 # World time, in seconds
752 var time = uniforms["time"].as(UniformFloat) is lazy
753
754 # ---
755 # Animations
756
757 # Texture of all the frames of the animation
758 var animation_texture = uniforms["animation"].as(UniformSampler2D) is lazy
759
760 # Frame per second of the animation
761 var animation_fps = attributes["a_fps"].as(AttributeFloat) is lazy
762
763 # Number of frames in the animation
764 var animation_n_frames = attributes["a_n_frames"].as(AttributeFloat) is lazy
765
766 # Coordinates of each frame (mush be shared by all frames)
767 var animation_coord = attributes["a_coord"].as(AttributeVec2) is lazy
768
769 # Texture coordinates of the first frame
770 var animation_tex_coord = attributes["a_tex_coord"].as(AttributeVec2) is lazy
771
772 # Coordinate difference between each frame
773 var animation_tex_diff = attributes["a_tex_diff"].as(AttributeVec2) is lazy
774
775 # Animation start time, in seconds and in reference to `dt`
776 var animation_start = attributes["a_start"].as(AttributeFloat) is lazy
777
778 # Number of loops of the animation, -1 for infinite
779 var animation_loops = attributes["a_loops"].as(AttributeFloat) is lazy
780 end
781
782 redef class Point3d[N]
783 # ---
784 # Associate each point to its sprites
785
786 private var sprites: nullable Array[Sprite] = null
787
788 private fun sprites_add(sprite: Sprite)
789 do
790 var sprites = sprites
791 if sprites == null then
792 sprites = new Array[Sprite]
793 self.sprites = sprites
794 end
795 sprites.add sprite
796 end
797
798 private fun sprites_remove(sprite: Sprite)
799 do
800 var sprites = sprites
801 assert sprites != null
802 sprites.remove sprite
803 end
804
805 # ---
806 # Notify `sprites` on attribute modification
807
808 private fun needs_update
809 do
810 var sprites = sprites
811 if sprites != null then for s in sprites do s.needs_update
812 end
813
814 redef fun x=(v)
815 do
816 if isset _x and v != x then needs_update
817 super
818 end
819
820 redef fun y=(v)
821 do
822 if isset _y and v != y then needs_update
823 super
824 end
825
826 redef fun z=(v)
827 do
828 if isset _z and v != z then needs_update
829 super
830 end
831 end
832
833 redef class OffsetPoint3d
834 redef fun x=(v)
835 do
836 if isset _x and v != x then needs_update
837 super
838 end
839
840 redef fun y=(v)
841 do
842 if isset _y and v != y then needs_update
843 super
844 end
845
846 redef fun z=(v)
847 do
848 if isset _z and v != z then needs_update
849 super
850 end
851 end
852
853 # Set of sprites sorting them into different `SpriteContext`
854 private class SpriteSet
855 super HashSet[Sprite]
856
857 # Map texture then static vs dynamic to a `SpriteContext`
858 var contexts_map = new HashMap3[RootTexture, nullable RootTexture, Bool, Array[SpriteContext]]
859
860 # Contexts in `contexts_map`
861 var contexts_items = new Array[SpriteContext]
862
863 # Sprites needing resorting in `contexts_map`
864 var sprites_to_remap = new Array[Sprite]
865
866 # Animation speed multiplier (0.0 to pause, 1.0 for normal speed, etc.)
867 var time_mod = 1.0 is writable
868
869 # Seconds elapsed since the launch of the program, in world time responding to `time_mod`
870 var time = 0.0
871
872 # Add a sprite to the appropriate context
873 fun map_sprite(sprite: Sprite)
874 do
875 assert sprite.context == null else print_error "Sprite {sprite} belongs to another SpriteSet"
876
877 # Sort by texture and animation texture
878 var texture = sprite.texture.root
879 var animation = sprite.animation
880 var animation_texture = if animation != null then
881 animation.frames.first.root else null
882 var contexts = contexts_map[texture, animation_texture, sprite.static]
883
884 var context = null
885 if contexts != null then
886 for c in contexts.reverse_iterator do
887 var size = c.sprites.length + 1
888 if size * 4 <= 0xffff then
889 context = c
890 break
891 end
892 end
893 end
894
895 if context == null then
896 var usage = if sprite.static then gl_STATIC_DRAW else gl_DYNAMIC_DRAW
897 context = new SpriteContext(texture, animation_texture, usage)
898
899 if contexts == null then
900 contexts = new Array[SpriteContext]
901 contexts_map[texture, animation_texture, sprite.static] = contexts
902 end
903
904 contexts.add context
905 contexts_items.add context
906 end
907
908 context.sprites.add sprite
909 context.sprites_to_update.add sprite
910 context.last_sprite_to_update = sprite
911
912 sprite.context = context
913 sprite.sprite_set = self
914
915 if animation != null and sprite.animation_start == -1.0 then
916 # Start animation
917 sprite.animation_start = time
918 end
919 end
920
921 # Remove a sprite from its context
922 fun unmap_sprite(sprite: Sprite)
923 do
924 var context = sprite.context
925 assert context != null
926 context.sprites.remove sprite
927
928 sprite.context = null
929 sprite.sprite_set = null
930 end
931
932 # Draw all sprites by all contexts
933 fun draw
934 do
935 for sprite in sprites_to_remap do
936 unmap_sprite sprite
937 map_sprite sprite
938 end
939 sprites_to_remap.clear
940
941 for context in contexts_items do context.draw
942 end
943
944 redef fun add(e)
945 do
946 if contexts_items.has(e.context) then return
947 map_sprite e
948 super
949 end
950
951 redef fun remove(e)
952 do
953 super
954 if e isa Sprite then unmap_sprite e
955 end
956
957 redef fun remove_all(e)
958 do
959 if not has(e) then return
960 remove e
961 end
962
963 redef fun clear
964 do
965 for sprite in self do
966 sprite.context = null
967 sprite.sprite_set = null
968 end
969 super
970 for c in contexts_items do c.destroy
971 contexts_map.clear
972 contexts_items.clear
973 end
974 end
975
976 # Context for calls to `glDrawElements`
977 #
978 # Each context has only one `texture` and `usage`, but many sprites.
979 private class SpriteContext
980
981 # ---
982 # Context config and state
983
984 # Only root texture drawn by this context
985 var texture: nullable RootTexture
986
987 # Only animation texture drawn by this context
988 var animation_texture: nullable RootTexture
989
990 # OpenGL ES usage of `buffer_array` and `buffer_element`
991 var usage: GLBufferUsage
992
993 # Sprites drawn by this context
994 var sprites = new GroupedSprites
995
996 # Sprites to update since last `draw`
997 var sprites_to_update = new Set[Sprite]
998
999 # Cache of the last `Sprite` added to `sprites_to_update` since the last call to `draw`
1000 var last_sprite_to_update: nullable Sprite = null
1001
1002 # Sprites that have been update and for which `needs_update` can be set to false
1003 var updated_sprites = new Array[Sprite]
1004
1005 # Buffer size to preallocate at `resize`, multiplied by `sprites.length`
1006 #
1007 # Require: `resize_ratio >= 1.0`
1008 var resize_ratio = 1.2
1009
1010 # ---
1011 # OpenGL ES data
1012
1013 # OpenGL ES buffer name for vertex data
1014 var buffer_array: Int = -1
1015
1016 # OpenGL ES buffer name for indices
1017 var buffer_element: Int = -1
1018
1019 # Current capacity, in sprites, of `buffer_array` and `buffer_element`
1020 var buffer_capacity = 0
1021
1022 # C buffers used to pass the data of a single sprite
1023 var local_data_buffer = new GLfloatArray(float_per_vertex*4) is lazy
1024 var local_indices_buffer = new CUInt16Array(indices_per_sprite) is lazy
1025
1026 # ---
1027 # Constants
1028
1029 # Number of GL_FLOAT per vertex of `Simple2dProgram`
1030 var float_per_vertex: Int is lazy do
1031 return 4 + 4 + 4 + # vec4 translation, vec4 color, vec4 coord,
1032 1 + 2 + 4*4 + # float scale, vec2 tex_coord, vec4 rotation_row*,
1033 1 + 1 + # float a_fps, float a_n_frames,
1034 2 + 2 + 2 + # vec2 a_coord, vec2 a_tex_coord, vec2 a_tex_diff,
1035 1 + 1 # float a_start, float a_loops
1036 end
1037
1038 # Number of bytes per vertex of `Simple2dProgram`
1039 var bytes_per_vertex: Int is lazy do
1040 var fs = 4 # sizeof(GL_FLOAT)
1041 return fs * float_per_vertex
1042 end
1043
1044 # Number of bytes per sprite
1045 var bytes_per_sprite: Int is lazy do return bytes_per_vertex * 4
1046
1047 # Number of vertex indices per sprite draw call (2 triangles)
1048 var indices_per_sprite = 6
1049
1050 # ---
1051 # Main services
1052
1053 # Allocate `buffer_array` and `buffer_element`
1054 fun prepare
1055 do
1056 var bufs = glGenBuffers(2)
1057 buffer_array = bufs[0]
1058 buffer_element = bufs[1]
1059
1060 var gl_error = glGetError
1061 assert gl_error == gl_NO_ERROR else print_error gl_error
1062 end
1063
1064 # Destroy `buffer_array` and `buffer_element`
1065 fun destroy
1066 do
1067 glDeleteBuffers([buffer_array, buffer_element])
1068 var gl_error = glGetError
1069 assert gl_error == gl_NO_ERROR else print_error gl_error
1070
1071 buffer_array = -1
1072 buffer_element = -1
1073 end
1074
1075 # Resize `buffer_array` and `buffer_element` to fit all `sprites` (and more)
1076 fun resize
1077 do
1078 app.perf_clock_sprites.lapse
1079
1080 # Allocate a bit more space
1081 var capacity = (sprites.capacity.to_f * resize_ratio).to_i
1082
1083 var array_bytes = capacity * bytes_per_sprite
1084 glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
1085 assert glIsBuffer(buffer_array)
1086 glBufferData(gl_ARRAY_BUFFER, array_bytes, new Pointer.nul, usage)
1087 var gl_error = glGetError
1088 assert gl_error == gl_NO_ERROR else print_error gl_error
1089
1090 # GL_TRIANGLES 6 vertices * sprite
1091 var n_indices = capacity * indices_per_sprite
1092 var ius = 2 # sizeof(GL_UNSIGNED_SHORT)
1093 var element_bytes = n_indices * ius
1094 glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
1095 assert glIsBuffer(buffer_element)
1096 glBufferData(gl_ELEMENT_ARRAY_BUFFER, element_bytes, new Pointer.nul, usage)
1097 gl_error = glGetError
1098 assert gl_error == gl_NO_ERROR else print_error gl_error
1099
1100 buffer_capacity = capacity
1101
1102 sys.perfs["gamnit flat gpu resize"].add app.perf_clock_sprites.lapse
1103 end
1104
1105 # Update GPU data of `sprite`
1106 fun update_sprite(sprite: Sprite)
1107 do
1108 var context = sprite.context
1109 if context != self then return
1110
1111 var sprite_index = sprite.context_index
1112 assert sprite_index != -1
1113
1114 # Vertices data
1115
1116 var data = local_data_buffer
1117 var o = 0
1118 for v in [0..4[ do
1119 # vec4 translation
1120 data[o+ 0] = sprite.center.x
1121 data[o+ 1] = sprite.center.y
1122 data[o+ 2] = sprite.center.z
1123 data[o+ 3] = 0.0
1124
1125 # vec4 color
1126 data[o+ 4] = sprite.tint[0]
1127 data[o+ 5] = sprite.tint[1]
1128 data[o+ 6] = sprite.tint[2]
1129 data[o+ 7] = sprite.tint[3]
1130
1131 # float scale
1132 data[o+ 8] = sprite.scale
1133
1134 # vec4 coord
1135 data[o+ 9] = sprite.texture.vertices[v*3+0]
1136 data[o+10] = sprite.texture.vertices[v*3+1]
1137 data[o+11] = sprite.texture.vertices[v*3+2]
1138 data[o+12] = 0.0
1139
1140 # vec2 tex_coord
1141 var texture = texture
1142 if texture != null then
1143 var tc = if sprite.invert_x then
1144 sprite.texture.texture_coords_invert_x
1145 else sprite.texture.texture_coords
1146 data[o+13] = tc[v*2+0]
1147 data[o+14] = tc[v*2+1]
1148 end
1149
1150 # mat4 rotation
1151 var rot
1152 if sprite.rotation == 0.0 then
1153 # Cache the matrix at no rotation
1154 rot = once new Matrix.identity(4)
1155 else
1156 rot = new Matrix.rotation(sprite.rotation, 0.0, 0.0, 1.0)
1157 end
1158 data.fill_from_matrix(rot, o+15)
1159
1160 var animation = sprite.animation
1161 if animation == null then
1162 for i in [31..40] do data[o+i] = 0.0
1163 else
1164 # a_fps
1165 data[o+31] = animation.fps
1166
1167 # a_n_frames
1168 data[o+32] = animation.frames.length.to_f
1169
1170 # a_coord
1171 data[o+33] = animation.frames.first.vertices[v*3+0]
1172 data[o+34] = animation.frames.first.vertices[v*3+1]
1173
1174 # a_tex_coord
1175 var tc = if sprite.invert_x then
1176 animation.frames.first.texture_coords_invert_x
1177 else animation.frames.first.texture_coords
1178 data[o+35] = tc[v*2]
1179 data[o+36] = tc[v*2+1]
1180
1181 # a_tex_diff
1182 var dx = animation.frames[1].texture_coords[0] - animation.frames[0].texture_coords[0]
1183 var dy = animation.frames[1].texture_coords[1] - animation.frames[0].texture_coords[1]
1184 data[o+37] = dx
1185 data[o+38] = dy
1186
1187 # a_start
1188 data[o+39] = sprite.animation_start
1189
1190 # a_loops
1191 data[o+40] = sprite.animation_loops
1192 end
1193
1194 o += float_per_vertex
1195 end
1196
1197 glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
1198 glBufferSubData(gl_ARRAY_BUFFER, sprite_index*bytes_per_sprite, bytes_per_sprite, data.native_array)
1199
1200 var gl_error = glGetError
1201 assert gl_error == gl_NO_ERROR else print_error gl_error
1202
1203 # Element / indices
1204 #
1205 # 0--1
1206 # | /|
1207 # |/ |
1208 # 2--3
1209
1210 var indices = local_indices_buffer
1211 var io = sprite_index*4
1212 indices[0] = io+0
1213 indices[1] = io+2
1214 indices[2] = io+1
1215 indices[3] = io+1
1216 indices[4] = io+2
1217 indices[5] = io+3
1218
1219 glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
1220 glBufferSubData(gl_ELEMENT_ARRAY_BUFFER, sprite_index*6*2, 6*2, indices.native_array)
1221
1222 gl_error = glGetError
1223 assert gl_error == gl_NO_ERROR else print_error gl_error
1224 end
1225
1226 # Draw all `sprites`
1227 #
1228 # Call `resize` and `update_sprite` as needed before actual draw operation.
1229 #
1230 # Require: `app.simple_2d_program` and `mvp` must be bound on the GPU
1231 fun draw
1232 do
1233 if buffer_array == -1 then prepare
1234
1235 assert buffer_array > 0 and buffer_element > 0 else
1236 print_error "Internal error: {self} was destroyed"
1237 end
1238
1239 # Setup
1240 glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
1241 glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
1242
1243 # Resize GPU buffers?
1244 var update_everything = false
1245 if sprites.capacity > buffer_capacity then
1246 # Try to defragment first
1247 var moved = sprites.defragment
1248
1249 if sprites.capacity > buffer_capacity then
1250 # Defragmentation wasn't enough, grow
1251 resize
1252
1253 # We must update everything
1254 update_everything = true
1255 for s in sprites.items do if s != null then sprites_to_update.add s
1256 else
1257 # Just update the moved sprites
1258 for s in moved do sprites_to_update.add s
1259 end
1260 else if sprites.available.not_empty then
1261 # Defragment a bit anyway
1262 # TODO defrag only when there's time left on a frame
1263 var moved = sprites.defragment(1)
1264 for s in moved do sprites_to_update.add s
1265 end
1266
1267 # Update GPU sprites data
1268 if sprites_to_update.not_empty or update_everything then
1269 app.perf_clock_sprites.lapse
1270
1271 if update_everything then
1272 for sprite in sprites.items do if sprite != null then
1273 update_sprite(sprite)
1274 end
1275 else
1276 for sprite in sprites_to_update do update_sprite(sprite)
1277 end
1278
1279 sprites_to_update.clear
1280 last_sprite_to_update = null
1281
1282 sys.perfs["gamnit flat gpu update"].add app.perf_clock_sprites.lapse
1283 end
1284
1285 # Update uniforms specific to this context
1286 var texture = texture
1287 app.simple_2d_program.use_texture.uniform texture != null
1288 if texture != null then
1289 glActiveTexture gl_TEXTURE0
1290 glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
1291 app.simple_2d_program.texture.uniform 0
1292 end
1293 var gl_error = glGetError
1294 assert gl_error == gl_NO_ERROR else print_error gl_error
1295
1296 var animation = animation_texture
1297 if animation != null then
1298 glActiveTexture gl_TEXTURE1
1299 glBindTexture(gl_TEXTURE_2D, animation.gl_texture)
1300 app.simple_2d_program.animation_texture.uniform 1
1301 end
1302 gl_error = glGetError
1303 assert gl_error == gl_NO_ERROR else print_error gl_error
1304
1305 # Configure attributes, in order:
1306 # vec4 translation, vec4 color, float scale, vec4 coord, vec2 tex_coord, vec4 rotation_row*,
1307 # a_fps, a_n_frames, a_coord, a_tex_coord, a_tex_diff, a_start, a_loops
1308
1309 var offset = 0
1310 var p = app.simple_2d_program
1311 var sizeof_gl_float = 4 # sizeof(GL_FLOAT)
1312
1313 var size = 4 # Number of floats
1314 glEnableVertexAttribArray p.translation.location
1315 glVertexAttribPointeri(p.translation.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1316 offset += size * sizeof_gl_float
1317 gl_error = glGetError
1318 assert gl_error == gl_NO_ERROR else print_error gl_error
1319
1320 size = 4
1321 glEnableVertexAttribArray p.color.location
1322 glVertexAttribPointeri(p.color.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1323 offset += size * sizeof_gl_float
1324 gl_error = glGetError
1325 assert gl_error == gl_NO_ERROR else print_error gl_error
1326
1327 size = 1
1328 glEnableVertexAttribArray p.scale.location
1329 glVertexAttribPointeri(p.scale.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1330 offset += size * sizeof_gl_float
1331 gl_error = glGetError
1332 assert gl_error == gl_NO_ERROR else print_error gl_error
1333
1334 size = 4
1335 glEnableVertexAttribArray p.coord.location
1336 glVertexAttribPointeri(p.coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1337 offset += size * sizeof_gl_float
1338 gl_error = glGetError
1339 assert gl_error == gl_NO_ERROR else print_error gl_error
1340
1341 size = 2
1342 glEnableVertexAttribArray p.tex_coord.location
1343 glVertexAttribPointeri(p.tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1344 offset += size * sizeof_gl_float
1345 gl_error = glGetError
1346 assert gl_error == gl_NO_ERROR else print_error gl_error
1347
1348 size = 4
1349 for r in [p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3] do
1350 if r.is_active then
1351 glEnableVertexAttribArray r.location
1352 glVertexAttribPointeri(r.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1353 end
1354 offset += size * sizeof_gl_float
1355 gl_error = glGetError
1356 assert gl_error == gl_NO_ERROR else print_error gl_error
1357 end
1358
1359 size = 1
1360 glEnableVertexAttribArray p.animation_fps.location
1361 glVertexAttribPointeri(p.animation_fps.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1362 offset += size * sizeof_gl_float
1363 gl_error = glGetError
1364 assert gl_error == gl_NO_ERROR else print_error gl_error
1365
1366 size = 1
1367 glEnableVertexAttribArray p.animation_n_frames.location
1368 glVertexAttribPointeri(p.animation_n_frames.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1369 offset += size * sizeof_gl_float
1370 gl_error = glGetError
1371 assert gl_error == gl_NO_ERROR else print_error gl_error
1372
1373 size = 2
1374 glEnableVertexAttribArray p.animation_coord.location
1375 glVertexAttribPointeri(p.animation_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1376 offset += size * sizeof_gl_float
1377 gl_error = glGetError
1378 assert gl_error == gl_NO_ERROR else print_error gl_error
1379
1380 size = 2
1381 glEnableVertexAttribArray p.animation_tex_coord.location
1382 glVertexAttribPointeri(p.animation_tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1383 offset += size * sizeof_gl_float
1384 gl_error = glGetError
1385 assert gl_error == gl_NO_ERROR else print_error gl_error
1386
1387 size = 2
1388 glEnableVertexAttribArray p.animation_tex_diff.location
1389 glVertexAttribPointeri(p.animation_tex_diff.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1390 offset += size * sizeof_gl_float
1391 gl_error = glGetError
1392 assert gl_error == gl_NO_ERROR else print_error gl_error
1393
1394 size = 1
1395 glEnableVertexAttribArray p.animation_start.location
1396 glVertexAttribPointeri(p.animation_start.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1397 offset += size * sizeof_gl_float
1398 gl_error = glGetError
1399 assert gl_error == gl_NO_ERROR else print_error gl_error
1400
1401 size = 1
1402 glEnableVertexAttribArray p.animation_loops.location
1403 glVertexAttribPointeri(p.animation_loops.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
1404 offset += size * sizeof_gl_float
1405 gl_error = glGetError
1406 assert gl_error == gl_NO_ERROR else print_error gl_error
1407
1408 # Actual draw
1409 for s in sprites.starts, e in sprites.ends do
1410 var l = e-s
1411 glDrawElementsi(gl_TRIANGLES, l*indices_per_sprite, gl_UNSIGNED_SHORT, 2*s*indices_per_sprite)
1412 gl_error = glGetError
1413 assert gl_error == gl_NO_ERROR else print_error gl_error
1414 end
1415
1416 # Take down
1417 for attr in [p.translation, p.color, p.scale, p.coord, p.tex_coord,
1418 p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3: Attribute] do
1419 if not attr.is_active then continue
1420 glDisableVertexAttribArray(attr.location)
1421 gl_error = glGetError
1422 assert gl_error == gl_NO_ERROR else print_error gl_error
1423 end
1424
1425 glBindBuffer(gl_ARRAY_BUFFER, 0)
1426 glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, 0)
1427 gl_error = glGetError
1428 assert gl_error == gl_NO_ERROR else print_error gl_error
1429 end
1430 end
1431
1432 # Representation of sprite data on the GPU
1433 #
1434 # The main purpose of this class is to optimize the use of contiguous
1435 # space in GPU memory. Each contiguous memory block can be drawn in a
1436 # single call. The starts index of each block is kept by `starts,
1437 # and the end + 1 by `ends`.
1438 #
1439 # The data can be compressed by a call to `defragment`.
1440 #
1441 # ~~~
1442 # intrude import gamnit::flat
1443 #
1444 # var array = new GroupedArray[String]
1445 # assert array.to_s == ""
1446 #
1447 # array.add "a"
1448 # array.add "b"
1449 # array.add "c"
1450 # array.add "d"
1451 # array.add "e"
1452 # array.add "f"
1453 # assert array.to_s == "[a,b,c,d,e,f]"
1454 # assert array.capacity == 6
1455 #
1456 # array.remove "a"
1457 # assert array.to_s == "[b,c,d,e,f]"
1458 #
1459 # array.remove "b"
1460 # assert array.to_s == "[c,d,e,f]"
1461 #
1462 # array.remove "f"
1463 # assert array.to_s == "[c,d,e]"
1464 #
1465 # array.remove "d"
1466 # assert array.to_s == "[c][e]"
1467 #
1468 # array.add "A"
1469 # assert array.to_s == "[A][c][e]"
1470 #
1471 # array.add "B"
1472 # assert array.to_s == "[A,B,c][e]"
1473 #
1474 # array.remove "e"
1475 # assert array.to_s == "[A,B,c]"
1476 #
1477 # array.add "D"
1478 # assert array.to_s == "[A,B,c,D]"
1479 #
1480 # array.add "E"
1481 # assert array.to_s == "[A,B,c,D,E]"
1482 # assert array.capacity == 6
1483 # assert array.length == 5
1484 #
1485 # array.remove "A"
1486 # array.remove "B"
1487 # array.remove "c"
1488 # array.remove "D"
1489 # array.remove "E"
1490 # assert array.to_s == ""
1491 #
1492 # array.add "a"
1493 # assert array.to_s == "[a]"
1494 # ~~~
1495 private class GroupedArray[E]
1496
1497 # Memory with actual objects, and null in empty slots
1498 var items = new Array[nullable E]
1499
1500 # Number of items in the array
1501 var length = 0
1502
1503 # Number of item slots in the array
1504 fun capacity: Int do return items.length
1505
1506 # List of available slots
1507 var available = new MinHeap[Int].default
1508
1509 # Start index of filled chunks
1510 var starts = new List[Int]
1511
1512 # Index of the spots after filled chunks
1513 var ends = new List[Int]
1514
1515 # Add `item` to the first available slot and return its index
1516 fun add(item: E): Int
1517 do
1518 length += 1
1519
1520 if available.not_empty then
1521 # starts & ends can't be empty
1522
1523 var i = available.take
1524 items[i] = item
1525
1526 if i == starts.first - 1 then
1527 # slot 0 free, 1 taken
1528 starts.first -= 1
1529 else if i == 0 then
1530 # slot 0 and more free
1531 starts.unshift 0
1532 ends.unshift 1
1533 else if starts.length >= 2 and ends.first + 1 == starts[1] then
1534 # merge 2 chunks
1535 ends.remove_at 0
1536 starts.remove_at 1
1537 else
1538 # at end of first chunk
1539 ends.first += 1
1540 end
1541 return i
1542 end
1543
1544 items.add item
1545 if ends.is_empty then
1546 starts.add 0
1547 ends.add 1
1548 else ends.last += 1
1549 return ends.last - 1
1550 end
1551
1552 # Remove the first instance of `item`
1553 fun remove(item: E)
1554 do
1555 var index = items.index_of(item)
1556 remove_at(item, index)
1557 end
1558
1559 # Remove `item` at `index`
1560 fun remove_at(item: E, index: Int)
1561 do
1562 var i = index
1563 length -= 1
1564 items[i] = null
1565
1566 var ii = 0
1567 for s in starts, e in ends do
1568 if s <= i and i < e then
1569 if s == e-1 then
1570 # single item chunk
1571 starts.remove_at ii
1572 ends.remove_at ii
1573
1574 if starts.is_empty then
1575 items.clear
1576 available.clear
1577 return
1578 end
1579 else if e-1 == i then
1580 # last item of chunk
1581 ends[ii] -= 1
1582
1583 else if s == i then
1584 # first item of chunk
1585 starts[ii] += 1
1586 else
1587 # break up chunk
1588 ends.insert(ends[ii], ii+1)
1589 ends[ii] = i
1590 starts.insert(i+1, ii+1)
1591 end
1592
1593 available.add i
1594 return
1595 end
1596 ii += 1
1597 end
1598
1599 abort
1600 end
1601
1602 # Defragment and compress everything into a single chunks beginning at 0
1603 #
1604 # Returns the elements that moved as a list.
1605 #
1606 # ~~~
1607 # intrude import gamnit::flat
1608 #
1609 # var array = new GroupedArray[String]
1610 # array.add "a"
1611 # array.add "b"
1612 # array.add "c"
1613 # array.add "d"
1614 # array.remove "c"
1615 # array.remove "a"
1616 # assert array.to_s == "[b][d]"
1617 #
1618 # var moved = array.defragment
1619 # assert moved.to_s == "[d]"
1620 # assert array.to_s == "[d,b]"
1621 # assert array.length == 2
1622 # assert array.capacity == 2
1623 #
1624 # array.add "e"
1625 # array.add "f"
1626 # assert array.to_s == "[d,b,e,f]"
1627 # ~~~
1628 fun defragment(max: nullable Int): Array[E]
1629 do
1630 app.perf_clock_sprites.lapse
1631 max = max or else length
1632
1633 var moved = new Array[E]
1634 while max > 0 and (starts.length > 1 or starts.first != 0) do
1635 var i = ends.last - 1
1636 var e = items[i]
1637 assert e != null
1638 remove e
1639 add e
1640 moved.add e
1641 max -= 1
1642 end
1643
1644 if starts.length == 1 and starts.first == 0 then
1645 for i in [length..capacity[ do items.pop
1646 available.clear
1647 end
1648
1649 sys.perfs["gamnit flat gpu defrag"].add app.perf_clock_sprites.lapse
1650 return moved
1651 end
1652
1653 redef fun to_s
1654 do
1655 var ss = new Array[String]
1656 for s in starts, e in ends do
1657 ss.add "["
1658 for i in [s..e[ do
1659 var item: nullable Object = items[i]
1660 if item == null then item = "null"
1661 ss.add item.to_s
1662 if i != e-1 then ss.add ","
1663 end
1664 ss.add "]"
1665 end
1666 return ss.join
1667 end
1668 end
1669
1670 # Optimized `GroupedArray` to use `Sprite::context_index` and avoid using `index_of`
1671 private class GroupedSprites
1672 super GroupedArray[Sprite]
1673
1674 redef fun add(item)
1675 do
1676 var index = super
1677 item.context_index = index
1678 return index
1679 end
1680
1681 redef fun remove(item) do remove_at(item, item.context_index)
1682 end
1683
1684 redef class GLfloatArray
1685 private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
1686 do
1687 dst_offset = dst_offset or else 0
1688 var mat_len = matrix.width*matrix.height
1689 assert length >= mat_len + dst_offset
1690 native_array.fill_from_matrix_native(matrix.items, dst_offset, mat_len)
1691 end
1692 end
1693
1694 redef class NativeGLfloatArray
1695 private fun fill_from_matrix_native(matrix: matrix::NativeDoubleArray, dst_offset, len: Int) `{
1696 int i;
1697 for (i = 0; i < len; i ++)
1698 self[i+dst_offset] = (GLfloat)matrix[i];
1699 `}
1700 end