2D on a screen: windows and the frame loop#

Everything so far drew to a file. This chapter draws to a window: a surface that repaints, responds to the mouse and keyboard, and can animate.

On pperl that means the native SDL2 binding, which supplies a window, a hardware-accelerated 2D renderer, an event queue and timing. The CPAN GUI toolkits are covered at the end, briefly, because they cannot run here and the reason is worth stating once.

A window and a renderer#

use SDL2 qw(:all);

SDL_Init(SDL_INIT_VIDEO) == 0 or die "SDL_Init: ", SDL_GetError(), "\n";

my $win = SDL2::Window->new(title => 'pperl', w => 640, h => 480);
my $ren = $win->renderer(vsync => 1) or die "renderer: ", SDL_GetError(), "\n";

The explicit SDL_Init is optional: SDL2::Window->new initialises the video subsystem for you if it is not already up. The window closes when the last reference to $win goes away. A renderer draws into that window, using the GPU where one is available and falling back to software where it is not.

vsync => 1 ties frame presentation to the display refresh, which paces a loop without a sleep and avoids tearing. software => 1 forces the software path, which is what a headless test wants.

The frame loop#

Every interactive program has the same shape: drain the event queue, update state, draw, present.

my $running = 1;
while ($running) {
    while (my $ev = SDL_PollEvent()) {
        $running = 0 if $ev->{type} == SDL_QUIT;
    }
    $ren->set_draw_color(30, 30, 40);
    $ren->clear;
    $ren->set_draw_color(240, 180, 60);
    $ren->fill_rect([ 200, 150, 240, 180 ]);
    $ren->present;
}

This is immediate mode: the screen is a function of your state, recomputed every frame. Nothing is retained between frames, so there is no object to update when something moves; you just draw it in its new place.

Drain the queue with a while, not an if. Handling one event per frame falls progressively behind during fast mouse movement, and the symptom is input lagging seconds behind the user.

Drawing#

The renderer’s primitives are deliberately few:

  • $ren->clear fills the target with the draw colour.

  • $ren->set_draw_color($r, $g, $b, $a), alpha defaulting to 255.

  • $ren->draw_point, $ren->draw_line.

  • $ren->draw_rect($rect) outlines, $ren->fill_rect($rect) fills.

  • $ren->copy($texture, $src, $dst) blits a texture.

  • $ren->read_pixels($rect) returns raw RGBA bytes.

A rectangle is [$x, $y, $w, $h], or undef meaning the whole target.

There is no circle, no polygon, no thick line and no text. That is not an omission to work around casually: SDL’s renderer is a blitting engine, and anything richer is either a texture you prepared earlier or pixels you computed yourself with the algorithms chapter.

Textures, and drawing offscreen#

A texture is an image in the renderer’s memory. Created with SDL_TEXTUREACCESS_TARGET it can also be drawn into, which is how you keep an offscreen image that survives redraws:

my $canvas = SDL2::Texture->new($ren, w => 320, h => 240,
                                access => SDL_TEXTUREACCESS_TARGET);

$ren->set_target($canvas);               # draw into the texture
$ren->set_draw_color(255, 255, 255);
$ren->clear;
$ren->set_target(undef);                # undef: back to the window
$ren->copy($canvas, undef, [0, 0, 320, 240]);

set_target points the renderer at the texture, and undef points it back at the window. The direct function layer’s SDL_SetRenderTarget does the same and accepts the same objects. The drawing app is built on exactly this.

Input#

Events arrive as plain hash references with a type and whatever keys that type carries. A keyboard event has scancode, sym, mod and key_name; a mouse button event has x, y, button and clicks.

For continuous movement, ask the keyboard state rather than the queue:

SDL_PumpEvents();
$x -= 4 if SDL2::key_pressed(SDL_SCANCODE_LEFT);

Events report transitions, so moving per SDL_KEYDOWN inherits the operating system’s key-repeat delay and stutters. key_pressed answers “is it down right now”, which is what smooth movement needs. The game capstone covers the scancode and keysym distinction that goes with it.

What SDL2 is not#

It is not a widget toolkit. There are no buttons, no text fields, no layout, no focus handling, no accessibility. A control is a rectangle you draw and a coordinate test you write, which is fine for four colour swatches and is not fine for a settings dialogue.

If an application needs real widgets, SDL2 is the wrong layer and there is currently no right one on pperl. That is a genuine gap, stated rather than papered over.

The CPAN toolkits#

Prima, Perl/Tk, Gtk3 and Wx are the toolkits a search will find, and they are the right answer on perl5. None of them runs on pperl, for one structural reason: each is an XS module, and pperl cannot load a compiled extension, because XS binds to perl5’s C-level ABI and pperl has no such ABI to bind to. Prima.pm calls bootstrap Prima unconditionally at load time, so use Prima fails before any of your code runs.

The natural next question is why they are not reached through Peta::FFI the way Cairo and SDL2 are. The answer differs per toolkit, and for Prima it is decisive: there is no library to reach. Cairo binds libcairo, and SDL2 binds libSDL2. Prima is not a binding to anything; it is the implementation, with its own graphics engine and its own X11 widget layer. Prima 1.77 is roughly 117,000 lines of C and header alongside 95,000 lines of Perl module code. There is no .so on the far side of an FFI call, so a native Prima would mean reimplementing a windowing toolkit, not wrapping one.

Gtk3 and Wx do wrap real libraries and are therefore reachable in principle, at the cost of a large binding each. Both are also in poor health upstream: Wx and its Alien::wxWidgets dependency have not had a release in years, and Gtk3 is in slow maintenance.

For this guide they are mention-only. If you are writing for perl5 and want widgets, they remain good answers; on pperl the working answer for drawing to a screen is SDL2.

Choosing#

You want …

Reach for

A window, drawing and input on pperl

SDL2

Per-frame animation, a game loop

SDL2

An offscreen surface that survives redraws

SDL2 target texture

Vector output to a file rather than a screen

Cairo

Real widgets on pperl

nothing today

With a window and a frame loop, the toolkit arc is complete. The next arc goes under it: pperl’s native FFI, how the bindings this chapter used are built, and two projects that put the whole guide together.