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
#
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`
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
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
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
# 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
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
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
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
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
# 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`
# 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
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;
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"
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
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
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
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
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
# 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
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
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
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
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
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`
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
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*,
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
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
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
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
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