Section 03: FFI Wrapper Layers

Status: Research Complete Goal: Design the ~3,300 lines of Ori FFI wrapper code that provides safe, capability-tracked access to C libraries for GPU rendering, text shaping, font rasterization, and windowing.

Context: These wrappers are thin — they exist to provide safe Ori types over unsafe C APIs. Every wrapper uses Deep FFI features: owned/borrowed for ownership tracking, #error() for automatic error wrapping, #free() for resource cleanup, and uses FFI("lib") for per-library capability isolation. Users never see the extern "c" blocks — they interact with safe Ori types.


03.1 ori_gpu — wgpu-native Wrapper (~1,500 lines)

C library: wgpu-native (WebGPU C API via webgpu.h) FFI capability: uses FFI("wgpu")

Why wgpu-native Over Raw Vulkan/Metal/DX12

wgpu-native exposes the WebGPU C API (webgpu.h) — the same API browsers ship. Benefits:

  • One API, all platforms: Vulkan (Linux/Windows/Android), Metal (macOS/iOS), DX12 (Windows), WebGPU (browsers)
  • Clean C API: ~80 handle-based functions, no COM, no Objective-C
  • WebGPU is the future: Same code targets native and web (when Ori adds WASM)
  • Validation layer: Debug builds get GPU validation for free

Deep FFI Design

extern "c" from "wgpu_native" #free(wgpuInstanceRelease) {
    @wgpu_create_instance (desc: borrowed CPtr) -> owned CPtr
        as "wgpuCreateInstance"
}

extern "c" from "wgpu_native" #free(wgpuDeviceRelease) {
    @wgpu_adapter_request_device (
        adapter: borrowed CPtr,
        desc: borrowed CPtr,
        callback: (c_int, CPtr, CPtr, CPtr) -> void,
        userdata: CPtr
    ) -> void as "wgpuAdapterRequestDevice"
}

extern "c" from "wgpu_native" #free(wgpuBufferRelease) {
    @wgpu_device_create_buffer (
        device: borrowed CPtr,
        desc: borrowed CPtr
    ) -> owned CPtr as "wgpuDeviceCreateBuffer"
}

extern "c" from "wgpu_native" {
    @wgpu_queue_submit (
        queue: borrowed CPtr,
        count: c_size,
        commands: borrowed CPtr
    ) -> void as "wgpuQueueSubmit"
}

Safe Ori Types

type GpuInstance = { handle: CPtr }
type GpuAdapter = { handle: CPtr }
type GpuDevice = { handle: CPtr }
type GpuQueue = { handle: CPtr }
type GpuBuffer = { handle: CPtr, size: int, usage: BufferUsage }
type RenderPipeline = { handle: CPtr }
type ShaderModule = { handle: CPtr }
type Texture = { handle: CPtr, format: TextureFormat, size: (int, int) }

// Ownership tracked — compiler auto-generates Drop via #free
impl GpuDevice {
    @create_buffer (self, size: int, usage: BufferUsage) -> Result<GpuBuffer, GpuError>
        uses FFI("wgpu") = { ... }

    @create_render_pipeline (self, desc: PipelineDesc) -> Result<RenderPipeline, GpuError>
        uses FFI("wgpu") = { ... }

    @create_shader_module (self, source: str) -> Result<ShaderModule, GpuError>
        uses FFI("wgpu") = { ... }
}

impl GpuQueue {
    @submit (self, commands: [CommandBuffer]) -> void
        uses FFI("wgpu") = { ... }

    @write_buffer (self, buffer: GpuBuffer, offset: int, data: [byte]) -> void
        uses FFI("wgpu") = { ... }
}

Glyph Atlas Manager

Port from oriterm/src/gpu/atlas/. Manages GPU texture pages for rendered glyphs:

type GlyphAtlas = {
    pages: [AtlasPage],
    cache: {GlyphKey: AtlasEntry},
    page_size: int,
}

type AtlasPage = {
    texture: Texture,
    allocator: GuillotineAllocator,
    format: AtlasFormat,
}

type AtlasFormat = R8 | Rgba8  // Monochrome vs color (emoji)

type GlyphKey = { font_id: int, glyph_id: int, size_px: int, subpixel_offset: int }
type AtlasEntry = { page: int, uv: Rect }

