Merge branch 'explain-assert' into master
[nit.git] / lib / gamnit / flat / flat_core.nit
index cd1d11d..320d471 100644 (file)
@@ -26,8 +26,6 @@ import gamnit
 intrude import gamnit::cameras
 intrude import gamnit::cameras_cache
 import gamnit::dynamic_resolution
-import gamnit::limit_fps
-import gamnit::camera_control
 
 # Visible 2D entity in the game world or UI
 #
@@ -82,27 +80,27 @@ class Sprite
        var texture: Texture is writable(texture_direct=)
 
        # Texture drawn to screen
-       fun texture=(value: Texture)
+       fun texture=(texture: Texture)
        do
-               if isset _texture and value != texture then
+               if isset _texture and texture != self.texture then
                        needs_update
-                       if value.root != texture.root then needs_remap
+                       if texture.root != self.texture.root then needs_remap
                end
-               texture_direct = value
+               texture_direct = texture
        end
 
        # Center position of this sprite in world coordinates
        var center: Point3d[Float] is writable(center_direct=), noautoinit
 
        # Center position of this sprite in world coordinates
-       fun center=(value: Point3d[Float]) is autoinit do
-               if isset _center and value != center then
+       fun center=(center: Point3d[Float]) is autoinit do
+               if isset _center and center != self.center then
                        needs_update
-                       center.sprites_remove self
+                       self.center.sprites_remove self
                end
 
-               value.sprites_add self
-               center_direct = value
+               center.sprites_add self
+               center_direct = center
        end
 
        # Last animation set with `animate`
@@ -243,6 +241,32 @@ class Sprite
                tint_direct = value
        end
 
+       # Draw order, higher values cause this sprite to be drawn latter
+       #
+       # Change this value to avoid artifacts when drawing non-opaque sprites.
+       # In general, sprites with a non-opaque `texture` and sprites closer to
+       # the camera should have a higher value to be drawn last.
+       #
+       # Sprites sharing a `draw_order` are drawn in the same pass.
+       # The sprite to sprite draw order is undefined and may change when adding
+       # and removing sprites, or changing their attributes.
+       #
+       # ### Warning
+       #
+       # Changing this value may have a negative performance impact if there are
+       # many different `draw_order` values across many sprites.
+       # Sprites sharing some attributes are drawn as group to reduce
+       # the communication overhead between the CPU and GPU,
+       # and changing `draw_order` may break up large groups into smaller groups.
+       var draw_order = 0 is writable(draw_order_direct=)
+
+       # Set draw order,  see `draw_order`
+       fun draw_order=(value: Int)
+       do
+               if isset _draw_order and value != draw_order then needs_remap
+               draw_order_direct = value
+       end
+
        # Is this sprite static and added in bulk?
        #
        # Set to `true` to give a hint to the framework that this sprite won't
@@ -354,10 +378,10 @@ redef class App
        var ui_camera = new UICamera(app.display.as(not null)) is lazy
 
        # World sprites drawn as seen by `world_camera`
-       var sprites: Set[Sprite] = new SpriteSet
+       var sprites = new SpriteSet
 
        # UI sprites drawn as seen by `ui_camera`, over world `sprites`
-       var ui_sprites: Set[Sprite] = new SpriteSet
+       var ui_sprites = new SpriteSet
 
        # Main method to refine in clients to update game logic and `sprites`
        fun update(dt: Float) do end
@@ -375,6 +399,9 @@ redef class App
                var display = display
                assert display != null
                glClear gl_COLOR_BUFFER_BIT
+
+               ui_camera.reset_height 1080.0
+               glViewport(0, 0, display.width, display.height)
                frame_core_ui_sprites display
                display.flip
 
@@ -393,15 +420,19 @@ redef class App
        # Second performance clock for smaller operations
        private var perf_clock_sprites = new Clock is lazy
 
-       redef fun on_create
+       redef fun create_gamnit
        do
                super
+               create_flat
+       end
 
+       # Prepare the flat framework services
+       fun create_flat
+       do
                var display = display
                assert display != null
 
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Prepare program
                var program = simple_2d_program
@@ -423,8 +454,7 @@ redef class App
                glViewport(0, 0, display.width, display.height)
                glClearColor(0.0, 0.0, 0.0, 1.0)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Prepare to draw
                for tex in all_root_textures do
