Textures and Rendering

This page covers GPU-side imaging in pyglet 3.0: drawing images, working with textures, texture atlases, and framebuffers.

Texture classes moved from pyglet.image to pyglet.graphics.texture in pyglet 3.0. Use Texture and related classes for GPU resources, and keep pyglet.image for CPU-side image data (see Images and Image Data).

Drawing images

For most users, the Sprite class is the best way to draw an image.

Note

image.blit no longer exists in pyglet 3.0. Use sprites for drawing.

Example:

import pyglet

window = pyglet.window.Window(800, 600)
image = pyglet.image.load("kitten.png")
sprite = pyglet.sprite.Sprite(image, x=100, y=80)

@window.event
def on_draw():
    window.clear()
    sprite.draw()

pyglet.app.run()

Sprites can be created from either ImageData, Animation, or an existing texture object.

Batched sprites

If you need to draw many sprites, use a Batch:

batch = pyglet.graphics.Batch()
sprites = [pyglet.sprite.Sprite(image, batch=batch) for _ in range(100)]

@window.event
def on_draw():
    window.clear()
    batch.draw()

When using batches, draw order can be controlled with Group objects:

batch = pyglet.graphics.Batch()
background = pyglet.graphics.Group(order=0)
foreground = pyglet.graphics.Group(order=1)

pyglet.sprite.Sprite(image, batch=batch, group=background)
pyglet.sprite.Sprite(image, batch=batch, group=foreground)

To reduce texture switches, prefer atlased textures (for example via texture()) or use explicit texture bins/atlases as described below.

Converting images and textures

CPU image objects can be uploaded to the GPU with get_texture():

img = pyglet.image.load('kitten.png')
texture = img.get_texture()

If you need finer control over texture creation (for example filtering, address mode, texture type, or internal format settings), use Texture.create_from_image(...) instead of get_texture().

Reading texture pixels back to CPU memory can be done with Texture.fetch():

image_data = texture.fetch()

These readbacks can be expensive, especially if done every frame.

Writing into textures

To write CPU image data into an existing texture, use Texture.upload(...). This method was previously named blit_into.

image_data = pyglet.image.load('overlay.png').get_image_data()
texture = pyglet.graphics.Texture.create(512, 512)
texture.upload(image_data, x=0, y=0, z=0)

Texture uploads replace texel data in the target region. They do not perform alpha blending with existing texels. Blending only applies when drawing geometry/sprites with blend state enabled. For render-based updates, see Drawing into a texture.

Image sequences and atlases

Sometimes a single source image is used to hold many sub-images (for example, sprite sheets). pyglet provides helpers for this workflow.

Texture grids

You can define the CPU-side grid first with ImageGrid (see Image grids in Images and Image Data). Then convert it to a GPU-side TextureGrid for efficient rendering:

explosion = pyglet.image.load('explosion.png')
explosion_grid = pyglet.image.ImageGrid(explosion, 1, 8)
explosion_tex_grid = pyglet.graphics.TextureGrid.from_image_grid(explosion_grid)

first_frame = explosion_tex_grid[0]

TextureGrid can also be created directly with the same grid arguments as ImageGrid (rows, columns, optional item_width/item_height, and padding), but with a texture as the first argument:

texture = explosion.get_texture()
explosion_tex_grid = pyglet.graphics.TextureGrid(texture, 1, 8)

pyglet.graphics.TextureGrid is the same class alias exposed as TextureGrid.

TextureGrid items are TextureRegion objects.

Texture bins and atlases

A TextureAtlas is a large texture that packs many smaller images. A TextureBin manages multiple atlases as needed.

images = [
    pyglet.image.load('img1.png'),
    pyglet.image.load('img2.png'),
]

bin = pyglet.graphics.atlas.TextureBin()
regions = [bin.add(image) for image in images]

The result of TextureBin.add is typically a TextureRegion.

3D textures

You can create a Texture3D from a sequence of images or from an ImageGrid (see Image grids):

explosion_3d = pyglet.graphics.texture.Texture3D.create_for_image_grid(explosion_grid)

Slicing a Texture3D returns TextureRegion objects for layers.

Framebuffers

For explicit framebuffer objects, use Framebuffer and Renderbuffer in pyglet.graphics.framebuffer.

import pyglet
from pyglet.enums import TextureFilter, FramebufferAttachment, ComponentFormat
from pyglet.graphics import Texture, Renderbuffer, Framebuffer

window = pyglet.window.Window()

color_buffer = Texture.create(width, height, filters=TextureFilter.NEAREST)
depth_buffer = Renderbuffer(window.context, width, height,
                            component_format=ComponentFormat.D, bit_size=24)

framebuffer = Framebuffer(context=window.context)
framebuffer.attach_texture(color_buffer, attachment=FramebufferAttachment.COLOR0)
framebuffer.attach_renderbuffer(depth_buffer, attachment=FramebufferAttachment.DEPTH)

framebuffer.bind()

Drawing into a texture

Use a framebuffer with a texture attachment when you want to render into a texture (for post-processing, compositing, dynamic texture generation, etc.).

import pyglet
from pyglet.enums import FramebufferAttachment, TextureFilter

window = pyglet.window.Window(800, 600)

source_image = pyglet.image.load('source.png')
source_sprite = pyglet.sprite.Sprite(source_image, x=100, y=120)

target_texture = pyglet.graphics.Texture.create(
    800, 600, filters=TextureFilter.NEAREST
)
framebuffer = pyglet.graphics.Framebuffer(context=window.context)
framebuffer.attach_texture(target_texture, attachment=FramebufferAttachment.COLOR0)

# This sprite displays the texture we rendered into:
result_sprite = pyglet.sprite.Sprite(target_texture, x=0, y=0)

@window.event
def on_draw():
    # Pass 1: render into texture:
    framebuffer.bind()
    window.clear()
    source_sprite.draw()
    framebuffer.unbind()

    # Pass 2: render texture to window:
    window.clear()
    result_sprite.draw()

pyglet.app.run()

This is different from Texture.upload(...): framebuffer rendering runs the normal draw pipeline (shaders, blending, draw order), while upload is a direct texel data replacement.

To capture the default framebuffer to an image:

from pyglet.graphics.framebuffer import get_screenshot

screenshot = get_screenshot()
screenshot.save('screenshot.png')

OpenGL texture access

This section assumes familiarity with texture mapping in OpenGL (for example, chapter 9 of the OpenGL Programming Guide).

To create a texture from image data:

kitten = pyglet.image.load('kitten.jpg')
texture = kitten.get_texture()

A Texture has a target and id:

from pyglet.graphics.api.gl import *
glBindTexture(texture.target, texture.id)

Texture filtering

By default, all textures are created with smooth (LINEAR) filtering.

To use a different filter for a specific texture, pass the filtering constant(s) to Texture.create via the filter arguments or Texture.create_from_image.

Pixel art

To enable nearest-neighbor filtering for retro-style games, set the corresponding variables of pyglet.graphics.texture.Texture to NEAREST:

from pyglet.enums import TextureFilter
pyglet.graphics.Texture.default_filters = (TextureFilter.NEAREST, TextureFilter.NEAREST)

Afterward, all textures pyglet creates will default to nearest-neighbor sampling.