03.2 ori_text — HarfBuzz + FreeType Wrapper (~800 lines)

Why Not swash

swash is ~15k lines of pure Rust with no C API, heavy use of Rust-specific patterns (&[u8] slicing, bytemuck, zero-copy parsing). Porting it to Ori would be premature — the byte-level font table parsing is not where Ori shines yet.

HarfBuzz + FreeType is the industry standard stack used by Chrome, Firefox, Android, GNOME, and LibreOffice. Clean C APIs, battle-tested, widely available.

HarfBuzz — Text Shaping

FFI capability: uses FFI("harfbuzz")

extern "c" from "harfbuzz" #free(hb_buffer_destroy) {
    @hb_buffer_create () -> owned CPtr as "hb_buffer_create"
}

extern "c" from "harfbuzz" {
    @hb_buffer_add_utf8 (
        buf: borrowed CPtr,
        text: borrowed CPtr,
        text_length: c_int,
        item_offset: c_int,
        item_length: c_int
    ) -> void as "hb_buffer_add_utf8"

    @hb_buffer_set_direction (buf: borrowed CPtr, dir: c_int) -> void
        as "hb_buffer_set_direction"

    @hb_buffer_set_script (buf: borrowed CPtr, script: c_int) -> void
        as "hb_buffer_set_script"

    @hb_buffer_set_language (buf: borrowed CPtr, lang: CPtr) -> void
        as "hb_buffer_set_language"

    @hb_shape (
        font: borrowed CPtr,
        buffer: borrowed CPtr,
        features: borrowed CPtr,
        num_features: c_int
    ) -> void as "hb_shape"

    @hb_buffer_get_glyph_infos (buf: borrowed CPtr, length: out c_int) -> borrowed CPtr
        as "hb_buffer_get_glyph_infos"

    @hb_buffer_get_glyph_positions (buf: borrowed CPtr, length: out c_int) -> borrowed CPtr
        as "hb_buffer_get_glyph_positions"
}

Safe Ori Shaping API

type ShapedRun = { glyphs: [ShapedGlyph], width: float }

type ShapedGlyph = {
    glyph_id: int,
    cluster: int,
    x_advance: float,
    y_advance: float,
    x_offset: float,
    y_offset: float,
}

@shape_text (font: Font, text: str, direction: TextDirection) -> ShapedRun
    uses FFI("harfbuzz") = {
    // Create buffer, add UTF-8, set direction/script/language, shape, extract results
    // All CPtr handles auto-freed via #free
}

FreeType — Font Rasterization

FFI capability: uses FFI("freetype")

extern "c" from "freetype" #error(nonzero) {
    @ft_init_freetype (lib: out CPtr) -> c_int as "FT_Init_FreeType"
    @ft_new_face (lib: borrowed CPtr, path: borrowed CPtr, index: c_long,
                  face: out CPtr) -> c_int as "FT_New_Face"
    @ft_set_pixel_sizes (face: borrowed CPtr, w: c_int, h: c_int) -> c_int
        as "FT_Set_Pixel_Sizes"
    @ft_load_glyph (face: borrowed CPtr, index: c_int, flags: c_int) -> c_int
        as "FT_Load_Glyph"
    @ft_render_glyph (slot: borrowed CPtr, mode: c_int) -> c_int
        as "FT_Render_Glyph"
}

Safe Ori Font API

type Font = { face: CPtr, metrics: FontMetrics }
type FontMetrics = { ascender: float, descender: float, line_height: float, units_per_em: int }

type GlyphBitmap = {
    pixels: [byte],
    width: int,
    height: int,
    bearing_x: float,
    bearing_y: float,
    advance: float,
}

@load_font (path: str, size_px: int) -> Result<Font, FfiError>
    uses FFI("freetype") = {
    let lib = ft_init_freetype()?
    let face = ft_new_face(lib, path, 0)?
    ft_set_pixel_sizes(face, size_px, size_px)?
    // Extract metrics, return safe Font type
}

@rasterize_glyph (font: Font, glyph_id: int) -> Result<GlyphBitmap, FfiError>
    uses FFI("freetype") = {
    ft_load_glyph(font.face, glyph_id, 0)?  // FT_LOAD_DEFAULT
    ft_render_glyph(font.face.glyph, 0)?     // FT_RENDER_MODE_NORMAL
    // Extract bitmap data into safe Ori types
}

