Grafika — the graphics API
One Metal-shaped C API that every 2D and 3D path draws through, with software and GPU backends.
Grafika is the unified graphics API. It is one C API shaped like Metal that is both the public surface applications draw through and the abstraction layer the system's own 2D and 3D paths route through. A pluggable backend does the actual work, and the same code runs on either.
#include "grafika.h"
Link with -lgrafika, alongside -lkvarcas and the Cairo/Pango stack the
software backend uses.
The model, in one breath
A device vends a command queue; the queue vends command buffers; you open an encoder — render, blit or compute — on a command buffer, record work, end the encoder, and commit.
Encoders only record. Nothing touches pixels until gr_command_commit().
Resources are buffers and textures. A render pipeline state selects a
built-in fragment program and a blend. A layer owns a drawable that you
render into and present. A fence orders work. Every object is reference
counted with one universal pair, gr_retain() and gr_release().
GrDevice *dev = gr_device_create(); /* software by default */
GrCommandQueue *q = gr_device_new_command_queue(dev);
GrTextureDesc td = { .format = GR_PIXELFORMAT_BGRA8_UNORM, .width = 256,
.height = 256, .usage = GR_TEXTURE_USAGE_RENDER_TARGET,
.storage = GR_STORAGE_SHARED };
GrTexture *rt = gr_device_new_texture(dev, &td);
GrCommandBuffer *cb = gr_queue_command_buffer(q);
GrRenderPassDesc pass = {0};
pass.color[0] = (GrColorAttachment){ .texture = rt, .load = GR_LOAD_CLEAR,
.store = GR_STORE_STORE, .clear_color = gr_color(0.1,0.2,0.3,1) };
GrRenderCommandEncoder *e = gr_command_render_encoder(cb, &pass);
gr_render_fill_rect(e, gr_rect(32,32,64,64), gr_color(1,0,0,1));
gr_render_end(e);
gr_command_commit(cb);
gr_release(cb);
Coordinate space
Device space is top-left origin, like the framebuffer, Kvarcas and the compositor — not Metal's bottom-left. If you are porting Metal code, this is the one thing that will be wrong everywhere at once.
Geometry is value-typed: GrOrigin, GrSize, GrRegion (integer, texel space)
and GrRect (float, device space).
Pixel formats
GR_PIXELFORMAT_BGRA8_UNORM is the native order — a uint32 laid out
0xAARRGGBB, premultiplied — and it is the format every present target uses.
GR_PIXELFORMAT_RGBA8_UNORM and GR_PIXELFORMAT_A8_UNORM also exist.
The software backend's raster path assumes BGRA render targets. Blits and compute are byte-correct for any format.
Encoders
Only one encoder is open on a command buffer at a time, which is Metal's rule.
Render — gr_command_render_encoder(). Set state with
gr_render_set_pipeline, _set_viewport, _set_scissor,
_set_fragment_color, _set_fragment_texture, _set_vertex_buffer. Then
either the vertex path, gr_render_draw(prim, first, count) over GrVertex2D
({x, y, u, v}), or the immediate conveniences gr_render_fill_rect() and
gr_render_draw_texture(). Finish with gr_render_end().
Blit — gr_command_blit_encoder(). The copy engine, exact byte copies:
gr_blit_copy_texture, _copy_buffer_to_texture, _copy_texture_to_buffer,
_fill_buffer.
Compute — gr_command_compute_encoder(), then
gr_compute_dispatch(kernel, target, color). Built-in kernels only
(GR_COMPUTE_FILL, GR_COMPUTE_INVERT); there is no shader language yet, and
this page will say so plainly until there is.
Pipelines
gr_device_new_render_pipeline(dev, &desc), where the descriptor picks a
built-in fragment kind and a blend:
GR_FRAGMENT_SOLIDemits the encoder's fragment colour;GR_FRAGMENT_TEXTUREsamples fragment texture 0;GR_BLEND_NONEcopies,GR_BLEND_ALPHAis alpha-over.
Textures, buffers and CPU access
gr_device_new_texture() and gr_device_new_buffer[_bytes]() create them.
Read and write a CPU-visible texture with gr_texture_get_bytes() and
gr_texture_replace_region() — Metal's getBytes and replaceRegion — bounded
by a GrRegion.
The sharing seam: zero copy
int fd = gr_texture_export_fd(tex);
/* send fd over an AF_UNIX socket with SCM_RIGHTS */
GrTexture *aliased = gr_device_import_shared(dev, fd, &same_desc);
This pair is the key seam. Create the texture with GR_TEXTURE_USAGE_SHARED,
export its descriptor, and an importer maps the same physical pages: a draw
through one handle is visible through the other, with no copy.
It is backed by a memfd on the software backend and by real dma-buf under the GPU backend. The API is identical either way — this is the whole point of the seam, and your code does not choose.
Present: layers and drawables
GrLayerDesc ld = { .format = GR_PIXELFORMAT_BGRA8_UNORM, .width = 1024,
.height = 768, .target = GR_LAYER_TARGET_FRAMEBUFFER };
GrLayer *layer = gr_device_new_layer(dev, &ld);
GrDrawable *dr = gr_layer_next_drawable(layer);
GrTexture *back = gr_drawable_texture(dr);
/* render into `back` */
gr_command_present_drawable(cb, dr);
gr_command_commit(cb);
GR_LAYER_TARGET_FRAMEBUFFER presents to the real scanout;
GR_LAYER_TARGET_MEMORY presents to an off-screen, readable RAM buffer, which
is what a test or a screenshot uses. gr_layer_read_pixels() reads presented
pixels back from either.
Fences
gr_device_new_fence(), gr_command_encode_signal_fence(),
_encode_wait_fence(), gr_fence_is_signalled().
On the software backend a commit is synchronous, so a signalled fence reads back signalled immediately. The API exists so that a GPU backend can block without a single line of your code changing.
Backends
gr_device_create() uses the software backend unless the GRAFIKA_BACKEND
environment variable names another. gr_device_create_backend("gpu_i915")
selects one explicitly.
software is the default and is complete: render (clear, fill, textured
quad, vertex triangles), blit, the built-in compute kernels, the sharing seam,
and present to both the framebuffer and off-screen.
gpu_i915 drives an Intel GPU through the kernel's DRM interface. It opens
the card and returns nothing when there is none, so a caller falls back to
software on a machine without a GPU — you do not have to detect this yourself.
It also checks what the card can do: on hardware whose command streamer is up it
composites with real GPU blits, and on a card with buffer sharing but no engine
it takes a CPU composite over the mapped buffer. Under this backend the sharing
seam is real dma-buf, and an imported texture aliases the same GPU pages.
The backend prints one banner line at startup naming the path it took, which is the fastest way to tell what you are actually running on.
What works today
The software backend is complete and is the reference. The GPU backend does real GPU-accelerated compositing and zero-copy buffer sharing on supported Intel hardware, falling back cleanly everywhere else. There is no shader language and no 3D pipeline in Grafika itself — 3D arrives through OpenGL and the Win32 graphics stack.
More documentation
- Booting the live image
- Installing to disk
- Packages and the filesystem
- How it boots
- The syscall ABI
- Hardware support
- ACPI
- UEFI boot entries
- NetBooting a Mac
- The AO toolkit
- The display protocol
- Desktop effects
- The level bezel
- Notifications
- Rastinis — the file manager
- File operations
- Audio — Garsas
- Hardware video decode
- Networking — Tinklas
- Network shares — SMB and NFS
- Remote control
- The Win32 subsystem
- DirectX and OpenGL for Win32 programs
- Ports and packaging