Getting Started 6: Creating a Renderer

SleepProgger edited this page May 2, 2017 · 16 revisions

Writing a Custom Renderer

If you have any cool ideas you want to try out for a new effect you will most likely end up writing a renderer with a different vertex format, shader, and custom update behavior. KivEnt tries to make this process as simple and pain-free as possible while keeping the rendering performant as it tends to eat up a huge amount of your frame time. In this tutorial we will write our own renderer to achieve this effect:

Field of Stars

A user in #kivy wanted to animate between several starfields, I suggested that he just dynamically create the scene and he was a bit sceptical of Kivy being capable of handling enough objects to create a convincing star field. So let's do it.

In this tutorial, we will begin with the code from Getting Started 1.

The content of this tutorial covers the directories:

examples/7_star_fade

Prerequisites: You will need to have compiled kivent_core in addition to having kivy and all its requirements installed.

Considerations

With Kivy's canvas instructions directly we would do something like:

self.instructions = []
with self.canvas:
    for x in range(number_to_make):
        col = Color((1., 1., 1., alpha)
        rect = Rectangle(size=(w, h), pos=(x, y), source='texture.png')
        instructions.append((col, rect))

and then somewhere later:

for col, rect in self.instructions:
    col.a = new_alpha
    rect.pos = new_pos

However, when we are batching all our drawing into one call we can no longer do the Color as a separate instruction. Each Color instruction would apply to the entire batch, not just one entity inside of it. The alternative is to move this data into the vertex and upload it with the rest of our vertex data.

Modifying the Vertex Format

The default vertex format in KivEnt looks like this (all vertex formats can be found in kivent_core.rendering.vertex_formats.pxd:

ctypedef struct VertexFormat4F:
    GLfloat[2] pos
    GLfloat[2] uvs

Right now we receive the coordinate of the vertex, and the uv coordinate of the texture. We need to introduce a GLfloat[4] to store our colors. We should not call it 'color' because there is an existing Kivy uniform named this we expect most shaders to have.

So our new format:

ctypedef struct VertexFormat8F:
    GLfloat[2] pos
    GLfloat[2] uvs
    GLfloat[4] vColor

Now, in the vertex_formats.pyx we need to write a little code to allow us to interpret our vertex format from python. This is similar to how Kivy declares a vertex format, but contains an extra member in the (name, count, type) tuple that describes the starting position in bytes of the attribute (in C what would be the result of offsetof macro). We do not have access to the offsetof macro in cython, but we do have a slightly hack alternative. It looks like this:

from cython cimport Py_ssize_t
cdef extern from "Python.h":
    ctypedef int Py_intptr_t

cdef VertexFormat4F* tmp1 = <VertexFormat4F*>NULL
pos_offset = <Py_ssize_t> (<Py_intptr_t>(tmp1.pos) - <Py_intptr_t>(tmp1))
uvs_offset = <Py_ssize_t> (<Py_intptr_t>(tmp1.uvs) - <Py_intptr_t>(tmp1))

vertex_format_4f = [
    (b'pos', 2, b'float', pos_offset), 
    (b'uvs', 2, b'float', uvs_offset),
    ]

Let's make one for our new vertex format:

cdef VertexFormat8F* tmp3 = <VertexFormat8F*>NULL
pos_offset = <Py_ssize_t> (<Py_intptr_t>(tmp3.pos) - <Py_intptr_t>(tmp3))
uvs_offset = <Py_ssize_t> (<Py_intptr_t>(tmp3.uvs) - <Py_intptr_t>(tmp3))
color_offset = <Py_ssize_t> (<Py_intptr_t>(tmp3.vColor) - <Py_intptr_t>(tmp3))

vertex_format_8f = [
    (b'pos', 2, b'float', pos_offset), 
    (b'uvs', 2, b'float', uvs_offset),
    (b'vColor', 4, b'float', color_offset),
    ]

We need to b' the strings for Py2/3 compatibility as these should be bytestrings, not unicode since we will be sending them to GL. The format is (name, count, type, offset in struct). So for instance to represent r, g, b, a data we add a 4 float array.

Modifying the Shader

This section will assume basic knowledge of the structure of a glsl shader. If you do not know about them read here.

The default shader for KivEnt looks like this:

---VERTEX SHADER---
#ifdef GL_ES
    precision highp float;
#endif

/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;

/* vertex attributes */
attribute vec2     pos;
attribute vec2     uvs;

/* uniform variables */
uniform mat4       modelview_mat;
uniform mat4       projection_mat;
uniform vec4       color;
uniform float      opacity;

void main (void) {
  frag_color = color * vec4(1.0, 1., 1.0, opacity);
  tex_coord0 = uvs;
  vec4 new_pos = vec4(pos.xy, 0.0, 1.0);
  gl_Position = projection_mat * modelview_mat * new_pos;

}


---FRAGMENT SHADER---
#ifdef GL_ES
    precision highp float;
#endif

/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;

/* uniform texture samplers */
uniform sampler2D texture0;

void main (void){

    gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
}

We are only going to modify the vertex shader today, the fragment shader will stay the same.

First, let's introduce the new vColor attribute:

/* vertex attributes */
attribute vec2     pos;
attribute vec2     uvs;
attribute vec4     vColor;

Now, we modify the main so that frag_color takes into account our vColor:

void main (void) {
  frag_color = vColor * color * vec4(1.0, 1., 1.0, opacity);
  tex_coord0 = uvs;
  vec4 new_pos = vec4(pos.xy, 0.0, 1.0);
  gl_Position = projection_mat * modelview_mat * new_pos;

}

New shader:

---VERTEX SHADER---
#ifdef GL_ES
    precision highp float;
#endif

/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tex_coord0;

/* vertex attributes */
attribute vec2     pos;
attribute vec2     uvs;
attribute vec4     vColor;

/* uniform variables */
uniform mat4       modelview_mat;
uniform mat4       projection_mat;
uniform vec4       color;
uniform float      opacity;

void main (void) {
  frag_color = vColor * color * vec4(1.0, 1., 1.0, opacity);
  tex_coord0 = uvs;
  vec4 new_pos = vec4(pos.xy, 0.0, 1.0);
  gl_Position = projection_mat * modelview_mat * new_pos;

}


---FRAGMENT SHADER---
#ifdef GL_ES
    precision highp float;
#endif

/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tex_coord0;

/* uniform texture samplers */
uniform sampler2D texture0;

void main (void){

    gl_FragColor = frag_color * texture2D(texture0, tex_coord0);
}

Subclassing the Renderer

The basic Renderer's update looks like this, I have added comments to explain the algorithm:

    def update(self, dt):
        cdef IndexedBatch batch
        cdef list batches
        cdef unsigned int batch_key
        cdef unsigned int index_offset, vert_offset
        cdef RenderStruct* render_comp
        cdef PositionStruct2D* pos_comp
        cdef VertexFormat4F* frame_data
        cdef GLushort* frame_indices
        cdef VertMesh vert_mesh
        cdef float* mesh_data
        cdef VertexFormat4F* vertex
        cdef unsigned short* mesh_indices
        cdef unsigned int used, i, real_index, component_count

        cdef ComponentPointerAggregator entity_components
        cdef int attribute_count = self.attribute_count
        cdef BatchManager batch_manager = self.batch_manager
        cdef dict batch_groups = batch_manager.batch_groups
        cdef list meshes = model_manager.meshes
        cdef CMesh mesh_instruction
        cdef MemoryBlock components_block
        cdef void** component_data
        ##Go through every batch_key, each batch_key represents 
        ##the rendering for a single texture file.
        for batch_key in batch_groups:
            batches = batch_groups[batch_key]
            #Go through each batch in the batch_group
            for batch in batches:
                #Retrieve the ComponentPointerAggregator for this batch.
                entity_components = batch.entity_components
                #Retrieve the memory for this block
                components_block = entity_components.memory_block
                #Find out how many entities we used
                used = components_block.used_count
                #Find out how many components per entity.
                component_count = entity_components.count
                #Cast the data to its type in this case void** (an array of void*)
                component_data = <void**>components_block.data
                #Get the appropriate buffer to fill with vertex data
                frame_data = <VertexFormat4F*>batch.get_vbo_frame_to_draw()
                #Get the appropriate buffer to fill with indices data
                frame_indices = <GLushort*>batch.get_indices_frame_to_draw()
                #Keep track of the number of indices we have drawn this frame.
                index_offset = 0
                #Now for each entry in the entity components (0 to used)
                for i in range(used):
                    #Calculate the actual index, there are i sets of component_count
                    #pointers representing the components for each entity
                    real_index = i * component_count
                    #Check that the pointer is not NULL (would mean this component
                    #is inactive.
                    if component_data[real_index] == NULL:
                        continue
                    #Get the pointer to our system component and cast it to 
                    #its actual type. Each component will be at the index 
                    #of its sytem_id in the system_names ListProperty.
                    render_comp = <RenderStruct*>component_data[real_index+0]
                    #Get the offset of this entity in the vertex buffer.
                    vert_offset = render_comp.vert_index
                    #Get the actual model
                    vert_mesh = meshes[render_comp.vert_index_key]
                    #The number of vertices in the model
                    vertex_count = vert_mesh._vert_count
                    #The number of indices
                    index_count = vert_mesh._index_count
                    #Check if we should actual render:
                    if render_comp.render:
                        #Get the position component.
                        pos_comp = <PositionStruct2D*>component_data[
                            real_index+1]
                        #Get the data for the meshes vertices
                        mesh_data = vert_mesh._data
                        #and indices
                        mesh_indices = vert_mesh._indices
                        #Starting at index_offset, write the index at
                        #position i into index_offset + i of frame
                        #indices
                        for i in range(index_count):
                            frame_indices[i+index_offset] = (
                                mesh_indices[i] + vert_offset)
                        #For each vertex in the model
                        for n in range(vertex_count):
                            #Get its position in the frame data
                            vertex = &frame_data[n + vert_offset]
                            #update the position attribute with the
                            #position of the entity and the position
                            #of that vertex in the model.
                            vertex.pos[0] = pos_comp.x + mesh_data[
                                n*attribute_count]
                            vertex.pos[1] = pos_comp.y + mesh_data[
                                n*attribute_count+1]
                            #Grab the uvs for the vertex from the mesh
                            vertex.uvs[0] = mesh_data[n*attribute_count+2]
                            vertex.uvs[1] = mesh_data[n*attribute_count+3]
                        #Increment the index_offset by the number of indices
                        #used for this component.
                        index_offset += index_count
                #Tell the batch how many indices to draw next frame.
                batch.set_index_count_for_frame(index_offset)
                ##Get the actual canvas instruction and flag it for update.
                mesh_instruction = batch.mesh_instruction
                mesh_instruction.flag_update()

For the most part everything before the vertex_count loop is going to stay the same, there are a couple static declarations we need to change the type of for our new VertexFormat8F and we need to introduce the color component.

First, we declare a new class and modify some of the defaults for Renderer:

cdef class ColorRenderer(Renderer):
    ##We need to tell the default names of the components
    ##that are retrieved when processing.
    system_names = ListProperty(['color_renderer', 'position',
        'color'])
    #set the default name for this GameSystem
    system_id = StringProperty('color_renderer')
    #Tell it how big our VertexFormat struct is in bytes.
    vertex_format_size = NumericProperty(sizeof(VertexFormat8F))

Next we need to modify the initialization to take into account our new type (the VertexFormat8F struct) and the associated vertex_format_8f python readable spec for the format.

    #This function is called as part of initialization, we need to tell it 
    #Which Vertex Format to use. Everything else should stay the same as the
    #other renderers
    cdef void* setup_batch_manager(self, Buffer master_buffer) except NULL:
        cdef KEVertexFormat batch_vertex_format = KEVertexFormat(
            sizeof(VertexFormat8F), *vertex_format_8f)
        self.batch_manager = BatchManager(
            self.size_of_batches, self.max_batches, self.frame_count, 
            batch_vertex_format, master_buffer, 'triangles', self.canvas,
            [x for x in self.system_names], 
            self.smallest_vertex_count, self.gameworld)
        return <void*>self.batch_manager

Finally, we need to modify the update function:

 def update(self, dt):
        cdef IndexedBatch batch
        cdef list batches
        cdef unsigned int batch_key
        cdef unsigned int index_offset, vert_offset
        cdef RenderStruct* render_comp
        cdef PositionStruct2D* pos_comp
        #declare color_comp's type
        cdef ColorStruct* color_comp
        #Change the type of frame_data
        cdef VertexFormat8F* frame_data
        cdef GLushort* frame_indices
        cdef VertMesh vert_mesh
        cdef float* mesh_data
        #Change the type of the vertex
        cdef VertexFormat8F* vertex
        cdef unsigned short* mesh_indices
        cdef unsigned int used, i, real_index, component_count, x, y

        cdef ComponentPointerAggregator entity_components
        cdef int attribute_count = self.attribute_count
        cdef BatchManager batch_manager = self.batch_manager
        cdef dict batch_groups = batch_manager.batch_groups
        cdef list meshes = model_manager.meshes
        cdef CMesh mesh_instruction
        cdef MemoryBlock components_block
        cdef void** component_data
        for batch_key in batch_groups:
            batches = batch_groups[batch_key]
            for batch in batches:
                entity_components = batch.entity_components
                components_block = entity_components.memory_block
                used = components_block.used_count
                component_count = entity_components.count
                component_data = <void**>components_block.data
                frame_data = <VertexFormat8F*>batch.get_vbo_frame_to_draw()
                frame_indices = <GLushort*>batch.get_indices_frame_to_draw()
                index_offset = 0
                for i in range(components_block.size):
                    real_index = i * component_count
                    if component_data[real_index] == NULL:
                        continue
                    render_comp = <RenderStruct*>component_data[real_index+0]
                    vert_offset = render_comp.vert_index
                    vert_mesh = meshes[render_comp.vert_index_key]
                    vertex_count = vert_mesh._vert_count
                    index_count = vert_mesh._index_count
                    if render_comp.render:
                        pos_comp = <PositionStruct2D*>component_data[
                            real_index+1]
                        mesh_data = vert_mesh._data
                        #look up our ColorStruct too
                        color_comp = <ColorStruct*>component_data[real_index+2]
                        mesh_indices = vert_mesh._indices
                        for y in range(index_count):
                            frame_indices[y+index_offset] = (
                                mesh_indices[y] + vert_offset)
                        for n in range(vertex_count):
                            vertex = &frame_data[n + vert_offset]
                            vertex.pos[0] = pos_comp.x + (
                                mesh_data[n*attribute_count])
                            vertex.pos[1] = pos_comp.y + (
                                mesh_data[n*attribute_count+1])
                            vertex.uvs[0] = mesh_data[n*attribute_count+2]
                            vertex.uvs[1] = mesh_data[n*attribute_count+3]
                            #modify the vColor prop using the color_comp
                            vertex.vColor[0] = color_comp.r
                            vertex.vColor[1] = color_comp.g
                            vertex.vColor[2] = color_comp.b
                            vertex.vColor[3] = color_comp.a
                        index_offset += index_count
                batch.set_index_count_for_frame(index_offset)
                mesh_instruction = batch.mesh_instruction
                mesh_instruction.flag_update()

This ColorRenderer has been included in kivent_core so you ought to be able to import it from systems.renderers.

Fading the Entities In and Out

The only part left is to build a GameSystem that lerps the alpha value of the ColorComponent from 0.0 to 1.0 to fade in and back from 1.0 to 0.0 to fade out.

Lerp is a linear interpolation that smoothly moves from one value to another across a given period of time. Our implementation will look like, where t is given in the interval: [0, 1.0]:

def lerp(v0, v1, t):
    return (1-t)*v0 + t * v1

Our new GameSystem is going to have 3 attributes:

  • time: The total amount of time for fade in + fade out
  • current_time: The amount of time that has been used, we will start at 0.
  • fade_out_start: The time when the fade out will begin.

In addition, when one entity is done fading out, we will remove it and create a new entity.

Here is our FadingSystem:

class FadingSystem(GameSystem):
    #We create an ObjectProperty so we can bind the create_entity
    #Function we want to use when the old object dies.
    make_entity = ObjectProperty(None)
    
    def update(self, dt):
        #Get the entities
        entities = self.gameworld.entities
        #Check each component
        for component in self.components:
            if component is not None:
                #Look up entity_id for component if its not None
                entity_id = component.entity_id
                #Get the entity
                entity = entities[entity_id]
                #Get the color comp
                color_comp = entity.color
                #add frame time to the current_time.
                component.current_time += dt
                current_time = component.current_time
                fade_out_start = component.fade_out_start
                time = component.time
                #calculate length of fade_out
                fade_out_time = time - fade_out_start
                #If we are past lifetime, remove and make a new 
                #entity.
                if current_time >= time:
                    self.gameworld.remove_entity(entity_id)
                    self.make_entity()
                #If we are still before fade out should begin, lerp from 0. to 1.
                if current_time < fade_out_start:
                    color_comp.a = lerp(0., 1., current_time / fade_out_start)
                else:
                #Otherwise lerp from 1.0 to 0.
                    color_comp.a = lerp(1., 0., 
                        (current_time - fade_out_start) / fade_out_time)

Creating a Bunch of Varied Stars

We have 6 graphics found in examples/7_star_fade/assets, however we are going to load in far more than 6 models using the various textures at different sizes:

#load the atlas
texture_manager.load_atlas('assets/stars.atlas')
#texkeys to choose from
keys = ['star1', 'star2', 'star3', 'star_circle', 'star_square']
#where we will store the names of models created
model_keys = []
mk_a = model_keys.append
#This function takes arguments attributes per vertex, width, height,
#texture key, name of the model
load_textured_rectangle = model_manager.load_textured_rectangle
for x in range(250):
    #generate a unique str name for the model
    model_key = 'star_m_' + str(x)
    #choose a texkey
    tex_key = choice(keys)
    #we will use the same height and width as our tex is square
    wh = randrange(1., 7.)
    #invoke function
    load_textured_rectangle(4, wh, wh, choice(keys), model_key)
    #add the texkey and modelkey to our model_keys list for later
    mk_a((model_key, tex_key))

Using the FadingSystem

Let's modify the TestGame class to support our new FadingSystem. Change the init to include it:

    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld(
            ['color', 'position', 'renderer', 'fade'],
            callback=self.init_game)

We are going to split entity creation out into a function that does it one at a time so that we can call it when an entity dies (bind it to the make_entity property of FadingSystem).

def draw_some_stuff(self):
        init_entity = self.gameworld.init_entity
        for x in range(5000):
            self.draw_a_star()

    def draw_a_star(self):
        model_to_use = choice(model_keys)
        pos = randint(0, Window.width), randint(0, Window.height)
        fade_in = randrange(10., 15.)
        fade_out = randrange(10., 15.)
        create_dict = {
            'position': pos,
            'color': (1., 1., 1., 0.),
            'renderer': {'texture': model_to_use[1], 
                'vert_mesh_key': model_to_use[0]},
            'fade': {'time': fade_in + fade_out,
                'fade_out_start': fade_in, 
                'current_time': 0,},
        }
        ent = self.gameworld.init_entity(create_dict, ['position', 'color', 
            'renderer', 'fade'])

Note when making a dynamic python GameSystem we only need to declare the attributes by name in a dict during creation in order to initialize them, unlike the StaticMemGameSystem that must create a struct that hold its data.

Let's also modify the 'main' state to include our new GameSystem as it is updateable.

    def setup_states(self):
        self.gameworld.add_state(state_name='main', 
            systems_added=['renderer'],
            systems_removed=[], systems_paused=[],
            systems_unpaused=['renderer', 'fade'],
            screenmanager_screen='main')

Finally, add FadingSystem to the kv file:

    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 4
        size: root.size
        pos: root.pos
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
            size_of_component_block: 128
        ColorSystem:
            system_id: 'color'
            gameworld: gameworld
            zones: ['general']
        ColorRenderer:
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 3
            updateable: True
            system_names: ['renderer', 'position','color']
            size_of_batches: 256
            size_of_component_block: 128
            shader_source: 'assets/glsl/positioncolor.glsl'
        FadingSystem:
            gameworld: gameworld
            system_id: 'fade'
            updateable: True
            make_entity: root.draw_a_star

Note that if we change the system_id of ColorRenderer, we must also change the system_names list to use that system_id. Don't forget to bind our create entity function (draw_a_star) to make_entity property of FadingSystem and set updateable to True.

Full Code

python:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.core.window import Window
from random import randint, choice, randrange
import kivent_core
from kivent_core.gameworld import GameWorld
from kivent_core.systems.position_systems import PositionSystem2D
from kivent_core.systems.renderers import Renderer
from kivent_core.systems.gamesystem import GameSystem
from kivent_core.managers.resource_managers import texture_manager
from kivy.properties import StringProperty, ObjectProperty
from kivy.factory import Factory
from kivent_core.managers.resource_managers import texture_manager
from os.path import dirname, join, abspath

texture_manager.load_atlas('assets/stars.atlas')

def lerp(v0, v1, t):
    return (1.-t)*v0 + t * v1

class FadingSystem(GameSystem):
    make_entity = ObjectProperty(None)
    
    def update(self, dt):
        entities = self.gameworld.entities
        for component in self.components:
            if component is not None:
                entity_id = component.entity_id
                entity = entities[entity_id]
                color_comp = entity.color
                component.current_time += dt
                current_time = component.current_time
                fade_out_start = component.fade_out_start
                time = component.time
                fade_out_time = time - fade_out_start
                if current_time >= time:
                    self.gameworld.remove_entity(entity_id)
                    self.make_entity()
                if current_time < fade_out_start:
                    color_comp.a = lerp(0., 255., 
                        current_time / fade_out_start)
                else:
                    color_comp.a = lerp(255., 0., 
                        (current_time - fade_out_start) / fade_out_time)


Factory.register('FadingSystem', cls=FadingSystem)


class TestGame(Widget):
    def __init__(self, **kwargs):
        super(TestGame, self).__init__(**kwargs)
        self.gameworld.init_gameworld(
            ['color', 'position', 'renderer', 'fade'],
            callback=self.init_game)

    def init_game(self):
        self.setup_states()
        self.load_resources()
        self.set_state()
        self.draw_some_stuff()

    def load_resources(self):
        keys = ['star1', 'star2', 'star3', 'star_circle', 'star_square']
        self.model_keys = model_keys = []
        mk_a = model_keys.append
        model_manager = self.gameworld.model_manager
        load_textured_rectangle = model_manager.load_textured_rectangle
        for x in range(100):
            model_key = 'star_m_' + str(x)
            tex_key = choice(keys)
            wh = randrange(1., 7.)
            real_name = load_textured_rectangle(
                'vertex_format_4f', wh, wh, tex_key, model_key)
            mk_a((model_key, tex_key))

    def draw_some_stuff(self):
        init_entity = self.gameworld.init_entity
        for x in range(1000):
            self.draw_a_star()

    def draw_a_star(self):
        model_to_use = choice(self.model_keys)
        pos = randint(0, Window.width), randint(0, Window.height)
        fade_in = randrange(10., 15.)
        fade_out = randrange(10., 15.)
        create_dict = {
            'position': pos,
            'color': (255, 255, 255, 0),
            'renderer': {'texture': model_to_use[1], 
                'model_key': model_to_use[0]},
            'fade': {'time': fade_in + fade_out,
                'fade_out_start': fade_in, 
                'current_time': 0,},
        }
        ent = self.gameworld.init_entity(create_dict, ['position', 'color', 
            'renderer', 'fade'])

    def setup_states(self):
        self.gameworld.add_state(state_name='main', 
            systems_added=['renderer'],
            systems_removed=[], systems_paused=[],
            systems_unpaused=['renderer', 'fade'],
            screenmanager_screen='main')

    def set_state(self):
        self.gameworld.state = 'main'

class DebugPanel(Widget):
    fps = StringProperty(None)

    def __init__(self, **kwargs):
        super(DebugPanel, self).__init__(**kwargs)
        Clock.schedule_once(self.update_fps)

    def update_fps(self,dt):
        self.fps = str(int(Clock.get_fps()))
        Clock.schedule_once(self.update_fps, .05)


class YourAppNameApp(App):
    def build(self):
        Window.clearcolor = (0, 0, 0, 1.)


if __name__ == '__main__':
    YourAppNameApp().run()

kv:

#:kivy 1.9.0

TestGame:

<TestGame>:
    gameworld: gameworld
    GameWorld:
        id: gameworld
        gamescreenmanager: gamescreenmanager
        size_of_gameworld: 100*1024
        size_of_entity_block: 128
        system_count: 4
        size: root.size
        pos: root.pos
        zones: {'general': 10000}
        PositionSystem2D:
            system_id: 'position'
            gameworld: gameworld
            zones: ['general']
            size_of_component_block: 128
        ColorSystem:
            system_id: 'color'
            gameworld: gameworld
            zones: ['general']
        ColorRenderer:
            gameworld: gameworld
            system_id: 'renderer'
            zones: ['general']
            frame_count: 3
            updateable: True
            system_names: ['renderer', 'position','color']
            size_of_batches: 256
            size_of_component_block: 128
            shader_source: 'assets/glsl/positioncolor.glsl'
        FadingSystem:
            gameworld: gameworld
            system_id: 'fade'
            updateable: True
            make_entity: root.draw_a_star

    GameScreenManager:
        id: gamescreenmanager
        size: root.size
        pos: root.pos
        gameworld: gameworld

<GameScreenManager>:
    MainScreen:
        id: main_screen

<MainScreen@GameScreen>:
    name: 'main'
    FloatLayout:
        DebugPanel:
            size_hint: (.2, .1)
            pos_hint: {'x': .225, 'y': .025}


<DebugPanel>:
    Label:
        pos: root.pos
        size: root.size
        font_size: root.size[1]*.5
        halign: 'center'
        valign: 'middle'
        color: (1,1,1,1)
        text: 'FPS: ' + root.fps if root.fps != None else 'FPS:'

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.