03.3 ori_window — SDL3 Wrapper (~600 lines)

C library: SDL3 FFI capability: uses FFI("SDL3")

Why SDL3 Over GLFW

FeatureSDL3GLFW
WindowingYesYes
Input (keyboard, mouse)YesYes
GamepadYesNo
AudioYesNo
ClipboardYesLimited
File dialogsYesNo
DPI handlingYesYes
WaylandFullPartial
Mobile (iOS/Android)YesNo
Licensezlibzlib

SDL3 is the better choice for a comprehensive framework — it handles platform differences and provides clipboard, DPI, file dialogs out of the box.

Deep FFI Design

extern "c" from "SDL3" #error(negative) {
    @sdl_init (flags: c_int) -> c_int as "SDL_Init"
}

extern "c" from "SDL3" #error(null) #free(sdl_destroy_window) {
    @sdl_create_window (
        title: borrowed CPtr,
        w: c_int,
        h: c_int,
        flags: c_int
    ) -> owned CPtr as "SDL_CreateWindow"
}

extern "c" from "SDL3" {
    @sdl_poll_event (event: out SdlEvent) -> c_int as "SDL_PollEvent"
    @sdl_get_window_size (win: borrowed CPtr, w: out c_int, h: out c_int) -> void
        as "SDL_GetWindowSize"
    @sdl_get_window_surface (win: borrowed CPtr) -> borrowed CPtr
        as "SDL_GetWindowSurface"
}

Safe Ori Window API

type Window = { handle: CPtr, width: int, height: int, scale: float }

type WindowEvent = Resize(w: int, h: int) | Close | Focus | Blur
    | KeyDown(key: Key, modifiers: Modifiers)
    | KeyUp(key: Key, modifiers: Modifiers)
    | MouseMove(x: float, y: float)
    | MouseDown(button: MouseButton, x: float, y: float)
    | MouseUp(button: MouseButton, x: float, y: float)
    | Scroll(dx: float, dy: float)
    | TextInput(text: str)
    | DpiChange(scale: float)

@create_window (title: str, width: int, height: int) -> Result<Window, FfiError>
    uses FFI("SDL3") = { ... }

@poll_events () -> [WindowEvent]
    uses FFI("SDL3") = { ... }

03.4 ori_font — Platform Font Discovery (~400 lines)

Platform-specific font discovery. Each platform has a different C API:

PlatformLibraryAPI Style
LinuxfontconfigPattern matching (FcPatternCreate, FcFontMatch)
macOSCoreTextCF-based (CTFontCreateWithName)
WindowsDirectWriteCOM-based (IDWriteFactory::CreateTextFormat)
// Unified Ori API — platform differences hidden behind FFI
type FontDescriptor = {
    family: str,
    weight: FontWeight,
    style: FontStyle,
    stretch: FontStretch,
}

type FontWeight = Thin | ExtraLight | Light | Regular | Medium | SemiBold | Bold | ExtraBold | Black

@find_font (desc: FontDescriptor) -> Result<str, FfiError>
    uses FFI(platform_font_lib) = {
    // Returns path to font file
    // Platform-specific implementation via conditional compilation
}

@find_system_font (family: str) -> Result<str, FfiError>
    uses FFI(platform_font_lib) = {
    find_font(FontDescriptor { family, weight: Regular, style: Normal, stretch: Normal })
}

03.5 FFI Layer Summary

LayerC LibraryFFI CapabilityDeep FFI Features Used
ori_gpuwgpu-nativeFFI("wgpu")owned/borrowed, #free, callbacks
ori_textHarfBuzzFFI("harfbuzz")borrowed (buffers), out (glyph info)
ori_textFreeTypeFFI("freetype")#error(nonzero), out (init), borrowed
ori_windowSDL3FFI("SDL3")#error(negative/null), #free, out (events)
ori_fontfontconfig/DW/CTFFI(platform)Platform-conditional, #error

Total extern functions: ~80 (wgpu) + ~15 (HarfBuzz) + ~10 (FreeType) + ~20 (SDL3) + ~15 (font) = ~140 C functions wrapped in safe Ori types.