Rendering Textures
Shapes are great for roughing out mechanics, but at some point you'll want real art.
That's where Texture
s come in.
Textures are GPU-friendly images that you can draw, flip, rotate, and scale. Think of them as a prepared image the GPU keeps on hand (VRAM). You upload it once, then tell the renderer where and how to place it each frame.
Creating Textures
Loading an image into GPU memory isn't free. It's best to load once and reuse rather than creating textures on the fly:
texture = kn.Texture("image.png")
while kn.window.is_open():
# Use texture in here
Beginner Tip:
If you need the same image multiple times (e.g. many enemies), reuse the same Texture
object.
Drawing Textures
The simplest draw call renders the texture at the top left corner of the window, (0, 0)
.
Though, because textures are drawn from their center by default, we need to specify the anchor point as TOP_LEFT
.
kn.renderer.draw(texture, anchor=kn.TOP_LEFT)
Result:

Batching
Modern SDL3 backends (which we use) batch draw calls automatically if you draw the same texture consecutively. This keeps the GPU from doing expensive state changes.
# Best performance:
for i in range(5):
kn.renderer.draw(texture, (i * 40, 200))
# Do sparingly:
kn.renderer.draw(texA)
kn.renderer.draw(texB)
kn.renderer.draw(texA)
So, wherever possible, group similar textures together when drawing.
Transformations
Texture objects contain attributes for the angle it should render at in radians and whether it should be renderered flipped horizontally and/or vertically. Below is an example that draws a texture regularly on the left and the same texture flipped horizontally and rotated 45 degrees on the right.
while kn.window.is_open():
...
# Draw normally (need to reset transformations)
texture.flip.h = False
texture.angle = 0
kn.renderer.draw(texture, ...)
# Draw horizontally flipped and rotated
texture.flip.h = True
texture.angle = math.pi / 4
kn.renderer.draw(texture, ...)
...
Result:

Atlases
If your game uses lots of small sprites (UI icons, sprite states, etc.), pack them into a single large texture called an atlas. This means:
- Only one texture bind (fewer GPU state changes)
- Faster draws when sprites share the same atlas
The renderer accepts a source rectangle to select a portion of the texture to draw.
heart_src = kn.Rect(0, 0, 32, 32)
power_src = kn.Rect(32, 0, 32, 32)
while kn.window.is_open():
...
kn.renderer.draw(atlas_tex, (16, 16, 32, 32), src=heart_src)
kn.renderer.draw(atlas_tex, (32, 32, 32, 32), src=power_src)
...
Texture atlas:

Result:
