SpriteBatch

  Edit on GitHub

An overview on reading images and drawing them in an optimized fashion via the SpriteBatch class.

Overview

To draw an image in LittleKt, we first need to convert that image from its original format (e.g. PNG) and then upload it to the GPU. By doing that, we now have a Texture that we can use for drawing.

It is common to draw the same texture, or even portions of the same texture, multiple times. Defining multiple portions of the textures and uploading them to the GPU one at a time is inefficient. It is more efficient to define the portions of that textures and send them to the GPU all at once. This is called batching and is exactly what SpriteBatch handles.

A SpriteBatch is given a texture and coordinates of each portion of that texture to draw. It caches the geometry until it is ready to upload to the GPU. If a different texture is supplied than the last texture, then it will bind the last texture and submit the cached geometry to be drawn, and begins to cache geometry for the new texture.

SpriteBatch

A SpriteBatch batches the geometry for drawing a single texture into a single draw call. A SpriteBatch uses an IndexedMesh internally to render the geometry. A Spritebatch must either called begin and end methods or the use method to do any drawing. The drawing must be done between the calls of these methods. A SpriteBatch, internally, uses the SpriteBatchShader which is a SpriteShader. This allows the batcher to send camera uniform updates to the shader. The camera uniform is a dynamic uniform shader, meaning if a different camera viewProjection is used, it will use the same shader module to render but with the updated uniform value. The size of the uniform buffer is dependent on the device’s minUniformBufferOffsetAlignment limit and must be aligned to it. By default, the uniform buffer size will be calculated with the following formula, using a default camera dynamic size of 50, which can be changed.

(Float.SIZE_BYTES * 16)
    .align(device.limits.minUniformBufferOffsetAlignment)
    .toLong() * cameraDynamicSize

Usage

val batch = SpriteBatch(context)

onUpdate {
    val renderPass = ... // render pass setup
    batch.use(renderPass) {
       // drawing logic goes here
    }

    // or we can use:

    batch.begin()
    // do drawing logic here
    batch.flush(renderPass)
    batch.end()
}

Projection Matrix

We can also set the projection matrix of the SpriteBatch that will be used in the shader to render the items. This is mainly used with a camera but can be any matrix:

batch.use(renderPass, camera.viewProjection) {
    // we are using the cameras view projection matrix to render
}

Drawing textures and texture slices

SpriteBatch contains many methods for drawing but the common draw methods are for drawing textures and slices of textures:

val batch = SpriteBatch(context)
val texture: Texture = resourcesVfs["texture.png"].readTexture()
val slices = texture.slice(16, 16)
val person = slices[0][0]

onUpdate {
    val renderPass = ... // render pass setup
    batch.use(renderPass) {
       // the draw method also contains a few more parameters such as origin, scale, rotation, colors, and flipping.
       // they all have a default value so we don't have to specify them.
       it.draw(texture, x = 50f, y = 25f)
       it.draw(person, x = 5f, y = 50f)
    }
    renderPass.end()
    
    // release render pass & surface textures
}