Skip to content

Chapter 10: First OpenGL rectangle

Let’s make the blank window prove it can draw

Section titled “Let’s make the blank window prove it can draw”
REAL OPENGL CAPTURE · CHAPTER 10One known rectangle, one known background
A real Xvfb display containing a 900 by 600 dark navy GTK OpenGL surface with a centered bright green rectangle measuring 450 by 300.
context
OpenGL 4.5 core · Mesa llvmpipe
rectangle
450×300 centered in 900×600
pixels
green center · navy client · black root
VISIBLE RESULTOpenGL capability and rectangle evidence
chapter-10-first-rectangle
$ GDK_BACKEND=x11 zig build run -Dgtk=true -Dcapture-timeout-ms=1800
[entry] ghostty
[main] process started
[app] created
[gtk] initialized
[surface 1] created
[gl] vendor=Mesa
[gl] renderer=llvmpipe (LLVM 15.0.7, 256 bits)
[gl] version=4.5 (Core Profile) Mesa 23.2.1-1ubuntu3.1~22.04.4
[gl] shading_language=4.50
[gtk] window presented 900x600
[gl] rectangle x=225 y=150 width=450 height=300
[gtk] event loop exited
[surface 1] destroyed
[gtk] terminated
[app] destroyed
[main] process exiting

The capture runs GTK's normal close path through an explicit capture-only timeout. CI repeats the native build, lifecycle smoke test, capture, and pixel checks.

It is tempting to jump straight to letters. But one glyph would mix OpenGL setup, shaders, textures, font loading, rasterization, metrics, and blending. If the screen stayed blank, we would not know which part failed.

A rectangle lets us test one short path:

GtkGLArea → current OpenGL context → viewport → known background → known region

Terminal state remains disconnected intentionally.

First, make sure the window lifetime is trustworthy

Section titled “First, make sure the window lifetime is trustworthy”

Before touching OpenGL, we clean up a few weak spots in the GTK scaffold:

  • runtime Surface now owns GtkWindow and GtkGLArea;
  • normal execution waits for window close;
  • capture timeout is opt-in;
  • GTK callbacks live in a small C shim compiled against real headers;
  • no-display initialization returns an error;
  • a native CI job builds/tests GTK and runs Xvfb capture.

This work prevents the screenshot from hiding a broken lifecycle.

Real Ghostty started with a simple GL clear too

Section titled “Real Ghostty started with a simple GL clear too”
SOURCE ARCHAEOLOGYThen, reconstruction, and now solve different-sized problems
  1. 01
    THEN · 2023-02-23Clear the first GLArea color

    Ghostty connected realize/render callbacks, loaded OpenGL, and cleared the GTK graphics surface to one color.

    7f34afaOpen first GTK render
  2. 02
    RECONSTRUCTION · CHAPTER 10Verify context and rectangle

    GtkGLArea requests OpenGL 4.3, records llvmpipe capabilities, clears a navy background, and scissor-clears a centered green region.

    chapter-10-first-rectangle
  3. 03
    NOW · PINNED MAINDrive the production renderer

    Current GTK Surface manages realize, unrealize, context errors, app-thread drawing, and renderer frame handoff.

    6ad1fe7Open current GLArea path

The realized GLArea reports:

vendor=Mesa
renderer=llvmpipe
version=4.5 Core Profile
shading language=4.50

We ask for OpenGL 4.3 or newer. Here Mesa’s llvmpipe software renderer gives us 4.5. That is good deterministic evidence, but it does not magically prove every hardware driver or Wayland setup.

ZIG SYNTAX BRIDGE

Draw inside the current GL callback

Start with the C model, then inspect each highlighted Zig line.

Open in the cumulative Zig/C reference
C MENTAL MODEL

native render callback

GTK invokes this C ABI function with the GL area, current-context metadata, and the registered surface context pointer.

Zig difference: The Zig side deliberately calls a narrow shim instead of recreating GTK macros and callback ABI details.

MEMORY / LIFETIME FLOW
  1. GTK Surfacewindow + GLArea widgets
  2. render callback threadtemporarily current GL context
  3. OpenGL contextviewport, scissor, clear state
  4. framebufferbackground + centered rectangle pixels

Why no higher-level tab: TypeScript canvas and Python graphics wrappers can draw rectangles, but they hide GTK callback ABI, current-context state, OpenGL coordinates, and native resource lifetime—the exact concepts this chapter introduces.

Text version: GTK enters a C render callback with native pointers. The callback makes the GLArea context current, reads widget dimensions, establishes a bottom-left framebuffer viewport, restricts drawing with a centered scissor rectangle, and clears it. Returning handles the frame but does not free the GTK Surface or GL context; their native lifetime continues until teardown.

Read the callback like a short recipe:

  1. makes the GLArea context current;
  2. sets a viewport from widget dimensions;
  3. clears the full client area to dark navy;
  4. enables scissor testing;
  5. clears the centered half-width/half-height region to green;
  6. disables scissor testing.

Notice what we did not add: no shader, vertex buffer, texture, or renderer class. A clear plus scissor is enough to prove pixels can change.

Check actual pixels, not just “the screenshot changed”

Section titled “Check actual pixels, not just “the screenshot changed””

The capture checks three root-image samples:

center = srgb(51,204,102)
background = srgb(13,18,31)
root = srgb(0,0,0)

The center must differ from the client background, and the client background must differ from the X root display. These three samples tell us the green rectangle, navy window, and black X root are all distinct. That is much stronger than saying “some screenshot bytes changed.”

File Status Deferred production behavior
src/apprt/gtk.zig adapted thin Zig ownership wrapper around the native shim
src/apprt/gtk_shim.c adapted GLArea lifecycle and one scissored clear; no renderer object, context loss, resize policy, or input
build.zig adapted optional GTK4/libepoxy and capture options
native CI project-owned deterministic llvmpipe evidence, not hardware/Wayland support

How does today’s renderer grow from this callback?

Section titled “How does today’s renderer grow from this callback?”
</>
Current OpenGL initializationsrc/renderer/OpenGL.zig:130–180Read version validation and GTK loading. Ignore resources not earned by the rectangle.
Read excerptGitHub
</>
Current GtkGLArea lifecyclesrc/apprt/gtk/class/surface.zig:3250–3380Read realize/unrealize/render ownership and context error handling.
Read excerptGitHub
implementation 5bc46b51742610097358128b5391c3471f491a72
tag chapter-10-first-rectangle

Geometry works—how do we draw one letter?

Section titled “Geometry works—how do we draw one letter?”

The graphics surface can draw geometry but not text. The next checkpoint should isolate font discovery, metrics, and one rasterized glyph before introducing an atlas or terminal-cell rendering.