# Reaching further: Peta::FFI for graphics The toolkit chapters covered what CPAN already wraps. This chapter, and the rest of the guide, goes further: reaching a C graphics library that has no CPAN binding at all, using pperl’s native FFI. This is a pperl-exclusive capability. Binding a C library the traditional way means writing a C shim, compiling it, and shipping a build toolchain with your code; pperl calls the C library directly, the same mechanism that already gives you `Math::GMP` with no compiled binding to build at all. This is not `FFI::Platypus`. `Peta::FFI` is built into the runtime, and its curated bindings are native Rust, not Perl-side declarations. The distinction matters for graphics because a real graphics binding needs to manage C-allocated objects (a surface, a context) across many calls, and pperl’s model handles that the way the C library itself does. The authoritative reference for the FFI layer is the [Auto-FFI page](../pperl-architecture/ffi.md); this chapter applies it to graphics specifically. ## The three levels `Peta::FFI` is best understood as three levels, from raw to curated. **Level 1, raw calls.** You name a library, a function, and a type-signature string, and pperl dispatches the call through `libffi`. Nothing is generated ahead of time; any signature you can spell works. ```perl use Peta::FFI qw(dlopen call dlclose); my $lib = dlopen("libm.so.6"); my $r = call($lib, "sqrt", "(d)d", 2.0); # sqrt(2) = 1.4142... dlclose($lib); ``` The signature `"(args)ret"` uses one letter per type: `v` void, `i` int, `l` long, `L` unsigned long or size_t, `d` double, `f` float, `p` a C string in, and `P` a mutable output buffer. That set covers a surprising amount of a C API: anything whose arguments and return are scalars, strings, or a byte buffer. **Level 2, a data-description layer.** Many C functions take or return **structs**, not scalars, and the raw type codes cannot describe a struct’s layout. The planned answer is a declarative layer where you describe a record’s fields once and let the FFI marshal it. This is the emerging middle path; it is not a shipping module yet, and this guide is explicit at each point about where a binding would need it. **Level 3, curated bindings.** The top level is a hand-written native binding: a Rust module that opens the library once, resolves every symbol it needs, and presents a clean Perl API, managing the C-allocated objects behind Perl values. `Math::GMP` is exactly this: `use Math::GMP` and you get arbitrary-precision integers with operator overloading, and nowhere did you touch `dlopen` or a signature string. The [next chapter](ffi-cairo-showcase.md) takes one of these that pperl already ships for a graphics library, `Cairo`, and shows how it works. ## What raw calls can and cannot reach The dividing line for graphics is the struct. A great deal of a C graphics API is reachable at Level 1 right now, because it is scalars and opaque handles: - Functions that take and return **numbers** (set a line width, a radius, a color component) map straight onto `i`, `d`, `f`, and friends. - Functions that take a **C string** (load a font by name, open a file) use `p`. - Functions that fill a **caller-provided buffer** (read pixels into memory you own) use `P`. - An **opaque handle** returned by the library (a surface pointer, a context pointer) is just an address. pperl already represents a library handle as an integer, and the curated pattern in the next chapter stores these object handles the same way: as an integer inside a blessed Perl reference. Where raw calls stop: - **Structs passed or returned by value.** A function taking a `Color { r, g, b, a }` by value, or returning a `Rectangle`, cannot be described by the scalar type codes. This is the Level 2 gap. - **Callbacks.** A library that calls back into your code (an event loop that invokes a handler, a draw signal) needs a C function pointer that re-enters Perl. That is beyond the raw layer. - **Unions.** An event type that is a union of many struct shapes (the classic windowing event record) combines both problems above. These are not permanent walls; they are the frontier the FFI layers are growing toward. This guide’s value is telling you exactly which side of the line a given task falls on, so you neither avoid what works nor promise what does not. ## Why a curated binding, not raw calls, for real work You could drive a graphics library entirely through Level 1 `call`s, and for a quick experiment that is fine. For anything real, a Level 3 curated binding is the right unit, for three reasons. First, **object lifetime**. A graphics context is allocated by the library and must be freed exactly once. A curated binding ties that lifetime to a Perl value’s `DESTROY`, so the C object is released when the Perl object goes away, with no manual bookkeeping. Raw calls leave that entirely to you. First-class Perl **ergonomics** is the second reason: `$surface->fill` reads better and is harder to get wrong than a `call` with a hand-written signature at every use site. Third, **the signature is written once**. In a curated binding the `"(ppd)i"` strings live in one Rust module, checked once; every caller just uses the method. Scattering raw signatures through application code is how you get a mismatch that corrupts the stack. The [Cairo showcase](ffi-cairo-showcase.md) makes all three concrete with the `Cairo` binding pperl ships, a real vector library whose API is almost entirely scalars and one opaque handle, which is precisely the shape that reaches cleanly today. ## The shape of what follows The next chapter builds a curated Cairo binding the way `Math::GMP` is built, so you can see a graphics library brought up on pperl end to end. Then the two capstones use the maintained toolkit stack from Arc B, anchored on Prima, to build a drawing application and a 2D game you can run, with the FFI thread woven in where it earns its place and honestly flagged where the frontier begins. The [final chapter](whats-next.md) draws the map of what is left.