GPU framebuffer / render-target readback for sokol applications.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Mikael Säker 91c058c946 deps: track sokol bd6dd7c
Keeps the four-way pin byte-identical — reveal, dvui-sokol, sokey and this must
name the same sokol commit or Zig resolves two of them and app.Event stops
equalling app.Event.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 22:24:32 +02:00
src Initial commit: Metal-backend sg.Image readback + pure-Zig PNG encoder 2026-05-30 12:07:23 +02:00
.gitignore Initial commit: Metal-backend sg.Image readback + pure-Zig PNG encoder 2026-05-30 12:07:23 +02:00
build.zig Initial commit: Metal-backend sg.Image readback + pure-Zig PNG encoder 2026-05-30 12:07:23 +02:00
build.zig.zon deps: track sokol bd6dd7c 2026-08-06 22:24:32 +02:00
LICENSE Add MIT license 2026-07-25 12:00:21 +02:00
README.md docs: repoint links to git.urverk.org 2026-07-25 11:41:32 +02:00

sokol-capture

GPU framebuffer / render-target readback for sokol-gpu applications. Pure Zig, no dvui dependency — usable from raw-sokol engines and dvui-sokol apps alike.

Status

  • Backend: Metal (macOS) only. Reads MTLStorageModePrivate render targets via a blit to a shared MTLBuffer, then getBytes. Other backends return error.UnsupportedBackend; D3D11 / GL / WGPU paths are TODO.
  • Capture: synchronous one-shot — capturePixels(allocator, img) blocks on waitUntilCompleted. Async / per-frame video-style capture is planned, not implemented.
  • Encoding: built-in pure-Zig PNG encoder (RGBA8, filter 0, zlib via std.compress.flate).

Usage

const std = @import("std");
const sg = @import("sokol").gfx;
const capture = @import("sokol_capture");

// `img` is any sokol render-target sg.Image.
var pixels = try capture.capturePixels(allocator, img);
defer pixels.deinit();
// pixels.pixels is tightly packed RGBA8, top-to-bottom.

const png_bytes = try capture.encodePng(allocator, pixels);
defer allocator.free(png_bytes);
// Write png_bytes to disk with your own file API.

Or in one shot:

const png_bytes = try capture.capturePngBytes(allocator, img);
defer allocator.free(png_bytes);

The module is Io-agnostic — it returns bytes; callers handle file writes with whatever they already have (Zig std's std.Io, a runtime shim, etc).

Why a separate module?

  • A raw-sokol game engine needs screenshot capability without depending on dvui.
  • A dvui-sokol app (wk / cue / quote) gets the same primitive without duplicating Metal FFI in dvui-sokol.
  • Apple Silicon's private-storage render targets need a blit step; this module owns that pattern so consumers don't reinvent it.

Build

Requires Zig 0.16. Depends only on https://git.urverk.org/urverk/sokol-zig (same fork dvui-sokol pins, so the build graph dedupes when both are used).

zig build check   # compile-check
zig build test    # unit tests (PNG encoder)

Consumers add it as a path dep:

.sokol_capture = .{ .path = "../sokol-capture" },

then in build.zig:

const sokol_capture_dep = b.dependency("sokol_capture", .{
    .target = target,
    .optimize = optimize,
});
// add to your exe's imports:
.{ .name = "sokol_capture", .module = sokol_capture_dep.module("sokol_capture") },