@@ -435,12 +465,14 @@ redef class App
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
                end
+
+               sprites.reset
+               ui_sprites.reset
        end
 
        redef fun on_stop
        do
-               # Clean up
-               simple_2d_program.delete
+               super
 
                # Close gamnit
                var display = display
@@ -458,11 +490,16 @@ redef class App
                for sprite in ui_sprites do sprite.needs_update
        end
 
+       redef fun on_resume
+       do
+               clock.lapse
+               super
+       end
+
        redef fun frame_core(display)
        do
                # Check errors
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Update game logic and set sprites
                perf_clock_main.lapse
@@ -476,8 +513,7 @@ redef class App
                display.flip
 
                # Check errors
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        private var frame_dt = 0.0
@@ -508,12 +544,14 @@ redef class App
 
                # draw
                sprite_set.draw
+
+               assert glGetError == gl_NO_ERROR
        end
 
        # Draw world sprites from `sprites`
        protected fun frame_core_world_sprites(display: GamnitDisplay)
        do
-               frame_core_sprites(display, sprites.as(SpriteSet), world_camera)
+               frame_core_sprites(display, sprites, world_camera)
        end
 
        # Draw UI sprites from `ui_sprites`
@@ -522,7 +560,7 @@ redef class App
                # Reset only the depth buffer
                glClear gl_DEPTH_BUFFER_BIT
 
-               frame_core_sprites(display, ui_sprites.as(SpriteSet), ui_camera)
+               frame_core_sprites(display, ui_sprites, ui_camera)
        end
 end
 
@@ -663,7 +701,7 @@ private class Simple2dProgram
                        vec3 c; // coords
 
                        float end = a_start + a_loops/a_fps*a_n_frames;
