AnotherOS

Documentation

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_SOLID emits the encoder's fragment colour;
  • GR_FRAGMENT_TEXTURE samples fragment texture 0;
  • GR_BLEND_NONE copies, GR_BLEND_ALPHA is 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.