Note
Go to the end to download the full example code.
Text waterfall
An example showing a waterfall of text. On the left it shows the contents of the glyph atlas. One goal of this example is to strain the text rendering to its limits.
from wgpu.gui.auto import WgpuCanvas, run
import pygfx as gfx
import numpy as np
renderer = gfx.renderers.WgpuRenderer(WgpuCanvas(size=(800, 400)))
scene = gfx.Scene()
glyph_atlas = gfx.utils.text.glyph_atlas
glyph_atlas.clear_free_regions = True # So we can see regions being freed
# Add background
background = gfx.Background.from_color("#dde", "#fff")
scene.add(background)
# Add an image that shows the glyph atlas
atlas_viewer = gfx.Mesh(
gfx.plane_geometry(100, 100),
gfx.MeshBasicMaterial(color="red"),
)
scene.add(atlas_viewer)
atlas_viewer.local.x = -50
camera = gfx.OrthographicCamera(200, 100)
# Create a bunch of reusable text objects
def character_generator():
pieces = gfx.font_manager.select_font(" ", gfx.font_manager.default_font_props)
font = pieces[0][1]
while True:
for c in font.codepoints:
yield chr(c)
chargen = character_generator()
live_objects = set()
waiting_objects = set()
text_material = gfx.TextMaterial(color="#06E")
for i in range(100):
obj = gfx.Text(
gfx.TextGeometry(" ", font_size=18, screen_space=True),
text_material,
)
scene.add(obj)
waiting_objects.add(obj)
obj.local.y = -999
# The animate function makes the text objects fall down, and update the objects
# with a new character once they start their fall again.
# Until we have real garbage collection for glyphs, we fake it here.
def animate():
garbage_collect = True
# Let them fall
for obj in list(live_objects):
obj.local.y -= obj.fall_speed
if obj.local.y < -60:
live_objects.discard(obj)
waiting_objects.add(obj)
if garbage_collect:
all_indices = set()
for x in live_objects:
all_indices.update(int(index) for index in x.geometry.indices.data)
for index in obj.geometry.indices.data:
index = int(index)
if index not in all_indices:
glyph_atlas.free_region(index)
# Drop new objects
if waiting_objects:
obj = waiting_objects.pop()
live_objects.add(obj)
obj.local.y = 50
obj.local.x = np.random.uniform(0, 100)
obj.geometry.set_text(next(chargen))
obj.fall_speed = np.random.uniform(1, 4)
# Update the image
if atlas_viewer.material.map is not glyph_atlas.texture:
atlas_viewer.material.map = glyph_atlas.texture
# Render
renderer.render(scene, camera)
renderer.request_draw()
if __name__ == "__main__":
renderer.request_draw(animate)
run()
Total running time of the script: (0 minutes 20.142 seconds)