-                       if (a_loops == -1.0 || time < end) {
+                       if (a_fps != 0.0 && (a_loops == -1.0 || time < end)) {
                                // in animation
                                float frame = mod(floor((time-a_start)*a_fps), a_n_frames);
                                v_coord = a_tex_coord + a_tex_diff*frame;
@@ -851,26 +889,26 @@ redef class OffsetPoint3d
 end
 
 # Set of sprites sorting them into different `SpriteContext`
-private class SpriteSet
+class SpriteSet
        super HashSet[Sprite]
 
-       # Map texture then static vs dynamic to a `SpriteContext`
-       var contexts_map = new HashMap3[RootTexture, nullable RootTexture, Bool, Array[SpriteContext]]
-
-       # Contexts in `contexts_map`
-       var contexts_items = new Array[SpriteContext]
-
-       # Sprites needing resorting in `contexts_map`
-       var sprites_to_remap = new Array[Sprite]
-
        # Animation speed multiplier (0.0 to pause, 1.0 for normal speed, etc.)
        var time_mod = 1.0 is writable
 
        # Seconds elapsed since the launch of the program, in world time responding to `time_mod`
        var time = 0.0
 
+       # Map texture then static vs dynamic to a `SpriteContext`
+       private var contexts_map = new HashMap4[RootTexture, nullable RootTexture, Bool, Int, Array[SpriteContext]]
+
+       # Contexts in `contexts_map`, sorted by draw order
+       private var contexts_items = new Array[SpriteContext]
+
+       # Sprites needing resorting in `contexts_map`
+       private var sprites_to_remap = new Array[Sprite]
+
        # Add a sprite to the appropriate context
-       fun map_sprite(sprite: Sprite)
+       private fun map_sprite(sprite: Sprite)
        do
                assert sprite.context == null else print_error "Sprite {sprite} belongs to another SpriteSet"
 
@@ -879,7 +917,8 @@ private class SpriteSet
                var animation = sprite.animation
                var animation_texture = if animation != null then
                        animation.frames.first.root else null
-               var contexts = contexts_map[texture, animation_texture, sprite.static]
+               var draw_order = sprite.draw_order
+               var contexts = contexts_map[texture, animation_texture, sprite.static, draw_order]
 
                var context = null
                if contexts != null then
@@ -894,15 +933,17 @@ private class SpriteSet
 
                if context == null then
                        var usage = if sprite.static then gl_STATIC_DRAW else gl_DYNAMIC_DRAW
-                       context = new SpriteContext(texture, animation_texture, usage)
+                       context = new SpriteContext(texture, animation_texture, usage, draw_order)
 
                        if contexts == null then
                                contexts = new Array[SpriteContext]
-                               contexts_map[texture, animation_texture, sprite.static] = contexts
+                               contexts_map[texture, animation_texture, sprite.static, draw_order] = contexts
                        end
 
                        contexts.add context
+
                        contexts_items.add context
+                       sprite_draw_order_sorter.sort(contexts_items)
                end
 
                context.sprites.add sprite
@@ -919,7 +960,7 @@ private class SpriteSet
        end
 
        # Remove a sprite from its context
-       fun unmap_sprite(sprite: Sprite)
+       private fun unmap_sprite(sprite: Sprite)
        do
                var context = sprite.context
                assert context != null
@@ -930,14 +971,20 @@ private class SpriteSet
        end
 
        # Draw all sprites by all contexts
-       fun draw
+       private fun draw
        do
+               # Remap sprites that may need to change context
                for sprite in sprites_to_remap do
+
+                       # Skip if it was removed from this set after being modified
+                       if sprite.sprite_set != self then continue
+
                        unmap_sprite sprite
                        map_sprite sprite
                end
                sprites_to_remap.clear
 
+               # Sort by draw order
                for context in contexts_items do context.draw
        end
 
@@ -970,6 +1017,23 @@ private class SpriteSet
                for c in contexts_items do c.destroy
                contexts_map.clear
                contexts_items.clear
+               sprites_to_remap.clear
+       end
+
+       private fun reset
+       do
+               for sprite in self do
+                       sprite.context = null
+               end
+
+               for c in contexts_items do c.destroy
+               contexts_map.clear
+               contexts_items.clear
+               sprites_to_remap.clear
+
+               for sprite in self do
+                       map_sprite sprite
+               end
        end
 end
 
@@ -990,6 +1054,9 @@ private class SpriteContext
        # OpenGL ES usage of `buffer_array` and `buffer_element`
        var usage: GLBufferUsage
 
+       # Draw order shared by all `sprites`
+       var draw_order: Int
+
        # Sprites drawn by this context
        var sprites = new GroupedSprites
 
@@ -1057,16 +1124,14 @@ private class SpriteContext
                buffer_array = bufs[0]
                buffer_element = bufs[1]
 
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Destroy `buffer_array` and `buffer_element`
        fun destroy
        do
                glDeleteBuffers([buffer_array, buffer_element])
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                buffer_array = -1
                buffer_element = -1
@@ -1084,8 +1149,7 @@ private class SpriteContext
                glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
                assert glIsBuffer(buffer_array)
                glBufferData(gl_ARRAY_BUFFER, array_bytes, new Pointer.nul, usage)
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # GL_TRIANGLES 6 vertices * sprite
                var n_indices = capacity * indices_per_sprite
@@ -1094,8 +1158,7 @@ private class SpriteContext
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
                assert glIsBuffer(buffer_element)
                glBufferData(gl_ELEMENT_ARRAY_BUFFER, element_bytes, new Pointer.nul, usage)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                buffer_capacity = capacity
 
@@ -1179,8 +1242,12 @@ private class SpriteContext
                                data[o+36] = tc[v*2+1]
 
                                # a_tex_diff
-                               var dx = animation.frames[1].texture_coords[0] - animation.frames[0].texture_coords[0]
-                               var dy = animation.frames[1].texture_coords[1] - animation.frames[0].texture_coords[1]
+                               var dx = 0.0
+                               var dy = 0.0
+                               if animation.frames.length > 1 then
+                                       dx = animation.frames[1].texture_coords[0] - animation.frames[0].texture_coords[0]
+                                       dy = animation.frames[1].texture_coords[1] - animation.frames[0].texture_coords[1]
+                               end
                                data[o+37] = dx
                                data[o+38] = dy
 
@@ -1219,8 +1286,7 @@ private class SpriteContext
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, buffer_element)
                glBufferSubData(gl_ELEMENT_ARRAY_BUFFER, sprite_index*6*2, 6*2, indices.native_array)
 
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 
        # Draw all `sprites`
@@ -1290,8 +1356,7 @@ private class SpriteContext
                        glBindTexture(gl_TEXTURE_2D, texture.gl_texture)
                        app.simple_2d_program.texture.uniform 0
                end
-               var gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                var animation = animation_texture
                if animation != null then
@@ -1299,8 +1364,7 @@ private class SpriteContext
                        glBindTexture(gl_TEXTURE_2D, animation.gl_texture)
                        app.simple_2d_program.animation_texture.uniform 1
                end
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Configure attributes, in order:
                # vec4 translation, vec4 color, float scale, vec4 coord, vec2 tex_coord, vec4 rotation_row*,
@@ -1314,36 +1378,31 @@ private class SpriteContext
                glEnableVertexAttribArray p.translation.location
                glVertexAttribPointeri(p.translation.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                glEnableVertexAttribArray p.color.location
                glVertexAttribPointeri(p.color.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.scale.location
                glVertexAttribPointeri(p.scale.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                glEnableVertexAttribArray p.coord.location
                glVertexAttribPointeri(p.coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.tex_coord.location
                glVertexAttribPointeri(p.tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 4
                for r in [p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3] do
@@ -1352,65 +1411,56 @@ private class SpriteContext
                                glVertexAttribPointeri(r.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                        end
                        offset += size * sizeof_gl_float
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                size = 1
                glEnableVertexAttribArray p.animation_fps.location
                glVertexAttribPointeri(p.animation_fps.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_n_frames.location
                glVertexAttribPointeri(p.animation_n_frames.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_coord.location
                glVertexAttribPointeri(p.animation_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_tex_coord.location
                glVertexAttribPointeri(p.animation_tex_coord.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 2
                glEnableVertexAttribArray p.animation_tex_diff.location
                glVertexAttribPointeri(p.animation_tex_diff.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_start.location
                glVertexAttribPointeri(p.animation_start.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                size = 1
                glEnableVertexAttribArray p.animation_loops.location
                glVertexAttribPointeri(p.animation_loops.location, size, gl_FLOAT, false, bytes_per_vertex, offset)
                offset += size * sizeof_gl_float
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
 
                # Actual draw
                for s in sprites.starts, e in sprites.ends do
                        var l = e-s
                        glDrawElementsi(gl_TRIANGLES, l*indices_per_sprite, gl_UNSIGNED_SHORT, 2*s*indices_per_sprite)
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                # Take down
@@ -1418,14 +1468,12 @@ private class SpriteContext
                             p.rotation_row0, p.rotation_row1, p.rotation_row2, p.rotation_row3: Attribute] do
                        if not attr.is_active then continue
                        glDisableVertexAttribArray(attr.location)
-                       gl_error = glGetError
-                       assert gl_error == gl_NO_ERROR else print_error gl_error
+                       assert glGetError == gl_NO_ERROR
                end
 
                glBindBuffer(gl_ARRAY_BUFFER, 0)
                glBindBuffer(gl_ELEMENT_ARRAY_BUFFER, 0)
-               gl_error = glGetError
-               assert gl_error == gl_NO_ERROR else print_error gl_error
+               assert glGetError == gl_NO_ERROR
        end
 end
 
@@ -1684,10 +1732,11 @@ end
 redef class GLfloatArray
        private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
        do
-               dst_offset = dst_offset or else 0
+               dst_offset = dst_offset or else add_index
                var mat_len = matrix.width*matrix.height
                assert length >= mat_len + dst_offset
                native_array.fill_from_matrix_native(matrix.items, dst_offset, mat_len)
+               add_index += mat_len
        end
 end
 
@@ -1698,3 +1747,20 @@ redef class NativeGLfloatArray
                        self[i+dst_offset] = (GLfloat)matrix[i];
        `}
 end
+
+redef class Sys
+       private var sprite_draw_order_sorter = new DrawOrderComparator is lazy
+end
+
+# Sort `SpriteContext` by their `draw_order`
+private class DrawOrderComparator
+       super Comparator
+
+       # This class can't set COMPARED because
+       # `the public property cannot contain the private type...`
+       #redef type COMPARED: SpriteContext
+
+       # Require: `a isa SpriteContext and b isa SpriteContext`
+       redef fun compare(a, b)
+       do return a.as(SpriteContext).draw_order <=> b.as(SpriteContext).draw_order
+end