diff --git a/.gitignore b/.gitignore index fa91e0d038..20feb1f820 100644 --- a/.gitignore +++ b/.gitignore @@ -155,3 +155,8 @@ packages/registry/*/package.pr.toml # replacement across cooperating xtask processes. It is intentionally retained # so waiters never split onto different lock inodes. .*.kandelo-index.lock + +# SDL2 playground: auto-generated TTF→C-byte-array header. +# Regenerated from programs/sdl2/third_party/Inconsolata-Regular.ttf by +# scripts/build-programs.sh on every build. +programs/sdl2/third_party/inconsolata_ttf.h diff --git a/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts b/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts index e410887c5b..3f82511db6 100644 --- a/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts +++ b/apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts @@ -8,6 +8,14 @@ import { type ImageOwnedRuntimeLazyAssets, } from "../../../lib/init/image-owned-runtime-urls"; import { BrowserInputSource } from "../../../../../host/src/input/browser-input-source"; +import sdl2PlasmaFragSrc from "../../../../../programs/sdl2/presets/image/plasma.frag?raw"; +import sdl2AudioBarsFragSrc from "../../../../../programs/sdl2/presets/image/audio_bars.frag?raw"; +import sdl2TunnelwispFragSrc from "../../../../../programs/sdl2/presets/image/tunnelwisp.frag?raw"; +import sdl2SoundSineFragSrc from "../../../../../programs/sdl2/presets/sound/sine.frag?raw"; +import sdl2SoundTunnelwispFragSrc from "../../../../../programs/sdl2/presets/sound/tunnelwisp.frag?raw"; +import sdl2SoundFmBellFragSrc from "../../../../../programs/sdl2/presets/sound/fm_bell.frag?raw"; +import sdl2SoundNoiseSweepFragSrc from "../../../../../programs/sdl2/presets/sound/noise_sweep.frag?raw"; +import sdl2SoundChordFragSrc from "../../../../../programs/sdl2/presets/sound/chord.frag?raw"; import { WORDPRESS_CONFIG_INIT_SCRIPT, WORDPRESS_URL_MU_PLUGIN, @@ -25,8 +33,6 @@ import { } from "../../../lib/init/wordpress-mariadb-readiness"; import { MemoryFileSystem } from "../../../../../host/src/vfs/memory-fs"; import { - extractZipEntry, - parseZipCentralDirectory, } from "../../../../../host/src/vfs/zip"; import { resolveBrowserCorsProxyConfig, @@ -175,24 +181,10 @@ const OPTIONAL_BINARY_URLS = { import: "default", }, ), - ...import.meta.glob("../../../../../local-binaries/programs/wasm32/evdev_demo.wasm", { + ...import.meta.glob("../../../../../local-binaries/programs/wasm32/sdl2.wasm", { query: "?url", import: "default", }), - ...import.meta.glob("../../../../../binaries/programs/wasm32/evdev_demo.wasm", { - query: "?url", import: "default", - }), - // espeak-ng publishes a wasm output plus a runtime file, so the resolver - // mirrors its whole closure under the package directory. - ...import.meta.glob("../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", { - query: "?url", import: "default", - }), - ...import.meta.glob("../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", { - query: "?url", import: "default", - }), - ...import.meta.glob("../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", { - query: "?url", import: "default", - }), - ...import.meta.glob("../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", { + ...import.meta.glob("../../../../../binaries/programs/wasm32/sdl2.wasm", { query: "?url", import: "default", }), } as Record Promise>; @@ -332,8 +324,7 @@ const LIVE_DEMO_IDS = [ "wordpress-mariadb", "doom", "modeset", - "evdev", - "espeak", + "sdl2", ] as const; type LiveDemoId = (typeof LIVE_DEMO_IDS)[number]; @@ -432,11 +423,9 @@ const LIVE_DEMO_SPECS: Record = { image: "shell", features: ["kms"], }, - evdev: { - image: "shell", - }, - espeak: { + sdl2: { image: "shell", + features: ["kms"], }, }; @@ -496,21 +485,13 @@ interface LiveProfile { }; framebufferTest: boolean; /** - * Stage `evdev_demo` into `/usr/local/bin`, attach a `BrowserInputSource` - * to the window so keyboard/pointer events flow into the kernel's - * `/dev/input/event{0,1}`, and run the binary from bash so its event - * log streams to the user's Shell pane. + * Stage the SDL2 GLSL playground at `/usr/local/bin/sdl2` with its + * shader presets, attach a `BrowserInputSource` for the keyboard and + * wheel (the Modeset pane owns the pointer through `sendPointerAbs`), + * and run the binary from bash. Audio rides the /dev/dsp path every + * other sound demo uses. */ - evdevDemo: boolean; - /** - * Spawn `espeak-ng "..."` from the booted shell. espeak-ng links - * upstream pcaudiolib built with only its OSS backend, so - * `create_audio_device_object` falls through to `/dev/dsp` and a - * single binary invocation produces audible synthesised speech - * without any host-side pipeline. The binary + data dir are baked - * into the image via `stageEspeakRuntime`. - */ - espeakDemo: boolean; + sdl2Demo: boolean; } interface WebReadinessState { @@ -949,8 +930,7 @@ function customVfsProfile( shell: "default", maxVfsByteLength: CUSTOM_VFS_PROFILE_MAX_BYTES, framebufferTest: fb === "test", - evdevDemo: false, - espeakDemo: false, + sdl2Demo: false, }; } @@ -999,8 +979,7 @@ function profileFor(id: string, fb?: FbDemo): LiveProfile { }, }, framebufferTest: fb === "test", - evdevDemo: normalized === "evdev", - espeakDemo: normalized === "espeak", + sdl2Demo: normalized === "sdl2", }; } @@ -1277,16 +1256,11 @@ async function bootProfile( ensureDirRecursive(buildFs, dirname(profile.init.argv[0])); writeVfsBinary(buildFs, profile.init.argv[0], new Uint8Array(bytes), 0o755); } - // Both demos run their binary from a path, so the bytes have to be in the + // The demo runs its binary from a path, so the bytes have to be in the // image before the worker takes exclusive ownership of the VFS. - if (profile.espeakDemo) { - tick("staging espeak-ng..."); - await stageEspeakRuntime(buildFs); - assertCurrent(); - } - if (profile.evdevDemo) { - tick("staging evdev_demo..."); - await stageEvdevDemo(buildFs); + if (profile.sdl2Demo) { + tick("staging sdl2..."); + await stageSdl2Runtime(buildFs); assertCurrent(); } ensureDemoHomes(buildFs); @@ -1521,46 +1495,46 @@ async function bootProfile( tick, assertCurrent, ); - } else if (profile.espeakDemo) { - // The binary and its voice data are already in the image; see - // stageEspeakRuntime. Playback rides the /dev/dsp path every other - // sound demo uses. - void (async () => { - try { - tick("running espeak-ng..."); - await host.runShellCommand( - `/usr/bin/espeak-ng "Welcome to Kandelo, the WebAssembly POSIX kernel"`, - ); - tick("espeak-ng exited"); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - tick(`espeak-ng failed: ${msg}`); - } - })(); - } else if (profile.evdevDemo) { + } else if (profile.sdl2Demo) { // autoCommand can't run this: the InputSource must be attached before - // the binary starts polling /dev/input/event{0,1}. The binary itself is - // already in the image; see stageEvdevDemo. - const kernelForEvdev = kernel; + // the binary starts polling /dev/input/event{0,1}. The binary and its + // shader presets are already in the image; see stageSdl2Runtime. + const kernelForSdl2 = kernel; void (async () => { try { tick("attaching input source..."); - kernelForEvdev.attachInputSource(new BrowserInputSource(window), { - width: window.innerWidth, - height: window.innerHeight, - }); - tick("running evdev_demo..."); - // evdev_demo runs forever; runShellCommand resolves when the - // bash prompt reappears (it never will) or rejects after its - // internal 5-minute timeout. Both are expected — log neutrally. - await host.runShellCommand("/usr/local/bin/evdev_demo"); - tick("evdev_demo exited"); + // Keyboard goes through BrowserInputSource (typing, ESC → evdev + // event0). The POINTER is owned by the Modeset pane, which feeds + // framebuffer-positioned pointer events into evdev event1 via + // `sendPointerAbs` — so this source's pointer feed is disabled + // (its window-relative coordinates would fight the pane's + // correct ones). WHEEL stays enabled: REL_WHEEL carries no + // absolute coordinates, so it doesn't fight the pane, and it + // drives the editor's mouse-scroll (SDL_MOUSEWHEEL). + // The dims set EVIOCGABS's ABS_X/Y.maximum. SDL treats event1 + // as a relative mouse (it advertises REL_X/Y) and clamps the + // cursor to the window rather than this range, but the + // framebuffer size (1920×1080, matching + // host/src/dri/kms-registry.ts and the Modeset canvas) keeps + // the bounds sane for any ABS-aware consumer. + const SDL2_FB_W = 1920; + const SDL2_FB_H = 1080; + kernelForSdl2.attachInputSource( + new BrowserInputSource(window, { pointer: false, wheel: true }), + { width: SDL2_FB_W, height: SDL2_FB_H }, + ); + tick("running sdl2..."); + // The playground runs until ESC; runShellCommand resolves when + // the bash prompt reappears or rejects after its internal + // 5-minute timeout. Both are expected — log neutrally. + await host.runShellCommand("/usr/local/bin/sdl2"); + tick("sdl2 exited"); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (/timed out waiting for PTY prompt/.test(msg)) { - tick("evdev_demo running (long-tail; no further status updates)"); + tick("sdl2 running (long-tail; no further status updates)"); } else { - tick(`evdev_demo failed: ${msg}`); + tick(`sdl2 failed: ${msg}`); } } })(); @@ -1640,51 +1614,41 @@ function stageShellUtilities( } /** - * Bake espeak-ng and its voice data into the image. + * Bake the SDL2 GLSL playground and its shader presets into the image. * - * Both come from the espeak-ng package closure, so the demo consumes the same - * bytes the resolver published. libespeak-ng's PATH_ESPEAK_DATA is fixed to - * /usr/share at build time, so the data tree has to land unpacked there. + * The playground's source-resolution chain is + * 1. /home/shaders//current.frag (user-editable) + * 2. /usr/share/shaders//.frag (preset) + * 3. built-in fallback compiled into main.c + * Staging (2) makes the browser path exercise the VFS leg; + * /home/shaders/ is created so Ctrl+S can write (1) without first + * creating directories. tunnelwisp is the boot default for both modes; + * the others are loadable through the editor's Ctrl+L preset browser. */ -async function stageEspeakRuntime(fs: MemoryFileSystem): Promise { - const binaryUrl = await optionalBinaryUrl([ - "../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", - "../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng.wasm", - ], "espeak-ng.wasm"); - const binary = await fetch(binaryUrl) - .then(failOn("espeak-ng.wasm")) - .then((r) => r.arrayBuffer()); - ensureDirRecursive(fs, "/usr/bin"); - writeVfsBinary(fs, "/usr/bin/espeak-ng", new Uint8Array(binary), 0o755); - - const dataUrl = await optionalBinaryUrl([ - "../../../../../local-binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", - "../../../../../binaries/programs/wasm32/espeak-ng/espeak-ng-data.zip", - ], "espeak-ng-data.zip"); - const data = await fetch(dataUrl) - .then(failOn("espeak-ng-data.zip")) - .then((r) => r.arrayBuffer()); - const zipBytes = new Uint8Array(data); - const root = "/usr/share/espeak-ng-data"; - ensureDirRecursive(fs, root); - for (const entry of parseZipCentralDirectory(zipBytes)) { - if (entry.isDirectory) continue; - const target = `${root}/${entry.fileName}`; - ensureDirRecursive(fs, target.slice(0, target.lastIndexOf("/"))); - writeVfsBinary(fs, target, extractZipEntry(zipBytes, entry), 0o644); - } -} - -async function stageEvdevDemo(fs: MemoryFileSystem): Promise { +async function stageSdl2Runtime(fs: MemoryFileSystem): Promise { const url = await optionalBinaryUrl([ - "../../../../../local-binaries/programs/wasm32/evdev_demo.wasm", - "../../../../../binaries/programs/wasm32/evdev_demo.wasm", - ], "evdev_demo.wasm"); + "../../../../../local-binaries/programs/wasm32/sdl2.wasm", + "../../../../../binaries/programs/wasm32/sdl2.wasm", + ], "sdl2.wasm"); const bytes = await fetch(url) - .then(failOn("evdev_demo.wasm")) + .then(failOn("sdl2.wasm")) .then((r) => r.arrayBuffer()); ensureDirRecursive(fs, "/usr/local/bin"); - writeVfsBinary(fs, "/usr/local/bin/evdev_demo", new Uint8Array(bytes), 0o755); + writeVfsBinary(fs, "/usr/local/bin/sdl2", new Uint8Array(bytes), 0o755); + + ensureDirRecursive(fs, "/usr/share/shaders/image"); + ensureDirRecursive(fs, "/home/shaders/image"); + writeVfsFile(fs, "/usr/share/shaders/image/plasma.frag", sdl2PlasmaFragSrc); + writeVfsFile(fs, "/usr/share/shaders/image/audio_bars.frag", sdl2AudioBarsFragSrc); + writeVfsFile(fs, "/usr/share/shaders/image/tunnelwisp.frag", sdl2TunnelwispFragSrc); + + ensureDirRecursive(fs, "/usr/share/shaders/sound"); + ensureDirRecursive(fs, "/home/shaders/sound"); + writeVfsFile(fs, "/usr/share/shaders/sound/tunnelwisp.frag", sdl2SoundTunnelwispFragSrc); + writeVfsFile(fs, "/usr/share/shaders/sound/sine.frag", sdl2SoundSineFragSrc); + writeVfsFile(fs, "/usr/share/shaders/sound/fm_bell.frag", sdl2SoundFmBellFragSrc); + writeVfsFile(fs, "/usr/share/shaders/sound/noise_sweep.frag", sdl2SoundNoiseSweepFragSrc); + writeVfsFile(fs, "/usr/share/shaders/sound/chord.frag", sdl2SoundChordFragSrc); } function ensureDemoHomes(fs: MemoryFileSystem): void { diff --git a/apps/browser-demos/pages/kandelo/panes/Modeset.tsx b/apps/browser-demos/pages/kandelo/panes/Modeset.tsx index 061b1c818a..e1625e0a1f 100644 --- a/apps/browser-demos/pages/kandelo/panes/Modeset.tsx +++ b/apps/browser-demos/pages/kandelo/panes/Modeset.tsx @@ -147,10 +147,21 @@ export const Modeset: React.FC = ({ crtcId = 1, onDockControlsChan prevCanvasX = canvasX; prevCanvasY = canvasY; }; + // Absolute-position pointer feed for evdev consumers (SDL2's + // KMSDRM backend reads `/dev/input/event1`, which the PS/2 + // `sendMouseEvent` path above does NOT reach). `toCanvasCoords` + // already maps the OS pointer into framebuffer pixels (0..canvas + // .width), exactly the range SDL expects after we set the kernel's + // ABS_X/Y.maximum to the framebuffer size. modeset.c ignores + // event1 (it reads PS/2 /dev/input/mice), so feeding both is safe. + const sendAbs = (canvasX: number, canvasY: number) => { + handleRef.current?.sendPointerAbs(canvasX, canvasY, buttons); + }; const onPointerEnter = (e: PointerEvent) => { if (e.pointerType === "touch") return; const c = toCanvasCoords(e.clientX, e.clientY); handlePointerAt(c.x, c.y); + sendAbs(c.x, c.y); }; const onPointerLeave = (e: PointerEvent) => { if (e.pointerType === "touch") return; @@ -161,6 +172,7 @@ export const Modeset: React.FC = ({ crtcId = 1, onDockControlsChan if (e.pointerType === "touch" && e.pointerId !== activeTouchId) return; const c = toCanvasCoords(e.clientX, e.clientY); handlePointerAt(c.x, c.y); + sendAbs(c.x, c.y); }; const onPointerDown = (e: PointerEvent) => { if (e.pointerType === "touch") { @@ -181,7 +193,9 @@ export const Modeset: React.FC = ({ crtcId = 1, onDockControlsChan handlePointerAt(c.x, c.y); } buttons |= bit; + const c = toCanvasCoords(e.clientX, e.clientY); handleRef.current?.sendMouseEvent(0, 0, buttons); + sendAbs(c.x, c.y); }; const onPointerUp = (e: PointerEvent) => { if (e.pointerType === "touch") { @@ -194,7 +208,9 @@ export const Modeset: React.FC = ({ crtcId = 1, onDockControlsChan if (bit === 0) return; e.preventDefault(); buttons &= ~bit; + const c = toCanvasCoords(e.clientX, e.clientY); handleRef.current?.sendMouseEvent(0, 0, buttons); + sendAbs(c.x, c.y); }; const onPointerCancel = (e: PointerEvent) => { if (e.pointerType === "touch" && e.pointerId !== activeTouchId) return; diff --git a/apps/browser-demos/pages/kandelo/presets.ts b/apps/browser-demos/pages/kandelo/presets.ts index 9334246892..22ec9de392 100644 --- a/apps/browser-demos/pages/kandelo/presets.ts +++ b/apps/browser-demos/pages/kandelo/presets.ts @@ -129,24 +129,13 @@ export const PRESET_LIBRARY: Preset[] = [ estimatedUrlBytes: 612, }, { - id: "evdev", - title: "Evdev input log", - summary: "Keystrokes + pointer motion captured from the DOM and replayed through /dev/input/event{0,1}.", + id: "sdl2", + title: "SDL2 GLSL playground", + summary: "SDL2 GLSL shader playground: a live-coding editor (left) beside a GLES2 fragment shader rendered on a 1920×1080 /dev/dri/card0 surface (right), auto-recompiling 250 ms after you stop typing. F1/F2 switch between the image and sound shader, Ctrl+L cycles presets, Ctrl+S persists, ESC quits.", base: SHELL_BASE, packages: ["bash@local", "coreutils@local"], - accent: "#7e57c2", - glyph: "E", - bootCommand: ["bash", "-l", "-i"], - estimatedUrlBytes: 612, - }, - { - id: "espeak", - title: "OSS - Espeak-NG", - summary: "The kernel speaks: espeak-ng synthesises text directly through libpcaudio's OSS backend.", - base: SHELL_BASE, - packages: ["bash@local", "coreutils@local"], - accent: "#f48fb1", - glyph: "T", + accent: "#9c27b0", + glyph: "S", bootCommand: ["bash", "-l", "-i"], estimatedUrlBytes: 612, }, diff --git a/apps/browser-demos/test/kandelo-espeak.spec.ts b/apps/browser-demos/test/kandelo-espeak.spec.ts deleted file mode 100644 index 322928f659..0000000000 --- a/apps/browser-demos/test/kandelo-espeak.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { expect, test, type Page } from "@playwright/test"; - -const appUrl = (path: string): string => { - const baseUrl = process.env.KANDELO_TEST_BASE_URL; - return baseUrl ? new URL(path, baseUrl).href : path; -}; - -async function gotoOrSkip(page: Page, path: string) { - await page.goto(appUrl(path), { waitUntil: "domcontentloaded" }); - await page.waitForTimeout(2_000); - if (await page.locator("vite-error-overlay").count()) { - test.skip(true, "Required binary not built - Vite import error"); - } -} - -async function terminalText(page: Page): Promise { - return page.locator(".xterm-rows").first().evaluate((node) => node.textContent ?? ""); -} - -test("Kandelo espeak-ng demo speaks through pcaudiolib + /dev/dsp", async ({ page }) => { - test.setTimeout(300_000); - - await gotoOrSkip(page, "/?demo=espeak"); - - // Web Audio starts only after a trusted gesture. App.tsx activates the - // PCM sink from a capturing pointerdown listener, so a physical click - // anywhere moves the machine to its running audio state. - await page.locator("body").click({ position: { x: 5, y: 5 } }); - await expect(page.locator("[data-audio-state]")).toHaveAttribute( - "data-audio-state", - "running", - { timeout: 60_000 }, - ); - - // The boot-path branch in live-setup.ts runs - // `espeak-ng "Welcome to Kandelo, the WebAssembly POSIX kernel"`. - // espeak-ng prints a few status lines on stderr; the more reliable - // signal that the synth path worked end-to-end is the bash prompt - // reappearing after the binary exits. We watch for the trailing - // shell prompt instead of a specific espeak output line so the test - // doesn't break on cosmetic CLI changes upstream. pcaudiolib aborts - // the run when it cannot open /dev/dsp, so reaching the prompt with - // a running sink proves the OSS backend negotiated the device. - await expect - .poll(() => terminalText(page), { timeout: 180_000 }) - .toMatch(/[#$]\s*$/); -}); diff --git a/apps/browser-demos/test/kandelo-evdev.spec.ts b/apps/browser-demos/test/kandelo-evdev.spec.ts deleted file mode 100644 index 8d404bace8..0000000000 --- a/apps/browser-demos/test/kandelo-evdev.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { expect, test, type Page } from "@playwright/test"; - -const appUrl = (path: string): string => { - const baseUrl = process.env.KANDELO_TEST_BASE_URL; - return baseUrl ? new URL(path, baseUrl).href : path; -}; - -async function gotoOrSkip(page: Page, path: string) { - await page.goto(appUrl(path), { waitUntil: "domcontentloaded" }); - await page.waitForTimeout(2_000); - if (await page.locator("vite-error-overlay").count()) { - test.skip(true, "Required binary not built - Vite import error"); - } -} - -async function terminalText(page: Page): Promise { - return page.locator(".xterm-rows").first().evaluate((node) => node.textContent ?? ""); -} - -test("Kandelo evdev demo forwards keystrokes + pointer through /dev/input/event{0,1}", async ({ page }) => { - test.setTimeout(300_000); - - await gotoOrSkip(page, "/?demo=evdev"); - - // The evdev_demo binary prints "ready:" once both /dev/input/event0 - // and /dev/input/event1 have been opened and EVIOCGNAME has succeeded - // on both. Waiting for that proves: the binary was staged into the - // VFS, bash exec'd it, and the kernel's A3 EVIOC* dispatch returned - // the correct device names. - await expect - .poll(() => terminalText(page), { timeout: 180_000 }) - .toContain("ready:"); - - const readyText = await terminalText(page); - expect(readyText).toContain("kbd: wpk virtual keyboard"); - expect(readyText).toContain("ptr: wpk virtual pointer"); - - // BrowserInputSource preventDefaults every key it translates, so when - // the terminal pane is focused the only way "key down: code=30" - // (KEY_A) can appear in the xterm output is if BrowserInputSource - // caught the keydown, dispatched into kernel_input_event, the kernel - // fanned out to /dev/input/event0, and evdev_demo's read returned it. - // The dual-host parity claim of B4 is what this proves end-to-end. - await page.keyboard.press("KeyA"); - await expect - .poll(() => terminalText(page), { timeout: 15_000 }) - .toMatch(/key down: code=30/); - - // Pointer move → ABS_X/ABS_Y (pointer-lock inactive) → evdev_demo - // prints "ptr abs code=0 value=N" (REL_X==ABS_X==0 in Linux UAPI). - // The exact value depends on which DOM element pointermove fires on - // and its offsetX/offsetY, so just assert the shape of the line. - await page.mouse.move(100, 200); - await page.mouse.move(150, 250); - await expect - .poll(() => terminalText(page), { timeout: 15_000 }) - .toMatch(/ptr (abs|rel) code=\d+ value=-?\d+/); -}); diff --git a/apps/browser-demos/test/kandelo-merge-gate.spec.ts b/apps/browser-demos/test/kandelo-merge-gate.spec.ts index f3b5e6c3f2..9818d9631e 100644 --- a/apps/browser-demos/test/kandelo-merge-gate.spec.ts +++ b/apps/browser-demos/test/kandelo-merge-gate.spec.ts @@ -287,6 +287,10 @@ test("Kandelo shell demo runs bash, vim, and NetHack", async ({ page }) => { await gotoOrSkip(page, "/?demo=shell"); await waitForReady(page); await expect(page.locator(".xterm-rows").first()).toBeVisible({ timeout: 120_000 }); + // Input typed before bash's first prompt is legitimately discarded by the + // boot chain's startup typeahead flush (tcflush), so wait for the prompt + // like the other terminal tests do. + await waitForTerminalContent(page, /kandelo\$ ?/, 120_000); await runGuideScript( page, diff --git a/apps/browser-demos/test/kandelo-sdl2.spec.ts b/apps/browser-demos/test/kandelo-sdl2.spec.ts new file mode 100644 index 0000000000..c0599c32af --- /dev/null +++ b/apps/browser-demos/test/kandelo-sdl2.spec.ts @@ -0,0 +1,134 @@ +import { expect, test, type Page } from "@playwright/test"; + +const appUrl = (path: string): string => { + const baseUrl = process.env.KANDELO_TEST_BASE_URL; + return baseUrl ? new URL(path, baseUrl).href : path; +}; + +async function gotoOrSkip(page: Page, path: string) { + await page.goto(appUrl(path), { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(2_000); + if (await page.locator("vite-error-overlay").count()) { + test.skip(true, "Required binary not built - Vite import error"); + } +} + +async function openInternals(page: Page) { + const internals = page.getByRole("button", { name: "Internals" }); + if ((await internals.getAttribute("aria-pressed")) !== "true") { + await internals.click(); + } +} + +async function syslogText(page: Page): Promise { + const lines = await page.locator(".ksys-line").allInnerTexts(); + return lines.join("\n"); +} + +async function terminalText(page: Page): Promise { + const count = await page.locator(".xterm-rows").count(); + if (count === 0) return ""; + return page.locator(".xterm-rows").first().evaluate((n) => n.textContent ?? ""); +} + +test("Kandelo sdl2 demo (editor left + shader right, ESC quits)", async ({ page }) => { + test.setTimeout(240_000); + + await gotoOrSkip(page, "/?demo=sdl2"); + + await openInternals(page); + await expect + .poll(() => syslogText(page), { timeout: 90_000 }) + .toMatch(/running sdl2/); + + const canvas = page.locator(".kmodeset-canvas").first(); + await expect(canvas).toBeVisible({ timeout: 30_000 }); + + /* Wait for the app's FIRST presented frame before sampling anything. + * "running sdl2" only means the process launched: between that and + * the first drmModePageFlip sit EGL/WebGL2 context creation, shader + * compiles, the text-atlas bake, and the sound-shader tile + * prerender + readback — under CI's software GL (SwiftShader) that + * init can outlast the whole sampling window, so sampling + * immediately just screenshots the Modeset pane's static + * "waiting for PAGE_FLIP" placeholder and fails as "not animating". + * The pane unmounts the placeholder exactly when the KMS stats SAB + * reports the first flip. */ + await expect( + page.getByText(/Waiting for PAGE_FLIP on CRTC/), + ).toBeHidden({ timeout: 90_000 }); + + /* The Modeset pane uses transferControlToOffscreen (see + * apps/browser-demos/pages/kandelo/panes/Modeset.tsx), which makes + * in-page drawImage(canvas, 0, 0) + getImageData unreliable on the + * placeholder canvas, and canvas.screenshot() returns the CSS + * bounding box (including letterbox background) at a size that + * differs from the canvas backing buffer. Use PNG byteLength + * variance instead: + * - Full-frame: an animating shader on the right pane compresses + * to different sizes across frames; a static (or blank) canvas + * does not. + * - Left-half clip: text glyph rendering produces a much larger + * PNG than a flat-gray clear. If the editor never drew, the + * left half compresses to ~1-2 KB; with text it lands well + * above 3 KB even with cursor blink off. */ + const fullSizes: number[] = []; + const leftSizes: number[] = []; + const bb = await canvas.boundingBox(); + if (!bb) throw new Error("canvas bounding box unavailable"); + const leftClip = { + x: bb.x, + y: bb.y, + width: Math.floor(bb.width / 2), + height: bb.height, + }; + /* Sample until byte-size variance appears instead of a fixed + * 6×500 ms schedule: under software GL the app may present only a + * few frames per second, so animation evidence needs a deadline, + * not a fixed sample count. */ + await expect + .poll( + async () => { + await page.waitForTimeout(500); + const full = await canvas.screenshot(); + const left = await page.screenshot({ clip: leftClip }); + fullSizes.push(full.byteLength); + leftSizes.push(left.byteLength); + return Math.max(...fullSizes) - Math.min(...fullSizes); + }, + { timeout: 30_000, message: "right pane not animating (no PNG byte-size variance within 30 s)" }, + ) + .toBeGreaterThan(400); + for (const sz of fullSizes) { + expect( + sz, + `canvas frame too small (blank?); fullSizes=${fullSizes.join(",")}`, + ).toBeGreaterThan(3_500); + } + /* Left half should be much bigger than a flat-gray PNG because + * Inconsolata glyphs add high-frequency detail. A flat-color + * rectangle of this size compresses to a few hundred bytes; with + * baked text we expect >3 KB consistently. */ + for (const sz of leftSizes) { + expect( + sz, + `left pane has no editor content; leftSizes=${leftSizes.join(",")}`, + ).toBeGreaterThan(3_000); + } + + /* ESC is the only exit path. Click body to take focus off the + * canvas, then dispatch Escape to BrowserInputSource → SDL_evdev. */ + await page.locator("body").click({ position: { x: 5, y: 5 } }); + await page.keyboard.press("Escape"); + + await openInternals(page); + await expect + .poll(() => syslogText(page), { timeout: 30_000 }) + .toMatch(/sdl2 exited/); + expect(await syslogText(page), "sdl2 reported failure") + .not.toMatch(/sdl2 failed/); + + await expect + .poll(() => terminalText(page), { timeout: 30_000 }) + .toMatch(/sdl2: OK frames=\d+ elapsed=\d+ ms exit=esc/); +}); diff --git a/docs/architecture.md b/docs/architecture.md index 952f927d3d..c1702095b4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2577,8 +2577,11 @@ the commands against a browser `WebGL2RenderingContext` or a test double in Node.js. The kernel does not contain GL rendering code. The user-space libraries are sysroot libraries, not kernel build outputs: -`scripts/build-musl.sh` installs the headers and builds `sysroot/lib/libdrm.a`, -`libgbm.a`, `libEGL.a`, and `libGLESv2.a`, plus matching pkg-config files. +`scripts/build-musl.sh` installs the headers, resolves the `libdrm` package +into `sysroot/lib/libdrm.a`, and builds `libgbm.a`, `libEGL.a`, and +`libGLESv2.a` from `libc/glue/`, plus matching pkg-config files. `libdrm` is +upstream 2.4.120, KMS subset only — the kernel implements enough of the +`DRM_IOCTL_MODE_*` surface for unmodified upstream sources to drive it. Packages that depend on these APIs link through `wasm32posix-pkg-config` and declare their resulting program artifacts as packages. The `modeset` demo is one such package: its VFS image installs `/usr/local/bin/modeset`, and diff --git a/docs/browser-support.md b/docs/browser-support.md index 84bb4aefd1..5a1967a5ef 100644 --- a/docs/browser-support.md +++ b/docs/browser-support.md @@ -321,8 +321,7 @@ Located in `apps/browser-demos/pages/`: | benchmark | (per-suite) | legacy spawn | Micro-benchmarks + WordPress + Erlang ring | | network | dash + GNU Netcat + curl | `kernel.boot` x 3 | Boots multiple local Kandelo machines and verifies UDP datagrams, TCP streams, and HTTP over virtual TCP | | doom | fbDOOM | legacy spawn | `/dev/fb0` framebuffer + canvas renderer + keyboard via stdin + mouse via `/dev/input/mice` (pointer-locked) + SFX **and** OPL2-synthesized music via `/dev/dsp` → AudioContext. The shareware `doom1.wad` is **fetched at page load** from a commit-pinned CDN URL (SHA-256 verified, Cache API cached); no IWAD ships in the package archive. | -| evdev | evdev_demo | dinit | Reads `/dev/input/event{0,1}` and prints each record. A `BrowserInputSource` translates DOM key and pointer events into `EV_KEY`/`EV_REL`/`EV_ABS` and pushes them through `kernel_input_event`. The binary comes from the `evdev-demo` package and is baked into the image before boot; the input source is attached first, because the binary polls as soon as it runs. | -| espeak | espeak-ng | dinit | Speech synthesis through upstream pcaudiolib's OSS backend, so playback rides the same `/dev/dsp` path as the doom demo. The binary and the voice data both come from the `espeak-ng` package closure — the data as the `espeak-ng-data.zip` runtime file, unpacked into `/usr/share/espeak-ng-data` while the image is composed, because libespeak-ng's `PATH_ESPEAK_DATA` is fixed at build time. | +| sdl2 | SDL2 GLSL playground | dinit | Live-coding shader editor on SDL2's KMSDRM backend: gap-buffer editor left, GLES2 fragment shader on `/dev/dri/card0` right, chip synth / sound shader through `/dev/dsp`. The binary comes from the `sdl2-demo` package and is baked into the image with its shader presets before boot. A `BrowserInputSource` feeds the keyboard and wheel into `/dev/input/event{0,1}`; the Modeset pane owns the pointer and injects framebuffer-absolute coordinates via `sendPointerAbs`. | The "Boot pattern" column reflects how the demo enters the kernel: - **`kernel.boot`** — `kernelOwnedFs: true`, exec the language interpreter as the first user process. diff --git a/docs/package-management.md b/docs/package-management.md index d1535803b1..c81d1c6159 100644 --- a/docs/package-management.md +++ b/docs/package-management.md @@ -973,10 +973,12 @@ source. ### Sysroot libraries are not packages Some APIs are part of the Kandelo sysroot rather than the package graph. The -DRI/EGL/GLES shims (`libdrm.a`, `libgbm.a`, `libEGL.a`, `libGLESv2.a`) are -built by `scripts/build-musl.sh` and exposed through -`wasm32posix-pkg-config`; they are not outputs of the `kernel` package and -should not be modeled as standalone package dependencies. +GBM/EGL/GLES shims (`libgbm.a`, `libEGL.a`, `libGLESv2.a`) are built by +`scripts/build-musl.sh` and exposed through `wasm32posix-pkg-config`; they are +not outputs of the `kernel` package and should not be modeled as standalone +package dependencies. `libdrm.a` sits beside them in the sysroot and is +reached the same way, but it *is* a package — `scripts/build-dri-stubs.sh` +resolves `packages/registry/libdrm` and symlinks the result in. A package that depends on those libraries should: @@ -986,7 +988,8 @@ A package that depends on those libraries should: `egl`, and/or `glesv2`. 3. Declare only the consumer artifact in `[[outputs]]`. 4. Add the relevant sysroot/glue inputs (`libc/glue/lib*_stub.c`, - `libc/glue/gl_abi.h`, `scripts/build-musl.sh`, `scripts/build-dri-stubs.sh`, + `libc/glue/gl_abi.h`, `packages/registry/libdrm/*`, + `scripts/build-musl.sh`, `scripts/build-dri-stubs.sh`, `scripts/build-gles-stubs.sh`) to `build.toml.inputs` so cache keys move when the sysroot implementation changes. diff --git a/host/src/input/browser-input-source.ts b/host/src/input/browser-input-source.ts index fd7e9e7947..7cabc505a3 100644 --- a/host/src/input/browser-input-source.ts +++ b/host/src/input/browser-input-source.ts @@ -34,16 +34,36 @@ export class BrowserInputSource implements InputSource { private dispatch: ((ev: InputEvent) => void) | null = null; private bindings: Array<[EventTarget, string, EventListener]> = []; - constructor(private target: EventTarget = window) {} + /** + * @param target Event source to bind to (defaults to `window`). + * @param opts.pointer When `false`, the pointer-motion/button handlers + * are not bound. Used when another surface owns the pointer feed (e.g. + * the kandelo Modeset pane injects framebuffer-absolute coordinates + * into `/dev/input/event1` itself, and a second window-relative feed + * here would fight it). + * @param opts.wheel Overrides whether the wheel handler is bound. + * Defaults to following `pointer`. Wheel events are `REL_WHEEL` and do + * NOT carry absolute coordinates, so they don't conflict with a pane + * that owns absolute positioning — `{ pointer: false, wheel: true }` + * lets that pane keep the pointer while the wheel still scrolls. + */ + constructor( + private target: EventTarget = window, + private opts: { pointer?: boolean; wheel?: boolean } = {}, + ) {} start(dispatch: (ev: InputEvent) => void): void { this.dispatch = dispatch; this.bind("keydown", this.onKeyDown); this.bind("keyup", this.onKeyUp); - this.bind("pointermove", this.onPointerMove); - this.bind("pointerdown", this.onPointerDown); - this.bind("pointerup", this.onPointerUp); - this.bind("wheel", this.onWheel); + if (this.opts.pointer !== false) { + this.bind("pointermove", this.onPointerMove); + this.bind("pointerdown", this.onPointerDown); + this.bind("pointerup", this.onPointerUp); + } + if (this.opts.wheel ?? this.opts.pointer !== false) { + this.bind("wheel", this.onWheel); + } // `pointerlockchange` only fires on document, never on window — so // it can't go through this.bind which is parametric over `target`. // Tracked in `bindings` for symmetric removal in stop(). diff --git a/host/test/gbm-surface-ring.test.ts b/host/test/gbm-surface-ring.test.ts new file mode 100644 index 0000000000..57a16e2efa --- /dev/null +++ b/host/test/gbm-surface-ring.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { runCentralizedProgram } from "./centralized-test-helper"; +import { tryResolveBinary } from "../src/binary-resolver"; + +const programBinary = tryResolveBinary("programs/gbm_surface_smoke.wasm"); + +describe("libgbm 2-BO surface ring", () => { + it.skipIf(!programBinary)( + "lock/release/has_free_buffers transitions match mesa semantics", + async () => { + const result = await runCentralizedProgram({ + programPath: programBinary!, + argv: ["gbm_surface_smoke"], + timeout: 5_000, + }); + expect( + result.exitCode, + `stdout=${result.stdout} stderr=${result.stderr}`, + ).toBe(0); + expect(result.stdout.trim()).toBe("OK"); + expect(result.stderr).not.toContain("FAIL:"); + }, + ); +}); diff --git a/host/test/sdl2-editor-unit.test.ts b/host/test/sdl2-editor-unit.test.ts new file mode 100644 index 0000000000..86da8d4d88 --- /dev/null +++ b/host/test/sdl2-editor-unit.test.ts @@ -0,0 +1,60 @@ +/* + * Native unit test for the pure gap-buffer logic in + * programs/sdl2/editor.c. The editor module depends only on renderer.h + * (stubbed in programs/sdl2/test/editor_test.c), so we compile it for + * the host and run it here — no wasm/kernel/SDL needed. This gives the + * selection / clipboard-dup / auto-replace / undo-redo logic a dedicated + * gate that the wasm integration test (sdl2.test.ts) can't observe, since + * those behaviors emit no stdout breadcrumb. + */ +import { execFileSync } from "node:child_process"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(__dirname, "../.."); +const SDL2_DIR = join(REPO_ROOT, "programs/sdl2"); + +import { describe, expect, it } from "vitest"; + +/* Pick the first working host C compiler. The CLAUDE.md gate runs vitest + * inside scripts/dev-shell.sh (clang present); macOS dev boxes have + * /usr/bin/clang and cc. */ +function findCompiler(): string | null { + for (const cc of ["cc", "clang", "gcc"]) { + try { + execFileSync(cc, ["--version"], { stdio: "ignore" }); + return cc; + } catch { + /* try the next candidate */ + } + } + return null; +} + +describe("SDL2 editor — pure gap-buffer logic (native unit test)", () => { + it("passes every editor_test.c case", () => { + const cc = findCompiler(); + expect(cc, "no host C compiler (cc/clang/gcc) found").not.toBeNull(); + + const editorC = join(SDL2_DIR, "editor.c"); + const testC = join(SDL2_DIR, "test/editor_test.c"); + expect(existsSync(editorC) && existsSync(testC)).toBe(true); + + const work = mkdtempSync(join(tmpdir(), "sdl2-editor-")); + const bin = join(work, "editor_test"); + try { + execFileSync( + cc!, + ["-std=c11", "-Wall", "-Wextra", "-Werror", "-O1", editorC, testC, "-o", bin], + { stdio: "pipe" }, + ); + const out = execFileSync(bin, { encoding: "utf8" }); + expect(out, out).toContain("editor_test: ALL PASS"); + } finally { + rmSync(work, { recursive: true, force: true }); + } + }); +}); diff --git a/host/test/sdl2-evdev-smoke.test.ts b/host/test/sdl2-evdev-smoke.test.ts new file mode 100644 index 0000000000..3f85d329e4 --- /dev/null +++ b/host/test/sdl2-evdev-smoke.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { runCentralizedProgram } from "./centralized-test-helper"; +import { tryResolveBinary } from "../src/binary-resolver"; + +const programBinary = tryResolveBinary("programs/sdl2_evdev_smoke.wasm"); + +describe("SDL2 evdev input backend", () => { + it.skipIf(!programBinary)( + "SDL_Init(EVENTS) + SDL_PumpEvents() round-trips on the wasm32 single-threaded path", + async () => { + const result = await runCentralizedProgram({ + programPath: programBinary!, + argv: ["sdl2_evdev_smoke"], + timeout: 10_000, + }); + expect( + result.exitCode, + `stdout=${result.stdout} stderr=${result.stderr}`, + ).toBe(0); + expect(result.stdout).toContain("OK evdev"); + expect(result.stderr).not.toContain("FAIL:"); + }, + ); +}); diff --git a/host/test/sdl2-kmsdrm-smoke.test.ts b/host/test/sdl2-kmsdrm-smoke.test.ts new file mode 100644 index 0000000000..b1e649e9e1 --- /dev/null +++ b/host/test/sdl2-kmsdrm-smoke.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { runCentralizedProgram } from "./centralized-test-helper"; +import { tryResolveBinary } from "../src/binary-resolver"; + +const programBinary = tryResolveBinary("programs/sdl2_kmsdrm_smoke.wasm"); + +describe("SDL2 KMSDRM video backend", () => { + it.skipIf(!programBinary)( + "SDL_Init(VIDEO) selects KMSDRM against /dev/dri/card0", + async () => { + const result = await runCentralizedProgram({ + programPath: programBinary!, + argv: ["sdl2_kmsdrm_smoke"], + timeout: 10_000, + }); + expect( + result.exitCode, + `stdout=${result.stdout} stderr=${result.stderr}`, + ).toBe(0); + // "KMSDRM" — confirms SDL2 didn't silently downgrade to "dummy". + expect(result.stdout).toContain("OK kmsdrm KMSDRM"); + expect(result.stderr).not.toContain("FAIL:"); + }, + ); +}); diff --git a/host/test/sdl2.test.ts b/host/test/sdl2.test.ts new file mode 100644 index 0000000000..9e6bd93ada --- /dev/null +++ b/host/test/sdl2.test.ts @@ -0,0 +1,231 @@ +/** + * End-to-end gate for `programs/sdl2.wasm` (KMSDRM video + ALSA audio + + * evdev input) run inside the centralised kernel under NodeKernelHost. + * It drives the binary with injected input events and asserts on the + * stdout breadcrumbs each subsystem emits — see the inline expectations + * below for the exact behaviors covered (source-resolution fallback, + * editor + atlas init, debounced recompile, F5 reload, synth + FFT + * upload, mute, sound-shader render, wheel scroll, preset cycle). + * + * Node has no real GL context, so host_gl_query returns -1: shader + * compiles take the headless "empty info log" branch and sound readback + * is all-zero (audible=0). Visual/audible gates live in the Playwright + * spec; the pure editor logic has its own native unit test in + * host/test/sdl2-editor-unit.test.ts. + */ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { NodeKernelHost } from "../src/node-kernel-host"; +import { tryResolveBinary } from "../src/binary-resolver"; + +const programBinary = tryResolveBinary("programs/sdl2.wasm"); + +const EV_SYN = 0x00; +const EV_KEY = 0x01; +const EV_REL = 0x02; +const REL_WHEEL = 0x08; /* mouse-wheel notch on /dev/input/event1 */ +const SYN_REPORT = 0x00; +const KEY_ESC = 1; +const KEY_F5 = 63; +const KEY_A = 30; /* linux/input-event-codes.h: typing 'a' */ +const KEY_LEFTCTRL = 29; +const KEY_M = 50; /* Ctrl+M toggles audio mute */ +const KEY_F2 = 60; /* F2 switches the editor to the sound shader */ +const KEY_L = 38; /* Ctrl+L cycles to the next shader preset */ + +async function waitFor( + stdoutRef: { value: string }, + needle: string, + timeoutMs: number, +): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (stdoutRef.value.includes(needle)) return; + await new Promise((r) => setTimeout(r, 10)); + } + throw new Error( + `Timed out waiting for ${JSON.stringify(needle)}.\n` + + `stdout so far:\n${stdoutRef.value}`, + ); +} + +describe("SDL2 playground — editor + audio + sound-shader end-to-end", () => { + it.skipIf(!programBinary)( + "loads the editor, bakes the atlas, debounces recompile on type, exits on ESC", + async () => { + const fileBuf = readFileSync(programBinary!); + const programBytes = fileBuf.buffer.slice( + fileBuf.byteOffset, + fileBuf.byteOffset + fileBuf.byteLength, + ); + + const stdout = { value: "" }; + const stderr = { value: "" }; + const host = new NodeKernelHost({ + onStdout: (_pid, data) => { + stdout.value += new TextDecoder().decode(data); + }, + onStderr: (_pid, data) => { + stderr.value += new TextDecoder().decode(data); + }, + }); + + try { + await host.init(); + host.setInputCanvasDims(1280, 720); + + const exitPromise = host.spawn(programBytes, ["sdl2"]); + + await waitFor(stdout, "sdl2: SDL_Init OK", 10_000); + + /* Wait long enough for the editor and atlas init to log, + * then inject: + * 1. a typing key (KEY_A) so the debounce timer arms, + * 2. F5 to exercise the "user file missing" branch, + * 3. ESC to quit. + * After KEY_A, wait for the recompile breadcrumb itself rather + * than sleeping a fixed 300 ms: on a slow CI runner the app's + * init can outlast wall-clock sleeps, so every queued key would + * drain in one SDL_PollEvent burst — and the F2 mode switch + * below cancels a still-pending debounce (last_edit_ts = 0), + * which is correct app behavior. Only injecting the next key + * after the recompile logs keeps the debounce observable at any + * loop speed. */ + await waitFor(stdout, "sdl2: editor loaded", 5_000); + await waitFor(stdout, "sdl2: text-atlas baked", 5_000); + + host.injectInputEvent(0, EV_KEY, KEY_A, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_A, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + /* AUTO_RECOMPILE_DEBOUNCE_MS=250 fires inside the main loop. */ + await waitFor(stdout, "sdl2: editor recompile", 10_000); + + host.injectInputEvent(0, EV_KEY, KEY_F5, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_F5, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 100)); + + /* Ctrl+M toggles the audio mute. Hold LeftCtrl, tap M, release. */ + host.injectInputEvent(0, EV_KEY, KEY_LEFTCTRL, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + host.injectInputEvent(0, EV_KEY, KEY_M, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_M, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + host.injectInputEvent(0, EV_KEY, KEY_LEFTCTRL, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 100)); + + /* F2 switches to the sound shader, which compiles + + * renders + reads back the FBO. Under headless Node GL the + * readback is all-zero, so it reports audible=0 and audio falls + * back to the synth — but the mode switch and render path run. */ + host.injectInputEvent(0, EV_KEY, KEY_F2, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_F2, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 150)); + + /* Mouse-wheel scroll on /dev/input/event1: SDL turns REL_WHEEL + * into SDL_MOUSEWHEEL, and (pointer defaults to the editor pane at + * x=0) main.c scrolls the editor and logs the notch count. */ + host.injectInputEvent(1, EV_REL, REL_WHEEL, -1); + host.injectInputEvent(1, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 50)); + + /* Ctrl+L exercises the preset-cycle path (readdir of + * /usr/share/shaders/). The NodeKernelHost rootfs stages no + * preset files, so this must surface the graceful "list empty" + * branch rather than crashing or hanging. */ + host.injectInputEvent(0, EV_KEY, KEY_LEFTCTRL, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + host.injectInputEvent(0, EV_KEY, KEY_L, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_L, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + host.injectInputEvent(0, EV_KEY, KEY_LEFTCTRL, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 50)); + + host.injectInputEvent(0, EV_KEY, KEY_ESC, 1); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + await new Promise((r) => setTimeout(r, 10)); + host.injectInputEvent(0, EV_KEY, KEY_ESC, 0); + host.injectInputEvent(0, EV_SYN, SYN_REPORT, 0); + + const exitCode = await Promise.race([ + exitPromise, + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error( + `ESC quit timed out.\nstdout=${stdout.value}\nstderr=${stderr.value}`, + ), + ), + 8_000, + ), + ), + ]); + expect( + exitCode, + `stdout=${stdout.value} stderr=${stderr.value}`, + ).toBe(0); + expect(stdout.value).toContain("sdl2: SDL_Init OK"); + expect(stdout.value).toMatch(/sdl2: OK frames=\d+ elapsed=\d+ ms exit=esc/); + expect(stderr.value).not.toContain("FAIL:"); + /* NodeKernelHost's rootfs has neither current.frag nor the + * staged preset, so the source chain must fall through to the + * built-in fallback. The Playwright path stages the preset + * and exercises the VFS leg. */ + expect(stdout.value).toContain("sdl2: shader-source=builtin-plasma"); + /* Editor + atlas init signals. */ + expect(stdout.value).toMatch(/sdl2: editor loaded \d+ chars/); + expect(stdout.value).toMatch(/sdl2: text-atlas baked=-?\d+/); + /* Typing fires the debounced auto-recompile path. */ + expect(stdout.value).toMatch(/sdl2: editor recompile/); + /* The chip synth initializes against the granted audio + * spec, and the per-frame FFT spectrum upload (iAudio) runs even + * under the headless Node GL stub. */ + expect(stdout.value).toMatch(/sdl2: audio synth rate=\d+ ch=\d+/); + expect(stdout.value).toMatch(/sdl2: audio spectrum uploaded bins=128/); + /* Ctrl+M toggled the mute. */ + expect(stdout.value).toContain("sdl2: audio muted"); + /* The sound-shader pipeline initialized, and F2 switched + * to sound mode and ran a render+readback dispatch. The track is + * rendered as multiple time-tiles; under the headless GL + * stub every tile's readback is silent (audible=0). */ + expect(stdout.value).toMatch(/sdl2: sound-shader init fbo=\d+x\d+ tiles=\d+/); + expect(stdout.value).toMatch(/sdl2: sound-source=builtin-sine/); + expect(stdout.value).toContain("sdl2: edit-mode=sound"); + expect(stdout.value).toMatch( + /sdl2: sound-shader render frames=\d+ tiles=\d+ audible=\d/, + ); + /* Mouse-wheel scroll: REL_WHEEL on event1 reaches the editor. */ + expect(stdout.value).toMatch(/sdl2: editor scroll wheel=-?\d+/); + /* Ctrl+L ran the preset-cycle path. With no staged presets in the + * Node rootfs it reports the empty list; with presets it loads one. */ + expect(stdout.value).toMatch(/sdl2: preset (load=|list empty)/); + /* F5 against a missing user file must surface as a WARN, not + * a crash: the binary stays alive long enough for ESC. */ + expect(stderr.value).toMatch( + /WARN: F5: \/home\/shaders\/image\/current\.frag not readable/, + ); + /* frames>0 proves the main loop ran at least one render pass. */ + const m = stdout.value.match(/frames=(\d+)/); + expect(m, `frames= not found in stdout: ${stdout.value}`).not.toBeNull(); + expect(Number(m![1])).toBeGreaterThan(0); + } finally { + await host.destroy(); + } + }, + 20_000, + ); +}); diff --git a/libc/glue/libdrm_stub.c b/libc/glue/libdrm_stub.c deleted file mode 100644 index 4cae267093..0000000000 --- a/libc/glue/libdrm_stub.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Minimal libdrm user-space shim for wasm-posix-kernel. - * - * Phase C of the DRI buffer-sharing plan - * (docs/plans/2026-05-25-dri-buffer-sharing-plan.md §C2). Provides - * the standard libdrm entry points so user code can - * `#include ` and `-ldrm` against this wasm32 sysroot. - * - * The kernel-side ioctl handlers live in - * crates/kernel/src/syscalls.rs::handle_dri_ioctl (Phase A); these - * wrappers issue the ioctls via channel_syscall.c::ioctl() and - * present a typical libdrm-shaped API to the caller. - * - * Scope (this file is intentionally small): - * - drmIoctl : EINTR-retrying ioctl(3) wrapper. - * - drmGetVersion : DRM_IOCTL_VERSION + synthesised - * client-side identity strings. - * - drmFreeVersion : frees a drmVersionPtr. - * - drmCloseBufferHandle : DRM_IOCTL_GEM_CLOSE wrapper. - * - * Buffer-object lifecycle (CREATE_DUMB / MAP_DUMB / DESTROY_DUMB) - * and PRIME (HANDLE_TO_FD / FD_TO_HANDLE) are wrapped by libgbm in - * C3/C4; users call libgbm's gbm_bo_* / gbm_bo_get_fd rather than - * the raw drmMode* / drmPrime* APIs. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int drmIoctl(int fd, unsigned long request, void *arg) -{ - int ret; - do { - ret = ioctl(fd, request, arg); - } while (ret < 0 && errno == EINTR); - return ret; -} - -drmVersionPtr drmGetVersion(int fd) -{ - drmVersionPtr v = (drmVersionPtr) calloc(1, sizeof(*v)); - if (!v) { - errno = ENOMEM; - return NULL; - } - - /* Kernel's DRM_IOCTL_VERSION fills the integer version triple - * and echoes the name/date/desc pointer fields back unchanged - * with zero length (Phase A — handle_dri_ioctl notes - * "We don't write the name/date/desc strings back in v1"). - * Pass a zeroed struct: we don't need string round-trip. */ - struct drm_version req; - memset(&req, 0, sizeof(req)); - if (drmIoctl(fd, DRM_IOCTL_VERSION, &req) < 0) { - int save = errno; - free(v); - errno = save; - return NULL; - } - - v->version_major = req.version_major; - v->version_minor = req.version_minor; - v->version_patchlevel = req.version_patchlevel; - - /* Synthesise identity strings client-side. The triple is fixed - * for the wasm-posix-kernel DRI surface; drivers detecting on - * this name should fall through to the CPU-shared tier (no - * acceleration claims). Date is the ABI-pin date — deterministic - * so the same shim compiles to the same identity across builds. */ - static const char NAME[] = "wasm-posix-kernel-dri"; - static const char DESC[] = "Kandelo wasm DRI render node"; - static const char DATE[] = "2026-05-20"; - - v->name = strdup(NAME); - v->name_len = v->name ? (int)(sizeof(NAME) - 1) : 0; - v->desc = strdup(DESC); - v->desc_len = v->desc ? (int)(sizeof(DESC) - 1) : 0; - v->date = strdup(DATE); - v->date_len = v->date ? (int)(sizeof(DATE) - 1) : 0; - - /* strdup failure leaves the matching pointer NULL and length 0. - * Upstream libdrm has the same robustness — consumers fall back - * to the integer version triple for driver detection. */ - return v; -} - -void drmFreeVersion(drmVersionPtr v) -{ - if (!v) return; - free(v->name); - free(v->date); - free(v->desc); - free(v); -} - -int drmCloseBufferHandle(int fd, uint32_t handle) -{ - struct drm_gem_close req; - memset(&req, 0, sizeof(req)); - req.handle = handle; - return drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &req); -} - -int drmSetMaster(int fd) -{ - return drmIoctl(fd, DRM_IOCTL_SET_MASTER, NULL); -} - -int drmDropMaster(int fd) -{ - return drmIoctl(fd, DRM_IOCTL_DROP_MASTER, NULL); -} - -/* KMS wrappers: pack libdrm `drmMode*` shape over the byte-identical - * `struct drm_mode_*` ioctl payloads. Get* allocates, Free* releases. */ - -drmModeResPtr drmModeGetResources(int fd) -{ - drmModeResPtr res = (drmModeResPtr) calloc(1, sizeof(*res)); - if (!res) { errno = ENOMEM; return NULL; } - - struct drm_mode_card_res req; - memset(&req, 0, sizeof(req)); - if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &req) < 0) { - int save = errno; free(res); errno = save; return NULL; - } - - uint32_t *fbs = NULL, *crtcs = NULL, *conns = NULL, *encs = NULL; - if (req.count_fbs) fbs = (uint32_t*) calloc(req.count_fbs, sizeof(uint32_t)); - if (req.count_crtcs) crtcs = (uint32_t*) calloc(req.count_crtcs, sizeof(uint32_t)); - if (req.count_connectors) conns = (uint32_t*) calloc(req.count_connectors, sizeof(uint32_t)); - if (req.count_encoders) encs = (uint32_t*) calloc(req.count_encoders, sizeof(uint32_t)); - - req.fb_id_ptr = (uintptr_t) fbs; - req.crtc_id_ptr = (uintptr_t) crtcs; - req.connector_id_ptr = (uintptr_t) conns; - req.encoder_id_ptr = (uintptr_t) encs; - - if (drmIoctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &req) < 0) { - int save = errno; - free(fbs); free(crtcs); free(conns); free(encs); free(res); - errno = save; - return NULL; - } - - res->count_fbs = (int) req.count_fbs; - res->fbs = fbs; - res->count_crtcs = (int) req.count_crtcs; - res->crtcs = crtcs; - res->count_connectors = (int) req.count_connectors; - res->connectors = conns; - res->count_encoders = (int) req.count_encoders; - res->encoders = encs; - res->min_width = req.min_width; - res->max_width = req.max_width; - res->min_height = req.min_height; - res->max_height = req.max_height; - return res; -} - -void drmModeFreeResources(drmModeResPtr ptr) -{ - if (!ptr) return; - free(ptr->fbs); - free(ptr->crtcs); - free(ptr->connectors); - free(ptr->encoders); - free(ptr); -} - -drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtc_id) -{ - drmModeCrtcPtr c = (drmModeCrtcPtr) calloc(1, sizeof(*c)); - if (!c) { errno = ENOMEM; return NULL; } - - struct drm_mode_crtc req; - memset(&req, 0, sizeof(req)); - req.crtc_id = crtc_id; - if (drmIoctl(fd, DRM_IOCTL_MODE_GETCRTC, &req) < 0) { - int save = errno; free(c); errno = save; return NULL; - } - c->crtc_id = req.crtc_id; - c->buffer_id = req.fb_id; - c->x = req.x; - c->y = req.y; - c->mode_valid = (int) req.mode_valid; - c->gamma_size = (int) req.gamma_size; - c->width = req.mode.hdisplay; - c->height = req.mode.vdisplay; - memcpy(&c->mode, &req.mode, sizeof(c->mode)); - return c; -} - -void drmModeFreeCrtc(drmModeCrtcPtr ptr) { free(ptr); } - -int drmModeSetCrtc(int fd, uint32_t crtc_id, uint32_t fb_id, - uint32_t x, uint32_t y, - uint32_t *connectors, int count, - drmModeModeInfoPtr mode) -{ - struct drm_mode_crtc req; - memset(&req, 0, sizeof(req)); - req.set_connectors_ptr = (uintptr_t) connectors; - req.count_connectors = (uint32_t) count; - req.crtc_id = crtc_id; - req.fb_id = fb_id; - req.x = x; - req.y = y; - if (mode) { - req.mode_valid = 1; - memcpy(&req.mode, mode, sizeof(req.mode)); - } - return drmIoctl(fd, DRM_IOCTL_MODE_SETCRTC, &req); -} - -drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id) -{ - drmModeEncoderPtr e = (drmModeEncoderPtr) calloc(1, sizeof(*e)); - if (!e) { errno = ENOMEM; return NULL; } - - struct drm_mode_get_encoder req; - memset(&req, 0, sizeof(req)); - req.encoder_id = encoder_id; - if (drmIoctl(fd, DRM_IOCTL_MODE_GETENCODER, &req) < 0) { - int save = errno; free(e); errno = save; return NULL; - } - e->encoder_id = req.encoder_id; - e->encoder_type = req.encoder_type; - e->crtc_id = req.crtc_id; - e->possible_crtcs = req.possible_crtcs; - e->possible_clones = req.possible_clones; - return e; -} - -void drmModeFreeEncoder(drmModeEncoderPtr ptr) { free(ptr); } - -drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id) -{ - drmModeConnectorPtr cn = (drmModeConnectorPtr) calloc(1, sizeof(*cn)); - if (!cn) { errno = ENOMEM; return NULL; } - - struct drm_mode_get_connector req; - memset(&req, 0, sizeof(req)); - req.connector_id = connector_id; - if (drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &req) < 0) { - int save = errno; free(cn); errno = save; return NULL; - } - - drmModeModeInfoPtr modes = NULL; - uint32_t *encs = NULL; - if (req.count_modes) - modes = (drmModeModeInfoPtr) calloc(req.count_modes, sizeof(*modes)); - if (req.count_encoders) - encs = (uint32_t*) calloc(req.count_encoders, sizeof(uint32_t)); - - req.modes_ptr = (uintptr_t) modes; - req.encoders_ptr = (uintptr_t) encs; - req.count_props = 0; - req.props_ptr = 0; - req.prop_values_ptr = 0; - - if (drmIoctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &req) < 0) { - int save = errno; - free(modes); free(encs); free(cn); - errno = save; - return NULL; - } - - cn->connector_id = req.connector_id; - cn->encoder_id = req.encoder_id; - cn->connector_type = req.connector_type; - cn->connector_type_id = req.connector_type_id; - cn->connection = (drmModeConnection) req.connection; - cn->mmWidth = req.mm_width; - cn->mmHeight = req.mm_height; - cn->subpixel = (drmModeSubPixel) req.subpixel; - cn->count_modes = (int) req.count_modes; - cn->modes = modes; - cn->count_encoders = (int) req.count_encoders; - cn->encoders = encs; - return cn; -} - -void drmModeFreeConnector(drmModeConnectorPtr ptr) -{ - if (!ptr) return; - free(ptr->modes); - free(ptr->encoders); - free(ptr); -} - -int drmModeAddFB2(int fd, uint32_t width, uint32_t height, - uint32_t pixel_format, - uint32_t handles[4], uint32_t pitches[4], - uint32_t offsets[4], - uint32_t *buf_id, uint32_t flags) -{ - struct drm_mode_fb_cmd2 req; - memset(&req, 0, sizeof(req)); - req.width = width; - req.height = height; - req.pixel_format = pixel_format; - req.flags = flags; - if (handles) memcpy(req.handles, handles, sizeof(req.handles)); - if (pitches) memcpy(req.pitches, pitches, sizeof(req.pitches)); - if (offsets) memcpy(req.offsets, offsets, sizeof(req.offsets)); - int rc = drmIoctl(fd, DRM_IOCTL_MODE_ADDFB2, &req); - if (rc == 0 && buf_id) *buf_id = req.fb_id; - return rc; -} - -int drmModeRmFB(int fd, uint32_t bufferId) -{ - return drmIoctl(fd, DRM_IOCTL_MODE_RMFB, &bufferId); -} - -int drmModePageFlip(int fd, uint32_t crtc_id, uint32_t fb_id, - uint32_t flags, void *user_data) -{ - struct drm_mode_crtc_page_flip req; - memset(&req, 0, sizeof(req)); - req.crtc_id = crtc_id; - req.fb_id = fb_id; - req.flags = flags; - req.user_data = (uint64_t)(uintptr_t) user_data; - return drmIoctl(fd, DRM_IOCTL_MODE_PAGE_FLIP, &req); -} - -int drmWaitVBlank(int fd, drmVBlankPtr vbl) -{ - return drmIoctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl); -} - -int drmHandleEvent(int fd, drmEventContextPtr ctx) -{ - char buffer[4096]; - ssize_t n = read(fd, buffer, sizeof(buffer)); - if (n < 0) return -errno; - - size_t off = 0; - while (off + sizeof(struct drm_event) <= (size_t) n) { - struct drm_event *e = (struct drm_event *) (buffer + off); - if (e->length < sizeof(struct drm_event) || - off + e->length > (size_t) n) break; - - if (e->type == DRM_EVENT_VBLANK || e->type == DRM_EVENT_FLIP_COMPLETE) { - if (e->length >= sizeof(struct drm_event_vblank)) { - struct drm_event_vblank *v = (struct drm_event_vblank *) e; - void *ud = (void*)(uintptr_t) v->user_data; - if (e->type == DRM_EVENT_VBLANK) { - if (ctx->vblank_handler) - ctx->vblank_handler(fd, v->sequence, - v->tv_sec, v->tv_usec, ud); - } else if (ctx->version >= 3 && ctx->page_flip_handler2) { - ctx->page_flip_handler2(fd, v->sequence, - v->tv_sec, v->tv_usec, - v->crtc_id, ud); - } else if (ctx->page_flip_handler) { - ctx->page_flip_handler(fd, v->sequence, - v->tv_sec, v->tv_usec, ud); - } - } - } - off += e->length; - } - return 0; -} diff --git a/libc/glue/libegl_stub.c b/libc/glue/libegl_stub.c index 3cf5111868..fe3e6965e3 100644 --- a/libc/glue/libegl_stub.c +++ b/libc/glue/libegl_stub.c @@ -256,3 +256,65 @@ EGLBoolean eglWaitClient(void) { } EGLBoolean eglReleaseThread(void) { return EGL_TRUE; } + +/* ----- additional thin stubs required by SDL2 ------------------- */ + +/* SDL_egl.c's LOAD_FUNC under SDL_VIDEO_STATIC_ANGLE assigns these + * symbols directly into `_this->egl_data->NAME`. All of them must + * therefore exist at link time even when their behaviour is a no-op + * (SDL2 documents NULL returns + EGL_FALSE returns as "the + * extension/feature isn't available", which is exactly the truth + * for our single-window single-buffer surface). */ + +EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval) { + (void) interval; + if (dpy != EGL_DPY_HANDLE) { + g_last_error = EGL_BAD_DISPLAY; + return EGL_FALSE; + } + /* No vsync knob — the host bridge runs at the canvas's natural + * cadence (rAF in the browser, hrtime tick on Node). Accept + * any interval and return EGL_TRUE so SDL2 doesn't surface an + * error to the app. */ + return EGL_TRUE; +} + +EGLBoolean eglWaitGL(void) { + _wpk_gl_flush(); + return EGL_TRUE; +} + +EGLBoolean eglWaitNative(EGLint engine) { + (void) engine; + return EGL_TRUE; +} + +EGLenum eglQueryAPI(void) { + return EGL_OPENGL_ES_API; +} + +EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, + const EGLint *attrib_list) { + (void) config; (void) attrib_list; + if (dpy != EGL_DPY_HANDLE) { + g_last_error = EGL_BAD_DISPLAY; + return EGL_NO_SURFACE; + } + /* SDL2 only requests pbuffers under SDL_VIDEO_OFFSCREEN, which + * we don't enable — return EGL_NO_SURFACE so any accidental + * caller fails fast. */ + g_last_error = EGL_BAD_CONFIG; + return EGL_NO_SURFACE; +} + +/* eglGetProcAddress: SDL2's LOAD_FUNC_EGLEXT routes extension lookups + * through this entry point. We don't expose any of the EGL-side + * extensions (eglCreateSyncKHR, eglQueryDevicesEXT, …), so returning + * NULL is the documented "extension not present" answer. Returning + * NULL is also fine for the GLES2 entry-point fallback path — + * libGLESv2.a exports gl* directly, so the program's calls resolve at + * link time; SDL2 itself never calls a gl* through this pointer. */ +void (*eglGetProcAddress(const char *procname))(void) { + (void) procname; + return NULL; +} diff --git a/libc/glue/libgbm_stub.c b/libc/glue/libgbm_stub.c index 4cc8d1291a..1de2746cd4 100644 --- a/libc/glue/libgbm_stub.c +++ b/libc/glue/libgbm_stub.c @@ -9,24 +9,34 @@ * * Surface implemented (v1): * - gbm_create_device / gbm_device_destroy + * - gbm_device_is_format_supported (32-bpp fourccs) * - gbm_bo_create (linear, 32-bpp formats only) * - gbm_bo_destroy * - gbm_bo_get_fd (PRIME export → handle-to-fd) * - gbm_bo_import (GBM_BO_IMPORT_FD: fd-to-handle) * - gbm_bo_map / gbm_bo_unmap (single-plane mmap) + * - gbm_bo_write (map + memcpy) + * - gbm_bo_set/get_user_data (opaque pointer with destroy cb) * - accessors: get_width / get_height / get_stride / get_format * / get_bpp / get_modifier / get_handle / get_device * + * Surface scanout (added for SDL2 KMSDRM): + * - gbm_surface_create / gbm_surface_create_with_modifiers / + * gbm_surface_create_with_modifiers2 + * - gbm_surface_lock_front_buffer / gbm_surface_release_buffer + * - gbm_surface_has_free_buffers / gbm_surface_destroy + * + * The surface holds a fixed-size two-BO ring (double-buffer); + * lock_front_buffer hands out an unused BO and release_buffer + * returns it. Modifiers other than DRM_FORMAT_MOD_LINEAR are + * ignored — every BO is linear, CPU-shared, single-plane. + * * Declared in but intentionally NOT implemented (consumers * calling these get link-time undefined-symbol errors): * gbm_bo_create_with_modifiers*, gbm_bo_get_*_for_plane, * gbm_bo_get_plane_count, gbm_bo_get_offset, gbm_bo_get_handle_for_plane, - * gbm_bo_write, gbm_bo_set/get_user_data, gbm_surface_*, * gbm_device_get_fd, gbm_device_get_backend_name, - * gbm_device_is_format_supported, * gbm_device_get_format_modifier_plane_count, gbm_format_get_name. - * Phase C v1 is deliberately minimal — every BO is linear, - * CPU-shared, single-plane, with the DRM_FORMAT_MOD_LINEAR modifier. */ #include @@ -56,6 +66,21 @@ struct gbm_bo { uint64_t modifier; /* DRM_FORMAT_MOD_LINEAR for v1 */ void *map_addr; /* lazy: set by gbm_bo_map */ size_t map_len; + void *user_data; + void (*user_data_destroy)(struct gbm_bo *, void *); + struct gbm_surface *surface; /* non-NULL when owned by a surface ring */ +}; + +#define WPK_GBM_SURFACE_BO_COUNT 2 + +struct gbm_surface { + struct gbm_device *dev; + uint32_t width, height; + uint32_t format; + uint32_t flags; + struct gbm_bo *bos[WPK_GBM_SURFACE_BO_COUNT]; + int in_use[WPK_GBM_SURFACE_BO_COUNT]; + int next; /* round-robin cursor for lock_front_buffer */ }; static uint32_t format_bpp(uint32_t fourcc) { @@ -248,6 +273,11 @@ void gbm_bo_unmap(struct gbm_bo *bo, void *map_data) { void gbm_bo_destroy(struct gbm_bo *bo) { if (!bo) return; + if (bo->user_data_destroy) { + bo->user_data_destroy(bo, bo->user_data); + bo->user_data = NULL; + bo->user_data_destroy = NULL; + } if (bo->map_addr) { munmap(bo->map_addr, bo->map_len); bo->map_addr = NULL; @@ -257,6 +287,49 @@ void gbm_bo_destroy(struct gbm_bo *bo) { free(bo); } +int gbm_bo_write(struct gbm_bo *bo, const void *buf, size_t count) { + if (!bo || !buf) { + errno = EINVAL; + return -1; + } + if ((uint64_t) count > bo->size) { + errno = EINVAL; + return -1; + } + /* Reuse the lazy-mapped pointer when present; otherwise mmap + * the dumb buffer and copy. gbm_bo_map's flags+rect args are + * ignored in v1, so passing zeros + NULLs is fine. */ + void *addr = bo->map_addr; + int mapped_here = 0; + if (!addr) { + addr = gbm_bo_map(bo, 0, 0, bo->width, bo->height, 0, NULL, NULL); + if (!addr) { + return -1; + } + mapped_here = 1; + } + memcpy(addr, buf, count); + if (mapped_here) { + /* Hold the mapping — repeated writes are common from + * cursor / icon uploads. gbm_bo_destroy will unmap. */ + } + return 0; +} + +void gbm_bo_set_user_data(struct gbm_bo *bo, void *data, + void (*destroy_user_data)(struct gbm_bo *, void *)) { + if (!bo) return; + if (bo->user_data_destroy && bo->user_data) { + bo->user_data_destroy(bo, bo->user_data); + } + bo->user_data = data; + bo->user_data_destroy = destroy_user_data; +} + +void *gbm_bo_get_user_data(struct gbm_bo *bo) { + return bo ? bo->user_data : NULL; +} + uint32_t gbm_bo_get_width(struct gbm_bo *bo) { return bo->width; } uint32_t gbm_bo_get_height(struct gbm_bo *bo) { return bo->height; } uint32_t gbm_bo_get_stride(struct gbm_bo *bo) { return bo->stride; } @@ -273,3 +346,179 @@ union gbm_bo_handle gbm_bo_get_handle(struct gbm_bo *bo) { h.u32 = bo->handle; return h; } + +/* Every BO the kernel's CREATE_DUMB path produces is a single linear + * plane, so the per-plane accessors collapse onto the whole-BO ones. + * SDL2's KMSDRM backend walks planes to build its drmModeAddFB2 call. */ + +int gbm_bo_get_plane_count(struct gbm_bo *bo) { + (void) bo; + return 1; +} + +union gbm_bo_handle gbm_bo_get_handle_for_plane(struct gbm_bo *bo, int plane) { + union gbm_bo_handle h; + h.u32 = plane == 0 ? bo->handle : 0; + return h; +} + +uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo *bo, int plane) { + return plane == 0 ? bo->stride : 0; +} + +uint32_t gbm_bo_get_offset(struct gbm_bo *bo, int plane) { + (void) bo; (void) plane; + return 0; +} + +int gbm_device_is_format_supported(struct gbm_device *gbm, + uint32_t format, uint32_t flags) { + (void) gbm; (void) flags; + /* Match format_bpp's set — the only formats the kernel's + * CREATE_DUMB path accepts. SDL2 KMSDRM probes ARGB8888 with + * GBM_BO_USE_SCANOUT|GBM_BO_USE_RENDERING; cursor probes use + * ARGB8888 with USE_CURSOR|USE_WRITE. Both fall under the same + * underlying linear dumb buffer in v1. */ + return format_bpp(format) ? 1 : 0; +} + +/* ----- gbm_surface_* (double-buffered scanout ring) --------------- + * + * SDL2's KMSDRM backend wants a gbm_surface it can hand to + * eglCreateWindowSurface and then drive page-flipping via + * gbm_surface_lock_front_buffer / release_buffer. The mesa + * implementation backs each surface with a small ring of BOs that + * EGL renders into and KMS scans out from. + * + * Our v1 mirrors that with a fixed two-BO ring, which is enough for + * a single-window double-buffered demo. The BOs are allocated + * eagerly so consumers always observe the same width/height/stride + * across lock cycles. Repeated lock without an intervening release + * (or a release without a prior lock) returns NULL — that matches + * mesa's behaviour when the ring is exhausted. + */ + +static struct gbm_bo *surface_alloc_bo(struct gbm_surface *s) { + /* Map GBM_BO_USE_* flags onto the kernel-side DUMB path; v1 + * accepts any flag combination because all BOs are linear + * CPU-shared dumbs. */ + struct gbm_bo *bo = gbm_bo_create(s->dev, s->width, s->height, + s->format, s->flags); + if (bo) { + bo->surface = s; + } + return bo; +} + +struct gbm_surface *gbm_surface_create(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, uint32_t flags) { + if (!gbm || !width || !height || !format_bpp(format)) { + errno = EINVAL; + return NULL; + } + struct gbm_surface *s = (struct gbm_surface *) calloc(1, sizeof(*s)); + if (!s) { + errno = ENOMEM; + return NULL; + } + s->dev = gbm; + s->width = width; + s->height = height; + s->format = format; + s->flags = flags; + for (int i = 0; i < WPK_GBM_SURFACE_BO_COUNT; i++) { + s->bos[i] = surface_alloc_bo(s); + if (!s->bos[i]) { + int save = errno; + for (int j = 0; j < i; j++) { + s->bos[j]->surface = NULL; + gbm_bo_destroy(s->bos[j]); + } + free(s); + errno = save; + return NULL; + } + } + return s; +} + +struct gbm_surface * +gbm_surface_create_with_modifiers(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count) { + (void) modifiers; (void) count; + /* v1 only emits DRM_FORMAT_MOD_LINEAR; ignoring caller-supplied + * modifier set matches the documented mesa fallback when no + * requested modifier is available. */ + return gbm_surface_create(gbm, width, height, format, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); +} + +struct gbm_surface * +gbm_surface_create_with_modifiers2(struct gbm_device *gbm, + uint32_t width, uint32_t height, + uint32_t format, + const uint64_t *modifiers, + const unsigned int count, + uint32_t flags) { + (void) modifiers; (void) count; + return gbm_surface_create(gbm, width, height, format, flags); +} + +struct gbm_bo *gbm_surface_lock_front_buffer(struct gbm_surface *surface) { + if (!surface) { + errno = EINVAL; + return NULL; + } + /* Hand out the next free BO in round-robin order. Consumers + * are expected to release the previous frame's BO before + * locking the next; if both BOs are in use we return NULL + + * EBUSY (mesa returns NULL in the equivalent ring-exhausted + * case). */ + for (int i = 0; i < WPK_GBM_SURFACE_BO_COUNT; i++) { + int idx = (surface->next + i) % WPK_GBM_SURFACE_BO_COUNT; + if (!surface->in_use[idx]) { + surface->in_use[idx] = 1; + surface->next = (idx + 1) % WPK_GBM_SURFACE_BO_COUNT; + return surface->bos[idx]; + } + } + errno = EBUSY; + return NULL; +} + +void gbm_surface_release_buffer(struct gbm_surface *surface, struct gbm_bo *bo) { + if (!surface || !bo) return; + for (int i = 0; i < WPK_GBM_SURFACE_BO_COUNT; i++) { + if (surface->bos[i] == bo) { + surface->in_use[i] = 0; + return; + } + } + /* Releasing a BO that doesn't belong to this surface is a + * caller bug; mesa silently ignores. */ +} + +int gbm_surface_has_free_buffers(struct gbm_surface *surface) { + if (!surface) return 0; + int free_count = 0; + for (int i = 0; i < WPK_GBM_SURFACE_BO_COUNT; i++) { + if (!surface->in_use[i]) free_count++; + } + return free_count; +} + +void gbm_surface_destroy(struct gbm_surface *surface) { + if (!surface) return; + for (int i = 0; i < WPK_GBM_SURFACE_BO_COUNT; i++) { + if (surface->bos[i]) { + surface->bos[i]->surface = NULL; + gbm_bo_destroy(surface->bos[i]); + surface->bos[i] = NULL; + } + } + free(surface); +} diff --git a/libc/glue/libglesv2_stub.c b/libc/glue/libglesv2_stub.c index aa604845a5..218cba042a 100644 --- a/libc/glue/libglesv2_stub.c +++ b/libc/glue/libglesv2_stub.c @@ -345,15 +345,47 @@ void glActiveTexture(GLenum unit) { EMIT_END() } -/* Pavel's pipeline only allocates render-target textures (data == NULL): - * the fragment shaders write each texture's contents. The upload path - * (data != NULL) needs per-(format,type) byte-size logic; extend when a - * demo actually needs to upload pixel data from C. */ +/* Bytes-per-pixel for the GL (format,type) pairs we know how to + * marshal. Returns 0 for unknown combos so glTexImage2D / glTexSubImage2D + * drops the upload rather than emit a garbled record. Extend when a + * demo needs a new combo. */ +static uint32_t bytes_per_pixel(GLenum format, GLenum type) { + if (type == GL_UNSIGNED_BYTE) { + switch (format) { + case GL_ALPHA: return 1; + case GL_LUMINANCE: return 1; + case GL_LUMINANCE_ALPHA: return 2; + case GL_RGB: return 3; + case GL_RGBA: return 4; + default: break; + } + } else if (type == GL_UNSIGNED_SHORT_5_6_5 + || type == GL_UNSIGNED_SHORT_4_4_4_4 + || type == GL_UNSIGNED_SHORT_5_5_5_1) { + return 2; + } + return 0; +} + void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *data) { - if (data != NULL) return; - EMIT_BEGIN(OP_TEX_IMAGE_2D, 36) + uint32_t dlen = 0; + if (data != NULL && width > 0 && height > 0) { + uint32_t bpp = bytes_per_pixel(format, type); + if (bpp > 0) { + dlen = (uint32_t) width * (uint32_t) height * bpp; + } + } + /* The TLV payload-length field is u16, so the largest single-call + * upload that fits is 0xFFFF - 36 (header fields) ≈ 65499 bytes. + * The caller is responsible for sizing textures to fit; this guard + * just prevents the encoder from writing a truncated record. */ + if (dlen > 0xFFFFu - 36u) { + dlen = 0; + data = NULL; + } + EMIT_BEGIN(OP_TEX_IMAGE_2D, 36u + dlen) w_u32(&_c, (uint32_t)target); w_i32(&_c, level); w_i32(&_c, internalFormat); @@ -362,7 +394,11 @@ void glTexImage2D(GLenum target, GLint level, GLint internalFormat, w_i32(&_c, border); w_u32(&_c, (uint32_t)format); w_u32(&_c, (uint32_t)type); - w_u32(&_c, 0u); /* dataLen */ + w_u32(&_c, dlen); + if (dlen > 0 && data != NULL) { + memcpy(_c, data, dlen); + _c += dlen; + } EMIT_END() } @@ -374,6 +410,21 @@ void glTexParameteri(GLenum target, GLenum pname, GLint param) { EMIT_END() } +void glPixelStorei(GLenum pname, GLint param) { + EMIT_BEGIN(OP_PIXEL_STOREI, 8) + w_u32(&_c, (uint32_t)pname); + w_i32(&_c, param); + EMIT_END() +} + +void glDeleteBuffers(GLsizei n, const GLuint *names) { + if (n <= 0 || !names) return; + EMIT_BEGIN(OP_DELETE_BUFFERS, 4u + (uint32_t)n * 4u) + w_u32(&_c, (uint32_t)n); + for (GLsizei i = 0; i < n; i++) w_u32(&_c, names[i]); + EMIT_END() +} + /* ----- uniforms ----------------------------------------------------- */ void glUniform1i(GLint location, GLint v) { diff --git a/libc/musl-overlay/include/drm/SOURCE.txt b/libc/musl-overlay/include/drm/SOURCE.txt deleted file mode 100644 index 24d0441f07..0000000000 --- a/libc/musl-overlay/include/drm/SOURCE.txt +++ /dev/null @@ -1,42 +0,0 @@ -Vendored Linux UAPI DRM headers -================================ - -Source: torvalds/linux @ v6.12 (LTS) -Tag SHA: 06090c9b622a7e1f797e775db4c035e0d779b76e (annotated) -Commit SHA: adc218676eef25575469234709c2d87185ca223a -Tagger: Linus Torvalds -Tag date: 2024-11-17T22:15:08Z -Vendored: 2026-05-20 - -Files are byte-for-byte upstream from `include/uapi/drm/` at the -above tag. Any wasm32-specific size or alignment differences are -intentionally NOT patched here — the kernel-side ABI shape lives -in `crates/shared/src/dri.rs` (see Task A1) and is asserted by the -ioctl-number tests there. The headers exist purely so user-space -code in this sysroot can `#include ` against the same -struct layouts and ioctl numbers the kernel expects. - -Files ------ -drm.h sha256 3ab6ac01bf91067aed96b70d7fa7847a86e7f726d74278151f085143688659cc -drm_mode.h sha256 3a5bad960e64a6374e046450dcb637145f4127950659a6429561d9d8faf4d14b -drm_fourcc.h sha256 dd775484fc3d603d20cde445f79b858906531b4a26104b4726a0c8852f7f4415 - -Fetch recipe (deterministic — re-run to refresh against the same tag) ---------------------------------------------------------------------- -TAG=v6.12 -BASE=https://raw.githubusercontent.com/torvalds/linux/${TAG}/include/uapi/drm -for h in drm.h drm_mode.h drm_fourcc.h; do - curl -sSfL -o libc/musl-overlay/include/drm/${h} "${BASE}/${h}" -done -shasum -a 256 libc/musl-overlay/include/drm/*.h - -If sha256s no longer match the values above, the upstream tag has -been re-pushed (rare for release tags) or the wrong tag is pinned. -Investigate before silently updating this file. - -Installation ------------- -`scripts/install-overlay-headers.sh ` copies these to -`/include/drm/`, making them reachable as `` -etc. from any user program compiled with the wasm32 sysroot. diff --git a/libc/musl-overlay/include/drm/drm.h b/libc/musl-overlay/include/drm/drm.h deleted file mode 100644 index 16122819ed..0000000000 --- a/libc/musl-overlay/include/drm/drm.h +++ /dev/null @@ -1,1416 +0,0 @@ -/* - * Header for the Direct Rendering Manager - * - * Author: Rickard E. (Rik) Faith - * - * Acknowledgments: - * Dec 1999, Richard Henderson , move to generic cmpxchg. - */ - -/* - * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. - * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef _DRM_H_ -#define _DRM_H_ - -#if defined(__KERNEL__) - -#include -#include -typedef unsigned int drm_handle_t; - -#elif defined(__linux__) - -#include -#include -typedef unsigned int drm_handle_t; - -#else /* One of the BSDs */ - -#include -#include -#include -typedef int8_t __s8; -typedef uint8_t __u8; -typedef int16_t __s16; -typedef uint16_t __u16; -typedef int32_t __s32; -typedef uint32_t __u32; -typedef int64_t __s64; -typedef uint64_t __u64; -typedef size_t __kernel_size_t; -typedef unsigned long drm_handle_t; - -#endif - -#if defined(__cplusplus) -extern "C" { -#endif - -#define DRM_NAME "drm" /**< Name in kernel, /dev, and /proc */ -#define DRM_MIN_ORDER 5 /**< At least 2^5 bytes = 32 bytes */ -#define DRM_MAX_ORDER 22 /**< Up to 2^22 bytes = 4MB */ -#define DRM_RAM_PERCENT 10 /**< How much system ram can we lock? */ - -#define _DRM_LOCK_HELD 0x80000000U /**< Hardware lock is held */ -#define _DRM_LOCK_CONT 0x40000000U /**< Hardware lock is contended */ -#define _DRM_LOCK_IS_HELD(lock) ((lock) & _DRM_LOCK_HELD) -#define _DRM_LOCK_IS_CONT(lock) ((lock) & _DRM_LOCK_CONT) -#define _DRM_LOCKING_CONTEXT(lock) ((lock) & ~(_DRM_LOCK_HELD|_DRM_LOCK_CONT)) - -typedef unsigned int drm_context_t; -typedef unsigned int drm_drawable_t; -typedef unsigned int drm_magic_t; - -/* - * Cliprect. - * - * \warning: If you change this structure, make sure you change - * XF86DRIClipRectRec in the server as well - * - * \note KW: Actually it's illegal to change either for - * backwards-compatibility reasons. - */ -struct drm_clip_rect { - unsigned short x1; - unsigned short y1; - unsigned short x2; - unsigned short y2; -}; - -/* - * Drawable information. - */ -struct drm_drawable_info { - unsigned int num_rects; - struct drm_clip_rect *rects; -}; - -/* - * Texture region, - */ -struct drm_tex_region { - unsigned char next; - unsigned char prev; - unsigned char in_use; - unsigned char padding; - unsigned int age; -}; - -/* - * Hardware lock. - * - * The lock structure is a simple cache-line aligned integer. To avoid - * processor bus contention on a multiprocessor system, there should not be any - * other data stored in the same cache line. - */ -struct drm_hw_lock { - __volatile__ unsigned int lock; /**< lock variable */ - char padding[60]; /**< Pad to cache line */ -}; - -/* - * DRM_IOCTL_VERSION ioctl argument type. - * - * \sa drmGetVersion(). - */ -struct drm_version { - int version_major; /**< Major version */ - int version_minor; /**< Minor version */ - int version_patchlevel; /**< Patch level */ - __kernel_size_t name_len; /**< Length of name buffer */ - char __user *name; /**< Name of driver */ - __kernel_size_t date_len; /**< Length of date buffer */ - char __user *date; /**< User-space buffer to hold date */ - __kernel_size_t desc_len; /**< Length of desc buffer */ - char __user *desc; /**< User-space buffer to hold desc */ -}; - -/* - * DRM_IOCTL_GET_UNIQUE ioctl argument type. - * - * \sa drmGetBusid() and drmSetBusId(). - */ -struct drm_unique { - __kernel_size_t unique_len; /**< Length of unique */ - char __user *unique; /**< Unique name for driver instantiation */ -}; - -struct drm_list { - int count; /**< Length of user-space structures */ - struct drm_version __user *version; -}; - -struct drm_block { - int unused; -}; - -/* - * DRM_IOCTL_CONTROL ioctl argument type. - * - * \sa drmCtlInstHandler() and drmCtlUninstHandler(). - */ -struct drm_control { - enum { - DRM_ADD_COMMAND, - DRM_RM_COMMAND, - DRM_INST_HANDLER, - DRM_UNINST_HANDLER - } func; - int irq; -}; - -/* - * Type of memory to map. - */ -enum drm_map_type { - _DRM_FRAME_BUFFER = 0, /**< WC (no caching), no core dump */ - _DRM_REGISTERS = 1, /**< no caching, no core dump */ - _DRM_SHM = 2, /**< shared, cached */ - _DRM_AGP = 3, /**< AGP/GART */ - _DRM_SCATTER_GATHER = 4, /**< Scatter/gather memory for PCI DMA */ - _DRM_CONSISTENT = 5 /**< Consistent memory for PCI DMA */ -}; - -/* - * Memory mapping flags. - */ -enum drm_map_flags { - _DRM_RESTRICTED = 0x01, /**< Cannot be mapped to user-virtual */ - _DRM_READ_ONLY = 0x02, - _DRM_LOCKED = 0x04, /**< shared, cached, locked */ - _DRM_KERNEL = 0x08, /**< kernel requires access */ - _DRM_WRITE_COMBINING = 0x10, /**< use write-combining if available */ - _DRM_CONTAINS_LOCK = 0x20, /**< SHM page that contains lock */ - _DRM_REMOVABLE = 0x40, /**< Removable mapping */ - _DRM_DRIVER = 0x80 /**< Managed by driver */ -}; - -struct drm_ctx_priv_map { - unsigned int ctx_id; /**< Context requesting private mapping */ - void *handle; /**< Handle of map */ -}; - -/* - * DRM_IOCTL_GET_MAP, DRM_IOCTL_ADD_MAP and DRM_IOCTL_RM_MAP ioctls - * argument type. - * - * \sa drmAddMap(). - */ -struct drm_map { - unsigned long offset; /**< Requested physical address (0 for SAREA)*/ - unsigned long size; /**< Requested physical size (bytes) */ - enum drm_map_type type; /**< Type of memory to map */ - enum drm_map_flags flags; /**< Flags */ - void *handle; /**< User-space: "Handle" to pass to mmap() */ - /**< Kernel-space: kernel-virtual address */ - int mtrr; /**< MTRR slot used */ - /* Private data */ -}; - -/* - * DRM_IOCTL_GET_CLIENT ioctl argument type. - */ -struct drm_client { - int idx; /**< Which client desired? */ - int auth; /**< Is client authenticated? */ - unsigned long pid; /**< Process ID */ - unsigned long uid; /**< User ID */ - unsigned long magic; /**< Magic */ - unsigned long iocs; /**< Ioctl count */ -}; - -enum drm_stat_type { - _DRM_STAT_LOCK, - _DRM_STAT_OPENS, - _DRM_STAT_CLOSES, - _DRM_STAT_IOCTLS, - _DRM_STAT_LOCKS, - _DRM_STAT_UNLOCKS, - _DRM_STAT_VALUE, /**< Generic value */ - _DRM_STAT_BYTE, /**< Generic byte counter (1024bytes/K) */ - _DRM_STAT_COUNT, /**< Generic non-byte counter (1000/k) */ - - _DRM_STAT_IRQ, /**< IRQ */ - _DRM_STAT_PRIMARY, /**< Primary DMA bytes */ - _DRM_STAT_SECONDARY, /**< Secondary DMA bytes */ - _DRM_STAT_DMA, /**< DMA */ - _DRM_STAT_SPECIAL, /**< Special DMA (e.g., priority or polled) */ - _DRM_STAT_MISSED /**< Missed DMA opportunity */ - /* Add to the *END* of the list */ -}; - -/* - * DRM_IOCTL_GET_STATS ioctl argument type. - */ -struct drm_stats { - unsigned long count; - struct { - unsigned long value; - enum drm_stat_type type; - } data[15]; -}; - -/* - * Hardware locking flags. - */ -enum drm_lock_flags { - _DRM_LOCK_READY = 0x01, /**< Wait until hardware is ready for DMA */ - _DRM_LOCK_QUIESCENT = 0x02, /**< Wait until hardware quiescent */ - _DRM_LOCK_FLUSH = 0x04, /**< Flush this context's DMA queue first */ - _DRM_LOCK_FLUSH_ALL = 0x08, /**< Flush all DMA queues first */ - /* These *HALT* flags aren't supported yet - -- they will be used to support the - full-screen DGA-like mode. */ - _DRM_HALT_ALL_QUEUES = 0x10, /**< Halt all current and future queues */ - _DRM_HALT_CUR_QUEUES = 0x20 /**< Halt all current queues */ -}; - -/* - * DRM_IOCTL_LOCK, DRM_IOCTL_UNLOCK and DRM_IOCTL_FINISH ioctl argument type. - * - * \sa drmGetLock() and drmUnlock(). - */ -struct drm_lock { - int context; - enum drm_lock_flags flags; -}; - -/* - * DMA flags - * - * \warning - * These values \e must match xf86drm.h. - * - * \sa drm_dma. - */ -enum drm_dma_flags { - /* Flags for DMA buffer dispatch */ - _DRM_DMA_BLOCK = 0x01, /**< - * Block until buffer dispatched. - * - * \note The buffer may not yet have - * been processed by the hardware -- - * getting a hardware lock with the - * hardware quiescent will ensure - * that the buffer has been - * processed. - */ - _DRM_DMA_WHILE_LOCKED = 0x02, /**< Dispatch while lock held */ - _DRM_DMA_PRIORITY = 0x04, /**< High priority dispatch */ - - /* Flags for DMA buffer request */ - _DRM_DMA_WAIT = 0x10, /**< Wait for free buffers */ - _DRM_DMA_SMALLER_OK = 0x20, /**< Smaller-than-requested buffers OK */ - _DRM_DMA_LARGER_OK = 0x40 /**< Larger-than-requested buffers OK */ -}; - -/* - * DRM_IOCTL_ADD_BUFS and DRM_IOCTL_MARK_BUFS ioctl argument type. - * - * \sa drmAddBufs(). - */ -struct drm_buf_desc { - int count; /**< Number of buffers of this size */ - int size; /**< Size in bytes */ - int low_mark; /**< Low water mark */ - int high_mark; /**< High water mark */ - enum { - _DRM_PAGE_ALIGN = 0x01, /**< Align on page boundaries for DMA */ - _DRM_AGP_BUFFER = 0x02, /**< Buffer is in AGP space */ - _DRM_SG_BUFFER = 0x04, /**< Scatter/gather memory buffer */ - _DRM_FB_BUFFER = 0x08, /**< Buffer is in frame buffer */ - _DRM_PCI_BUFFER_RO = 0x10 /**< Map PCI DMA buffer read-only */ - } flags; - unsigned long agp_start; /**< - * Start address of where the AGP buffers are - * in the AGP aperture - */ -}; - -/* - * DRM_IOCTL_INFO_BUFS ioctl argument type. - */ -struct drm_buf_info { - int count; /**< Entries in list */ - struct drm_buf_desc __user *list; -}; - -/* - * DRM_IOCTL_FREE_BUFS ioctl argument type. - */ -struct drm_buf_free { - int count; - int __user *list; -}; - -/* - * Buffer information - * - * \sa drm_buf_map. - */ -struct drm_buf_pub { - int idx; /**< Index into the master buffer list */ - int total; /**< Buffer size */ - int used; /**< Amount of buffer in use (for DMA) */ - void __user *address; /**< Address of buffer */ -}; - -/* - * DRM_IOCTL_MAP_BUFS ioctl argument type. - */ -struct drm_buf_map { - int count; /**< Length of the buffer list */ -#ifdef __cplusplus - void __user *virt; -#else - void __user *virtual; /**< Mmap'd area in user-virtual */ -#endif - struct drm_buf_pub __user *list; /**< Buffer information */ -}; - -/* - * DRM_IOCTL_DMA ioctl argument type. - * - * Indices here refer to the offset into the buffer list in drm_buf_get. - * - * \sa drmDMA(). - */ -struct drm_dma { - int context; /**< Context handle */ - int send_count; /**< Number of buffers to send */ - int __user *send_indices; /**< List of handles to buffers */ - int __user *send_sizes; /**< Lengths of data to send */ - enum drm_dma_flags flags; /**< Flags */ - int request_count; /**< Number of buffers requested */ - int request_size; /**< Desired size for buffers */ - int __user *request_indices; /**< Buffer information */ - int __user *request_sizes; - int granted_count; /**< Number of buffers granted */ -}; - -enum drm_ctx_flags { - _DRM_CONTEXT_PRESERVED = 0x01, - _DRM_CONTEXT_2DONLY = 0x02 -}; - -/* - * DRM_IOCTL_ADD_CTX ioctl argument type. - * - * \sa drmCreateContext() and drmDestroyContext(). - */ -struct drm_ctx { - drm_context_t handle; - enum drm_ctx_flags flags; -}; - -/* - * DRM_IOCTL_RES_CTX ioctl argument type. - */ -struct drm_ctx_res { - int count; - struct drm_ctx __user *contexts; -}; - -/* - * DRM_IOCTL_ADD_DRAW and DRM_IOCTL_RM_DRAW ioctl argument type. - */ -struct drm_draw { - drm_drawable_t handle; -}; - -/* - * DRM_IOCTL_UPDATE_DRAW ioctl argument type. - */ -typedef enum { - DRM_DRAWABLE_CLIPRECTS -} drm_drawable_info_type_t; - -struct drm_update_draw { - drm_drawable_t handle; - unsigned int type; - unsigned int num; - unsigned long long data; -}; - -/* - * DRM_IOCTL_GET_MAGIC and DRM_IOCTL_AUTH_MAGIC ioctl argument type. - */ -struct drm_auth { - drm_magic_t magic; -}; - -/* - * DRM_IOCTL_IRQ_BUSID ioctl argument type. - * - * \sa drmGetInterruptFromBusID(). - */ -struct drm_irq_busid { - int irq; /**< IRQ number */ - int busnum; /**< bus number */ - int devnum; /**< device number */ - int funcnum; /**< function number */ -}; - -enum drm_vblank_seq_type { - _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */ - _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */ - /* bits 1-6 are reserved for high crtcs */ - _DRM_VBLANK_HIGH_CRTC_MASK = 0x0000003e, - _DRM_VBLANK_EVENT = 0x4000000, /**< Send event instead of blocking */ - _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */ - _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */ - _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */ - _DRM_VBLANK_SIGNAL = 0x40000000 /**< Send signal instead of blocking, unsupported */ -}; -#define _DRM_VBLANK_HIGH_CRTC_SHIFT 1 - -#define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE) -#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_EVENT | _DRM_VBLANK_SIGNAL | \ - _DRM_VBLANK_SECONDARY | _DRM_VBLANK_NEXTONMISS) - -struct drm_wait_vblank_request { - enum drm_vblank_seq_type type; - unsigned int sequence; - unsigned long signal; -}; - -struct drm_wait_vblank_reply { - enum drm_vblank_seq_type type; - unsigned int sequence; - long tval_sec; - long tval_usec; -}; - -/* - * DRM_IOCTL_WAIT_VBLANK ioctl argument type. - * - * \sa drmWaitVBlank(). - */ -union drm_wait_vblank { - struct drm_wait_vblank_request request; - struct drm_wait_vblank_reply reply; -}; - -#define _DRM_PRE_MODESET 1 -#define _DRM_POST_MODESET 2 - -/* - * DRM_IOCTL_MODESET_CTL ioctl argument type - * - * \sa drmModesetCtl(). - */ -struct drm_modeset_ctl { - __u32 crtc; - __u32 cmd; -}; - -/* - * DRM_IOCTL_AGP_ENABLE ioctl argument type. - * - * \sa drmAgpEnable(). - */ -struct drm_agp_mode { - unsigned long mode; /**< AGP mode */ -}; - -/* - * DRM_IOCTL_AGP_ALLOC and DRM_IOCTL_AGP_FREE ioctls argument type. - * - * \sa drmAgpAlloc() and drmAgpFree(). - */ -struct drm_agp_buffer { - unsigned long size; /**< In bytes -- will round to page boundary */ - unsigned long handle; /**< Used for binding / unbinding */ - unsigned long type; /**< Type of memory to allocate */ - unsigned long physical; /**< Physical used by i810 */ -}; - -/* - * DRM_IOCTL_AGP_BIND and DRM_IOCTL_AGP_UNBIND ioctls argument type. - * - * \sa drmAgpBind() and drmAgpUnbind(). - */ -struct drm_agp_binding { - unsigned long handle; /**< From drm_agp_buffer */ - unsigned long offset; /**< In bytes -- will round to page boundary */ -}; - -/* - * DRM_IOCTL_AGP_INFO ioctl argument type. - * - * \sa drmAgpVersionMajor(), drmAgpVersionMinor(), drmAgpGetMode(), - * drmAgpBase(), drmAgpSize(), drmAgpMemoryUsed(), drmAgpMemoryAvail(), - * drmAgpVendorId() and drmAgpDeviceId(). - */ -struct drm_agp_info { - int agp_version_major; - int agp_version_minor; - unsigned long mode; - unsigned long aperture_base; /* physical address */ - unsigned long aperture_size; /* bytes */ - unsigned long memory_allowed; /* bytes */ - unsigned long memory_used; - - /* PCI information */ - unsigned short id_vendor; - unsigned short id_device; -}; - -/* - * DRM_IOCTL_SG_ALLOC ioctl argument type. - */ -struct drm_scatter_gather { - unsigned long size; /**< In bytes -- will round to page boundary */ - unsigned long handle; /**< Used for mapping / unmapping */ -}; - -/* - * DRM_IOCTL_SET_VERSION ioctl argument type. - */ -struct drm_set_version { - int drm_di_major; - int drm_di_minor; - int drm_dd_major; - int drm_dd_minor; -}; - -/* DRM_IOCTL_GEM_CLOSE ioctl argument type */ -struct drm_gem_close { - /** Handle of the object to be closed. */ - __u32 handle; - __u32 pad; -}; - -/* DRM_IOCTL_GEM_FLINK ioctl argument type */ -struct drm_gem_flink { - /** Handle for the object being named */ - __u32 handle; - - /** Returned global name */ - __u32 name; -}; - -/* DRM_IOCTL_GEM_OPEN ioctl argument type */ -struct drm_gem_open { - /** Name of object being opened */ - __u32 name; - - /** Returned handle for the object */ - __u32 handle; - - /** Returned size of the object */ - __u64 size; -}; - -/** - * DRM_CAP_DUMB_BUFFER - * - * If set to 1, the driver supports creating dumb buffers via the - * &DRM_IOCTL_MODE_CREATE_DUMB ioctl. - */ -#define DRM_CAP_DUMB_BUFFER 0x1 -/** - * DRM_CAP_VBLANK_HIGH_CRTC - * - * If set to 1, the kernel supports specifying a :ref:`CRTC index` - * in the high bits of &drm_wait_vblank_request.type. - * - * Starting kernel version 2.6.39, this capability is always set to 1. - */ -#define DRM_CAP_VBLANK_HIGH_CRTC 0x2 -/** - * DRM_CAP_DUMB_PREFERRED_DEPTH - * - * The preferred bit depth for dumb buffers. - * - * The bit depth is the number of bits used to indicate the color of a single - * pixel excluding any padding. This is different from the number of bits per - * pixel. For instance, XRGB8888 has a bit depth of 24 but has 32 bits per - * pixel. - * - * Note that this preference only applies to dumb buffers, it's irrelevant for - * other types of buffers. - */ -#define DRM_CAP_DUMB_PREFERRED_DEPTH 0x3 -/** - * DRM_CAP_DUMB_PREFER_SHADOW - * - * If set to 1, the driver prefers userspace to render to a shadow buffer - * instead of directly rendering to a dumb buffer. For best speed, userspace - * should do streaming ordered memory copies into the dumb buffer and never - * read from it. - * - * Note that this preference only applies to dumb buffers, it's irrelevant for - * other types of buffers. - */ -#define DRM_CAP_DUMB_PREFER_SHADOW 0x4 -/** - * DRM_CAP_PRIME - * - * Bitfield of supported PRIME sharing capabilities. See &DRM_PRIME_CAP_IMPORT - * and &DRM_PRIME_CAP_EXPORT. - * - * Starting from kernel version 6.6, both &DRM_PRIME_CAP_IMPORT and - * &DRM_PRIME_CAP_EXPORT are always advertised. - * - * PRIME buffers are exposed as dma-buf file descriptors. - * See :ref:`prime_buffer_sharing`. - */ -#define DRM_CAP_PRIME 0x5 -/** - * DRM_PRIME_CAP_IMPORT - * - * If this bit is set in &DRM_CAP_PRIME, the driver supports importing PRIME - * buffers via the &DRM_IOCTL_PRIME_FD_TO_HANDLE ioctl. - * - * Starting from kernel version 6.6, this bit is always set in &DRM_CAP_PRIME. - */ -#define DRM_PRIME_CAP_IMPORT 0x1 -/** - * DRM_PRIME_CAP_EXPORT - * - * If this bit is set in &DRM_CAP_PRIME, the driver supports exporting PRIME - * buffers via the &DRM_IOCTL_PRIME_HANDLE_TO_FD ioctl. - * - * Starting from kernel version 6.6, this bit is always set in &DRM_CAP_PRIME. - */ -#define DRM_PRIME_CAP_EXPORT 0x2 -/** - * DRM_CAP_TIMESTAMP_MONOTONIC - * - * If set to 0, the kernel will report timestamps with ``CLOCK_REALTIME`` in - * struct drm_event_vblank. If set to 1, the kernel will report timestamps with - * ``CLOCK_MONOTONIC``. See ``clock_gettime(2)`` for the definition of these - * clocks. - * - * Starting from kernel version 2.6.39, the default value for this capability - * is 1. Starting kernel version 4.15, this capability is always set to 1. - */ -#define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 -/** - * DRM_CAP_ASYNC_PAGE_FLIP - * - * If set to 1, the driver supports &DRM_MODE_PAGE_FLIP_ASYNC for legacy - * page-flips. - */ -#define DRM_CAP_ASYNC_PAGE_FLIP 0x7 -/** - * DRM_CAP_CURSOR_WIDTH - * - * The ``CURSOR_WIDTH`` and ``CURSOR_HEIGHT`` capabilities return a valid - * width x height combination for the hardware cursor. The intention is that a - * hardware agnostic userspace can query a cursor plane size to use. - * - * Note that the cross-driver contract is to merely return a valid size; - * drivers are free to attach another meaning on top, eg. i915 returns the - * maximum plane size. - */ -#define DRM_CAP_CURSOR_WIDTH 0x8 -/** - * DRM_CAP_CURSOR_HEIGHT - * - * See &DRM_CAP_CURSOR_WIDTH. - */ -#define DRM_CAP_CURSOR_HEIGHT 0x9 -/** - * DRM_CAP_ADDFB2_MODIFIERS - * - * If set to 1, the driver supports supplying modifiers in the - * &DRM_IOCTL_MODE_ADDFB2 ioctl. - */ -#define DRM_CAP_ADDFB2_MODIFIERS 0x10 -/** - * DRM_CAP_PAGE_FLIP_TARGET - * - * If set to 1, the driver supports the &DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE and - * &DRM_MODE_PAGE_FLIP_TARGET_RELATIVE flags in - * &drm_mode_crtc_page_flip_target.flags for the &DRM_IOCTL_MODE_PAGE_FLIP - * ioctl. - */ -#define DRM_CAP_PAGE_FLIP_TARGET 0x11 -/** - * DRM_CAP_CRTC_IN_VBLANK_EVENT - * - * If set to 1, the kernel supports reporting the CRTC ID in - * &drm_event_vblank.crtc_id for the &DRM_EVENT_VBLANK and - * &DRM_EVENT_FLIP_COMPLETE events. - * - * Starting kernel version 4.12, this capability is always set to 1. - */ -#define DRM_CAP_CRTC_IN_VBLANK_EVENT 0x12 -/** - * DRM_CAP_SYNCOBJ - * - * If set to 1, the driver supports sync objects. See :ref:`drm_sync_objects`. - */ -#define DRM_CAP_SYNCOBJ 0x13 -/** - * DRM_CAP_SYNCOBJ_TIMELINE - * - * If set to 1, the driver supports timeline operations on sync objects. See - * :ref:`drm_sync_objects`. - */ -#define DRM_CAP_SYNCOBJ_TIMELINE 0x14 -/** - * DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP - * - * If set to 1, the driver supports &DRM_MODE_PAGE_FLIP_ASYNC for atomic - * commits. - */ -#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 - -/* DRM_IOCTL_GET_CAP ioctl argument type */ -struct drm_get_cap { - __u64 capability; - __u64 value; -}; - -/** - * DRM_CLIENT_CAP_STEREO_3D - * - * If set to 1, the DRM core will expose the stereo 3D capabilities of the - * monitor by advertising the supported 3D layouts in the flags of struct - * drm_mode_modeinfo. See ``DRM_MODE_FLAG_3D_*``. - * - * This capability is always supported for all drivers starting from kernel - * version 3.13. - */ -#define DRM_CLIENT_CAP_STEREO_3D 1 - -/** - * DRM_CLIENT_CAP_UNIVERSAL_PLANES - * - * If set to 1, the DRM core will expose all planes (overlay, primary, and - * cursor) to userspace. - * - * This capability has been introduced in kernel version 3.15. Starting from - * kernel version 3.17, this capability is always supported for all drivers. - */ -#define DRM_CLIENT_CAP_UNIVERSAL_PLANES 2 - -/** - * DRM_CLIENT_CAP_ATOMIC - * - * If set to 1, the DRM core will expose atomic properties to userspace. This - * implicitly enables &DRM_CLIENT_CAP_UNIVERSAL_PLANES and - * &DRM_CLIENT_CAP_ASPECT_RATIO. - * - * If the driver doesn't support atomic mode-setting, enabling this capability - * will fail with -EOPNOTSUPP. - * - * This capability has been introduced in kernel version 4.0. Starting from - * kernel version 4.2, this capability is always supported for atomic-capable - * drivers. - */ -#define DRM_CLIENT_CAP_ATOMIC 3 - -/** - * DRM_CLIENT_CAP_ASPECT_RATIO - * - * If set to 1, the DRM core will provide aspect ratio information in modes. - * See ``DRM_MODE_FLAG_PIC_AR_*``. - * - * This capability is always supported for all drivers starting from kernel - * version 4.18. - */ -#define DRM_CLIENT_CAP_ASPECT_RATIO 4 - -/** - * DRM_CLIENT_CAP_WRITEBACK_CONNECTORS - * - * If set to 1, the DRM core will expose special connectors to be used for - * writing back to memory the scene setup in the commit. The client must enable - * &DRM_CLIENT_CAP_ATOMIC first. - * - * This capability is always supported for atomic-capable drivers starting from - * kernel version 4.19. - */ -#define DRM_CLIENT_CAP_WRITEBACK_CONNECTORS 5 - -/** - * DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT - * - * Drivers for para-virtualized hardware (e.g. vmwgfx, qxl, virtio and - * virtualbox) have additional restrictions for cursor planes (thus - * making cursor planes on those drivers not truly universal,) e.g. - * they need cursor planes to act like one would expect from a mouse - * cursor and have correctly set hotspot properties. - * If this client cap is not set the DRM core will hide cursor plane on - * those virtualized drivers because not setting it implies that the - * client is not capable of dealing with those extra restictions. - * Clients which do set cursor hotspot and treat the cursor plane - * like a mouse cursor should set this property. - * The client must enable &DRM_CLIENT_CAP_ATOMIC first. - * - * Setting this property on drivers which do not special case - * cursor planes (i.e. non-virtualized drivers) will return - * EOPNOTSUPP, which can be used by userspace to gauge - * requirements of the hardware/drivers they're running on. - * - * This capability is always supported for atomic-capable virtualized - * drivers starting from kernel version 6.6. - */ -#define DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT 6 - -/* DRM_IOCTL_SET_CLIENT_CAP ioctl argument type */ -struct drm_set_client_cap { - __u64 capability; - __u64 value; -}; - -#define DRM_RDWR O_RDWR -#define DRM_CLOEXEC O_CLOEXEC -struct drm_prime_handle { - __u32 handle; - - /** Flags.. only applicable for handle->fd */ - __u32 flags; - - /** Returned dmabuf file descriptor */ - __s32 fd; -}; - -struct drm_syncobj_create { - __u32 handle; -#define DRM_SYNCOBJ_CREATE_SIGNALED (1 << 0) - __u32 flags; -}; - -struct drm_syncobj_destroy { - __u32 handle; - __u32 pad; -}; - -#define DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE (1 << 0) -#define DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE (1 << 0) -struct drm_syncobj_handle { - __u32 handle; - __u32 flags; - - __s32 fd; - __u32 pad; -}; - -struct drm_syncobj_transfer { - __u32 src_handle; - __u32 dst_handle; - __u64 src_point; - __u64 dst_point; - __u32 flags; - __u32 pad; -}; - -#define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0) -#define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1) -#define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE (1 << 2) /* wait for time point to become available */ -#define DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE (1 << 3) /* set fence deadline to deadline_nsec */ -struct drm_syncobj_wait { - __u64 handles; - /* absolute timeout */ - __s64 timeout_nsec; - __u32 count_handles; - __u32 flags; - __u32 first_signaled; /* only valid when not waiting all */ - __u32 pad; - /** - * @deadline_nsec - fence deadline hint - * - * Deadline hint, in absolute CLOCK_MONOTONIC, to set on backing - * fence(s) if the DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE flag is - * set. - */ - __u64 deadline_nsec; -}; - -struct drm_syncobj_timeline_wait { - __u64 handles; - /* wait on specific timeline point for every handles*/ - __u64 points; - /* absolute timeout */ - __s64 timeout_nsec; - __u32 count_handles; - __u32 flags; - __u32 first_signaled; /* only valid when not waiting all */ - __u32 pad; - /** - * @deadline_nsec - fence deadline hint - * - * Deadline hint, in absolute CLOCK_MONOTONIC, to set on backing - * fence(s) if the DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE flag is - * set. - */ - __u64 deadline_nsec; -}; - -/** - * struct drm_syncobj_eventfd - * @handle: syncobj handle. - * @flags: Zero to wait for the point to be signalled, or - * &DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE to wait for a fence to be - * available for the point. - * @point: syncobj timeline point (set to zero for binary syncobjs). - * @fd: Existing eventfd to sent events to. - * @pad: Must be zero. - * - * Register an eventfd to be signalled by a syncobj. The eventfd counter will - * be incremented by one. - */ -struct drm_syncobj_eventfd { - __u32 handle; - __u32 flags; - __u64 point; - __s32 fd; - __u32 pad; -}; - - -struct drm_syncobj_array { - __u64 handles; - __u32 count_handles; - __u32 pad; -}; - -#define DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED (1 << 0) /* last available point on timeline syncobj */ -struct drm_syncobj_timeline_array { - __u64 handles; - __u64 points; - __u32 count_handles; - __u32 flags; -}; - - -/* Query current scanout sequence number */ -struct drm_crtc_get_sequence { - __u32 crtc_id; /* requested crtc_id */ - __u32 active; /* return: crtc output is active */ - __u64 sequence; /* return: most recent vblank sequence */ - __s64 sequence_ns; /* return: most recent time of first pixel out */ -}; - -/* Queue event to be delivered at specified sequence. Time stamp marks - * when the first pixel of the refresh cycle leaves the display engine - * for the display - */ -#define DRM_CRTC_SEQUENCE_RELATIVE 0x00000001 /* sequence is relative to current */ -#define DRM_CRTC_SEQUENCE_NEXT_ON_MISS 0x00000002 /* Use next sequence if we've missed */ - -struct drm_crtc_queue_sequence { - __u32 crtc_id; - __u32 flags; - __u64 sequence; /* on input, target sequence. on output, actual sequence */ - __u64 user_data; /* user data passed to event */ -}; - -#if defined(__cplusplus) -} -#endif - -#include "drm_mode.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -#define DRM_IOCTL_BASE 'd' -#define DRM_IO(nr) _IO(DRM_IOCTL_BASE,nr) -#define DRM_IOR(nr,type) _IOR(DRM_IOCTL_BASE,nr,type) -#define DRM_IOW(nr,type) _IOW(DRM_IOCTL_BASE,nr,type) -#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type) - -#define DRM_IOCTL_VERSION DRM_IOWR(0x00, struct drm_version) -#define DRM_IOCTL_GET_UNIQUE DRM_IOWR(0x01, struct drm_unique) -#define DRM_IOCTL_GET_MAGIC DRM_IOR( 0x02, struct drm_auth) -#define DRM_IOCTL_IRQ_BUSID DRM_IOWR(0x03, struct drm_irq_busid) -#define DRM_IOCTL_GET_MAP DRM_IOWR(0x04, struct drm_map) -#define DRM_IOCTL_GET_CLIENT DRM_IOWR(0x05, struct drm_client) -#define DRM_IOCTL_GET_STATS DRM_IOR( 0x06, struct drm_stats) -#define DRM_IOCTL_SET_VERSION DRM_IOWR(0x07, struct drm_set_version) -#define DRM_IOCTL_MODESET_CTL DRM_IOW(0x08, struct drm_modeset_ctl) -/** - * DRM_IOCTL_GEM_CLOSE - Close a GEM handle. - * - * GEM handles are not reference-counted by the kernel. User-space is - * responsible for managing their lifetime. For example, if user-space imports - * the same memory object twice on the same DRM file description, the same GEM - * handle is returned by both imports, and user-space needs to ensure - * &DRM_IOCTL_GEM_CLOSE is performed once only. The same situation can happen - * when a memory object is allocated, then exported and imported again on the - * same DRM file description. The &DRM_IOCTL_MODE_GETFB2 IOCTL is an exception - * and always returns fresh new GEM handles even if an existing GEM handle - * already refers to the same memory object before the IOCTL is performed. - */ -#define DRM_IOCTL_GEM_CLOSE DRM_IOW (0x09, struct drm_gem_close) -#define DRM_IOCTL_GEM_FLINK DRM_IOWR(0x0a, struct drm_gem_flink) -#define DRM_IOCTL_GEM_OPEN DRM_IOWR(0x0b, struct drm_gem_open) -#define DRM_IOCTL_GET_CAP DRM_IOWR(0x0c, struct drm_get_cap) -#define DRM_IOCTL_SET_CLIENT_CAP DRM_IOW( 0x0d, struct drm_set_client_cap) - -#define DRM_IOCTL_SET_UNIQUE DRM_IOW( 0x10, struct drm_unique) -#define DRM_IOCTL_AUTH_MAGIC DRM_IOW( 0x11, struct drm_auth) -#define DRM_IOCTL_BLOCK DRM_IOWR(0x12, struct drm_block) -#define DRM_IOCTL_UNBLOCK DRM_IOWR(0x13, struct drm_block) -#define DRM_IOCTL_CONTROL DRM_IOW( 0x14, struct drm_control) -#define DRM_IOCTL_ADD_MAP DRM_IOWR(0x15, struct drm_map) -#define DRM_IOCTL_ADD_BUFS DRM_IOWR(0x16, struct drm_buf_desc) -#define DRM_IOCTL_MARK_BUFS DRM_IOW( 0x17, struct drm_buf_desc) -#define DRM_IOCTL_INFO_BUFS DRM_IOWR(0x18, struct drm_buf_info) -#define DRM_IOCTL_MAP_BUFS DRM_IOWR(0x19, struct drm_buf_map) -#define DRM_IOCTL_FREE_BUFS DRM_IOW( 0x1a, struct drm_buf_free) - -#define DRM_IOCTL_RM_MAP DRM_IOW( 0x1b, struct drm_map) - -#define DRM_IOCTL_SET_SAREA_CTX DRM_IOW( 0x1c, struct drm_ctx_priv_map) -#define DRM_IOCTL_GET_SAREA_CTX DRM_IOWR(0x1d, struct drm_ctx_priv_map) - -#define DRM_IOCTL_SET_MASTER DRM_IO(0x1e) -#define DRM_IOCTL_DROP_MASTER DRM_IO(0x1f) - -#define DRM_IOCTL_ADD_CTX DRM_IOWR(0x20, struct drm_ctx) -#define DRM_IOCTL_RM_CTX DRM_IOWR(0x21, struct drm_ctx) -#define DRM_IOCTL_MOD_CTX DRM_IOW( 0x22, struct drm_ctx) -#define DRM_IOCTL_GET_CTX DRM_IOWR(0x23, struct drm_ctx) -#define DRM_IOCTL_SWITCH_CTX DRM_IOW( 0x24, struct drm_ctx) -#define DRM_IOCTL_NEW_CTX DRM_IOW( 0x25, struct drm_ctx) -#define DRM_IOCTL_RES_CTX DRM_IOWR(0x26, struct drm_ctx_res) -#define DRM_IOCTL_ADD_DRAW DRM_IOWR(0x27, struct drm_draw) -#define DRM_IOCTL_RM_DRAW DRM_IOWR(0x28, struct drm_draw) -#define DRM_IOCTL_DMA DRM_IOWR(0x29, struct drm_dma) -#define DRM_IOCTL_LOCK DRM_IOW( 0x2a, struct drm_lock) -#define DRM_IOCTL_UNLOCK DRM_IOW( 0x2b, struct drm_lock) -#define DRM_IOCTL_FINISH DRM_IOW( 0x2c, struct drm_lock) - -/** - * DRM_IOCTL_PRIME_HANDLE_TO_FD - Convert a GEM handle to a DMA-BUF FD. - * - * User-space sets &drm_prime_handle.handle with the GEM handle to export and - * &drm_prime_handle.flags, and gets back a DMA-BUF file descriptor in - * &drm_prime_handle.fd. - * - * The export can fail for any driver-specific reason, e.g. because export is - * not supported for this specific GEM handle (but might be for others). - * - * Support for exporting DMA-BUFs is advertised via &DRM_PRIME_CAP_EXPORT. - */ -#define DRM_IOCTL_PRIME_HANDLE_TO_FD DRM_IOWR(0x2d, struct drm_prime_handle) -/** - * DRM_IOCTL_PRIME_FD_TO_HANDLE - Convert a DMA-BUF FD to a GEM handle. - * - * User-space sets &drm_prime_handle.fd with a DMA-BUF file descriptor to - * import, and gets back a GEM handle in &drm_prime_handle.handle. - * &drm_prime_handle.flags is unused. - * - * If an existing GEM handle refers to the memory object backing the DMA-BUF, - * that GEM handle is returned. Therefore user-space which needs to handle - * arbitrary DMA-BUFs must have a user-space lookup data structure to manually - * reference-count duplicated GEM handles. For more information see - * &DRM_IOCTL_GEM_CLOSE. - * - * The import can fail for any driver-specific reason, e.g. because import is - * only supported for DMA-BUFs allocated on this DRM device. - * - * Support for importing DMA-BUFs is advertised via &DRM_PRIME_CAP_IMPORT. - */ -#define DRM_IOCTL_PRIME_FD_TO_HANDLE DRM_IOWR(0x2e, struct drm_prime_handle) - -#define DRM_IOCTL_AGP_ACQUIRE DRM_IO( 0x30) -#define DRM_IOCTL_AGP_RELEASE DRM_IO( 0x31) -#define DRM_IOCTL_AGP_ENABLE DRM_IOW( 0x32, struct drm_agp_mode) -#define DRM_IOCTL_AGP_INFO DRM_IOR( 0x33, struct drm_agp_info) -#define DRM_IOCTL_AGP_ALLOC DRM_IOWR(0x34, struct drm_agp_buffer) -#define DRM_IOCTL_AGP_FREE DRM_IOW( 0x35, struct drm_agp_buffer) -#define DRM_IOCTL_AGP_BIND DRM_IOW( 0x36, struct drm_agp_binding) -#define DRM_IOCTL_AGP_UNBIND DRM_IOW( 0x37, struct drm_agp_binding) - -#define DRM_IOCTL_SG_ALLOC DRM_IOWR(0x38, struct drm_scatter_gather) -#define DRM_IOCTL_SG_FREE DRM_IOW( 0x39, struct drm_scatter_gather) - -#define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, union drm_wait_vblank) - -#define DRM_IOCTL_CRTC_GET_SEQUENCE DRM_IOWR(0x3b, struct drm_crtc_get_sequence) -#define DRM_IOCTL_CRTC_QUEUE_SEQUENCE DRM_IOWR(0x3c, struct drm_crtc_queue_sequence) - -#define DRM_IOCTL_UPDATE_DRAW DRM_IOW(0x3f, struct drm_update_draw) - -#define DRM_IOCTL_MODE_GETRESOURCES DRM_IOWR(0xA0, struct drm_mode_card_res) -#define DRM_IOCTL_MODE_GETCRTC DRM_IOWR(0xA1, struct drm_mode_crtc) -#define DRM_IOCTL_MODE_SETCRTC DRM_IOWR(0xA2, struct drm_mode_crtc) -#define DRM_IOCTL_MODE_CURSOR DRM_IOWR(0xA3, struct drm_mode_cursor) -#define DRM_IOCTL_MODE_GETGAMMA DRM_IOWR(0xA4, struct drm_mode_crtc_lut) -#define DRM_IOCTL_MODE_SETGAMMA DRM_IOWR(0xA5, struct drm_mode_crtc_lut) -#define DRM_IOCTL_MODE_GETENCODER DRM_IOWR(0xA6, struct drm_mode_get_encoder) -#define DRM_IOCTL_MODE_GETCONNECTOR DRM_IOWR(0xA7, struct drm_mode_get_connector) -#define DRM_IOCTL_MODE_ATTACHMODE DRM_IOWR(0xA8, struct drm_mode_mode_cmd) /* deprecated (never worked) */ -#define DRM_IOCTL_MODE_DETACHMODE DRM_IOWR(0xA9, struct drm_mode_mode_cmd) /* deprecated (never worked) */ - -#define DRM_IOCTL_MODE_GETPROPERTY DRM_IOWR(0xAA, struct drm_mode_get_property) -#define DRM_IOCTL_MODE_SETPROPERTY DRM_IOWR(0xAB, struct drm_mode_connector_set_property) -#define DRM_IOCTL_MODE_GETPROPBLOB DRM_IOWR(0xAC, struct drm_mode_get_blob) -#define DRM_IOCTL_MODE_GETFB DRM_IOWR(0xAD, struct drm_mode_fb_cmd) -#define DRM_IOCTL_MODE_ADDFB DRM_IOWR(0xAE, struct drm_mode_fb_cmd) -/** - * DRM_IOCTL_MODE_RMFB - Remove a framebuffer. - * - * This removes a framebuffer previously added via ADDFB/ADDFB2. The IOCTL - * argument is a framebuffer object ID. - * - * Warning: removing a framebuffer currently in-use on an enabled plane will - * disable that plane. The CRTC the plane is linked to may also be disabled - * (depending on driver capabilities). - */ -#define DRM_IOCTL_MODE_RMFB DRM_IOWR(0xAF, unsigned int) -#define DRM_IOCTL_MODE_PAGE_FLIP DRM_IOWR(0xB0, struct drm_mode_crtc_page_flip) -#define DRM_IOCTL_MODE_DIRTYFB DRM_IOWR(0xB1, struct drm_mode_fb_dirty_cmd) - -/** - * DRM_IOCTL_MODE_CREATE_DUMB - Create a new dumb buffer object. - * - * KMS dumb buffers provide a very primitive way to allocate a buffer object - * suitable for scanout and map it for software rendering. KMS dumb buffers are - * not suitable for hardware-accelerated rendering nor video decoding. KMS dumb - * buffers are not suitable to be displayed on any other device than the KMS - * device where they were allocated from. Also see - * :ref:`kms_dumb_buffer_objects`. - * - * The IOCTL argument is a struct drm_mode_create_dumb. - * - * User-space is expected to create a KMS dumb buffer via this IOCTL, then add - * it as a KMS framebuffer via &DRM_IOCTL_MODE_ADDFB and map it via - * &DRM_IOCTL_MODE_MAP_DUMB. - * - * &DRM_CAP_DUMB_BUFFER indicates whether this IOCTL is supported. - * &DRM_CAP_DUMB_PREFERRED_DEPTH and &DRM_CAP_DUMB_PREFER_SHADOW indicate - * driver preferences for dumb buffers. - */ -#define DRM_IOCTL_MODE_CREATE_DUMB DRM_IOWR(0xB2, struct drm_mode_create_dumb) -#define DRM_IOCTL_MODE_MAP_DUMB DRM_IOWR(0xB3, struct drm_mode_map_dumb) -#define DRM_IOCTL_MODE_DESTROY_DUMB DRM_IOWR(0xB4, struct drm_mode_destroy_dumb) -#define DRM_IOCTL_MODE_GETPLANERESOURCES DRM_IOWR(0xB5, struct drm_mode_get_plane_res) -#define DRM_IOCTL_MODE_GETPLANE DRM_IOWR(0xB6, struct drm_mode_get_plane) -#define DRM_IOCTL_MODE_SETPLANE DRM_IOWR(0xB7, struct drm_mode_set_plane) -#define DRM_IOCTL_MODE_ADDFB2 DRM_IOWR(0xB8, struct drm_mode_fb_cmd2) -#define DRM_IOCTL_MODE_OBJ_GETPROPERTIES DRM_IOWR(0xB9, struct drm_mode_obj_get_properties) -#define DRM_IOCTL_MODE_OBJ_SETPROPERTY DRM_IOWR(0xBA, struct drm_mode_obj_set_property) -#define DRM_IOCTL_MODE_CURSOR2 DRM_IOWR(0xBB, struct drm_mode_cursor2) -#define DRM_IOCTL_MODE_ATOMIC DRM_IOWR(0xBC, struct drm_mode_atomic) -#define DRM_IOCTL_MODE_CREATEPROPBLOB DRM_IOWR(0xBD, struct drm_mode_create_blob) -#define DRM_IOCTL_MODE_DESTROYPROPBLOB DRM_IOWR(0xBE, struct drm_mode_destroy_blob) - -#define DRM_IOCTL_SYNCOBJ_CREATE DRM_IOWR(0xBF, struct drm_syncobj_create) -#define DRM_IOCTL_SYNCOBJ_DESTROY DRM_IOWR(0xC0, struct drm_syncobj_destroy) -#define DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD DRM_IOWR(0xC1, struct drm_syncobj_handle) -#define DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE DRM_IOWR(0xC2, struct drm_syncobj_handle) -#define DRM_IOCTL_SYNCOBJ_WAIT DRM_IOWR(0xC3, struct drm_syncobj_wait) -#define DRM_IOCTL_SYNCOBJ_RESET DRM_IOWR(0xC4, struct drm_syncobj_array) -#define DRM_IOCTL_SYNCOBJ_SIGNAL DRM_IOWR(0xC5, struct drm_syncobj_array) - -#define DRM_IOCTL_MODE_CREATE_LEASE DRM_IOWR(0xC6, struct drm_mode_create_lease) -#define DRM_IOCTL_MODE_LIST_LESSEES DRM_IOWR(0xC7, struct drm_mode_list_lessees) -#define DRM_IOCTL_MODE_GET_LEASE DRM_IOWR(0xC8, struct drm_mode_get_lease) -#define DRM_IOCTL_MODE_REVOKE_LEASE DRM_IOWR(0xC9, struct drm_mode_revoke_lease) - -#define DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT DRM_IOWR(0xCA, struct drm_syncobj_timeline_wait) -#define DRM_IOCTL_SYNCOBJ_QUERY DRM_IOWR(0xCB, struct drm_syncobj_timeline_array) -#define DRM_IOCTL_SYNCOBJ_TRANSFER DRM_IOWR(0xCC, struct drm_syncobj_transfer) -#define DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL DRM_IOWR(0xCD, struct drm_syncobj_timeline_array) - -/** - * DRM_IOCTL_MODE_GETFB2 - Get framebuffer metadata. - * - * This queries metadata about a framebuffer. User-space fills - * &drm_mode_fb_cmd2.fb_id as the input, and the kernels fills the rest of the - * struct as the output. - * - * If the client is DRM master or has &CAP_SYS_ADMIN, &drm_mode_fb_cmd2.handles - * will be filled with GEM buffer handles. Fresh new GEM handles are always - * returned, even if another GEM handle referring to the same memory object - * already exists on the DRM file description. The caller is responsible for - * removing the new handles, e.g. via the &DRM_IOCTL_GEM_CLOSE IOCTL. The same - * new handle will be returned for multiple planes in case they use the same - * memory object. Planes are valid until one has a zero handle -- this can be - * used to compute the number of planes. - * - * Otherwise, &drm_mode_fb_cmd2.handles will be zeroed and planes are valid - * until one has a zero &drm_mode_fb_cmd2.pitches. - * - * If the framebuffer has a format modifier, &DRM_MODE_FB_MODIFIERS will be set - * in &drm_mode_fb_cmd2.flags and &drm_mode_fb_cmd2.modifier will contain the - * modifier. Otherwise, user-space must ignore &drm_mode_fb_cmd2.modifier. - * - * To obtain DMA-BUF FDs for each plane without leaking GEM handles, user-space - * can export each handle via &DRM_IOCTL_PRIME_HANDLE_TO_FD, then immediately - * close each unique handle via &DRM_IOCTL_GEM_CLOSE, making sure to not - * double-close handles which are specified multiple times in the array. - */ -#define DRM_IOCTL_MODE_GETFB2 DRM_IOWR(0xCE, struct drm_mode_fb_cmd2) - -#define DRM_IOCTL_SYNCOBJ_EVENTFD DRM_IOWR(0xCF, struct drm_syncobj_eventfd) - -/** - * DRM_IOCTL_MODE_CLOSEFB - Close a framebuffer. - * - * This closes a framebuffer previously added via ADDFB/ADDFB2. The IOCTL - * argument is a framebuffer object ID. - * - * This IOCTL is similar to &DRM_IOCTL_MODE_RMFB, except it doesn't disable - * planes and CRTCs. As long as the framebuffer is used by a plane, it's kept - * alive. When the plane no longer uses the framebuffer (because the - * framebuffer is replaced with another one, or the plane is disabled), the - * framebuffer is cleaned up. - * - * This is useful to implement flicker-free transitions between two processes. - * - * Depending on the threat model, user-space may want to ensure that the - * framebuffer doesn't expose any sensitive user information: closed - * framebuffers attached to a plane can be read back by the next DRM master. - */ -#define DRM_IOCTL_MODE_CLOSEFB DRM_IOWR(0xD0, struct drm_mode_closefb) - -/* - * Device specific ioctls should only be in their respective headers - * The device specific ioctl range is from 0x40 to 0x9f. - * Generic IOCTLS restart at 0xA0. - * - * \sa drmCommandNone(), drmCommandRead(), drmCommandWrite(), and - * drmCommandReadWrite(). - */ -#define DRM_COMMAND_BASE 0x40 -#define DRM_COMMAND_END 0xA0 - -/** - * struct drm_event - Header for DRM events - * @type: event type. - * @length: total number of payload bytes (including header). - * - * This struct is a header for events written back to user-space on the DRM FD. - * A read on the DRM FD will always only return complete events: e.g. if the - * read buffer is 100 bytes large and there are two 64 byte events pending, - * only one will be returned. - * - * Event types 0 - 0x7fffffff are generic DRM events, 0x80000000 and - * up are chipset specific. Generic DRM events include &DRM_EVENT_VBLANK, - * &DRM_EVENT_FLIP_COMPLETE and &DRM_EVENT_CRTC_SEQUENCE. - */ -struct drm_event { - __u32 type; - __u32 length; -}; - -/** - * DRM_EVENT_VBLANK - vertical blanking event - * - * This event is sent in response to &DRM_IOCTL_WAIT_VBLANK with the - * &_DRM_VBLANK_EVENT flag set. - * - * The event payload is a struct drm_event_vblank. - */ -#define DRM_EVENT_VBLANK 0x01 -/** - * DRM_EVENT_FLIP_COMPLETE - page-flip completion event - * - * This event is sent in response to an atomic commit or legacy page-flip with - * the &DRM_MODE_PAGE_FLIP_EVENT flag set. - * - * The event payload is a struct drm_event_vblank. - */ -#define DRM_EVENT_FLIP_COMPLETE 0x02 -/** - * DRM_EVENT_CRTC_SEQUENCE - CRTC sequence event - * - * This event is sent in response to &DRM_IOCTL_CRTC_QUEUE_SEQUENCE. - * - * The event payload is a struct drm_event_crtc_sequence. - */ -#define DRM_EVENT_CRTC_SEQUENCE 0x03 - -struct drm_event_vblank { - struct drm_event base; - __u64 user_data; - __u32 tv_sec; - __u32 tv_usec; - __u32 sequence; - __u32 crtc_id; /* 0 on older kernels that do not support this */ -}; - -/* Event delivered at sequence. Time stamp marks when the first pixel - * of the refresh cycle leaves the display engine for the display - */ -struct drm_event_crtc_sequence { - struct drm_event base; - __u64 user_data; - __s64 time_ns; - __u64 sequence; -}; - -/* typedef area */ -#ifndef __KERNEL__ -typedef struct drm_clip_rect drm_clip_rect_t; -typedef struct drm_drawable_info drm_drawable_info_t; -typedef struct drm_tex_region drm_tex_region_t; -typedef struct drm_hw_lock drm_hw_lock_t; -typedef struct drm_version drm_version_t; -typedef struct drm_unique drm_unique_t; -typedef struct drm_list drm_list_t; -typedef struct drm_block drm_block_t; -typedef struct drm_control drm_control_t; -typedef enum drm_map_type drm_map_type_t; -typedef enum drm_map_flags drm_map_flags_t; -typedef struct drm_ctx_priv_map drm_ctx_priv_map_t; -typedef struct drm_map drm_map_t; -typedef struct drm_client drm_client_t; -typedef enum drm_stat_type drm_stat_type_t; -typedef struct drm_stats drm_stats_t; -typedef enum drm_lock_flags drm_lock_flags_t; -typedef struct drm_lock drm_lock_t; -typedef enum drm_dma_flags drm_dma_flags_t; -typedef struct drm_buf_desc drm_buf_desc_t; -typedef struct drm_buf_info drm_buf_info_t; -typedef struct drm_buf_free drm_buf_free_t; -typedef struct drm_buf_pub drm_buf_pub_t; -typedef struct drm_buf_map drm_buf_map_t; -typedef struct drm_dma drm_dma_t; -typedef union drm_wait_vblank drm_wait_vblank_t; -typedef struct drm_agp_mode drm_agp_mode_t; -typedef enum drm_ctx_flags drm_ctx_flags_t; -typedef struct drm_ctx drm_ctx_t; -typedef struct drm_ctx_res drm_ctx_res_t; -typedef struct drm_draw drm_draw_t; -typedef struct drm_update_draw drm_update_draw_t; -typedef struct drm_auth drm_auth_t; -typedef struct drm_irq_busid drm_irq_busid_t; -typedef enum drm_vblank_seq_type drm_vblank_seq_type_t; - -typedef struct drm_agp_buffer drm_agp_buffer_t; -typedef struct drm_agp_binding drm_agp_binding_t; -typedef struct drm_agp_info drm_agp_info_t; -typedef struct drm_scatter_gather drm_scatter_gather_t; -typedef struct drm_set_version drm_set_version_t; -#endif - -#if defined(__cplusplus) -} -#endif - -#endif diff --git a/libc/musl-overlay/include/drm/drm_fourcc.h b/libc/musl-overlay/include/drm/drm_fourcc.h deleted file mode 100644 index 78abd819fd..0000000000 --- a/libc/musl-overlay/include/drm/drm_fourcc.h +++ /dev/null @@ -1,1614 +0,0 @@ -/* - * Copyright 2011 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef DRM_FOURCC_H -#define DRM_FOURCC_H - -#include "drm.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/** - * DOC: overview - * - * In the DRM subsystem, framebuffer pixel formats are described using the - * fourcc codes defined in `include/uapi/drm/drm_fourcc.h`. In addition to the - * fourcc code, a Format Modifier may optionally be provided, in order to - * further describe the buffer's format - for example tiling or compression. - * - * Format Modifiers - * ---------------- - * - * Format modifiers are used in conjunction with a fourcc code, forming a - * unique fourcc:modifier pair. This format:modifier pair must fully define the - * format and data layout of the buffer, and should be the only way to describe - * that particular buffer. - * - * Having multiple fourcc:modifier pairs which describe the same layout should - * be avoided, as such aliases run the risk of different drivers exposing - * different names for the same data format, forcing userspace to understand - * that they are aliases. - * - * Format modifiers may change any property of the buffer, including the number - * of planes and/or the required allocation size. Format modifiers are - * vendor-namespaced, and as such the relationship between a fourcc code and a - * modifier is specific to the modifier being used. For example, some modifiers - * may preserve meaning - such as number of planes - from the fourcc code, - * whereas others may not. - * - * Modifiers must uniquely encode buffer layout. In other words, a buffer must - * match only a single modifier. A modifier must not be a subset of layouts of - * another modifier. For instance, it's incorrect to encode pitch alignment in - * a modifier: a buffer may match a 64-pixel aligned modifier and a 32-pixel - * aligned modifier. That said, modifiers can have implicit minimal - * requirements. - * - * For modifiers where the combination of fourcc code and modifier can alias, - * a canonical pair needs to be defined and used by all drivers. Preferred - * combinations are also encouraged where all combinations might lead to - * confusion and unnecessarily reduced interoperability. An example for the - * latter is AFBC, where the ABGR layouts are preferred over ARGB layouts. - * - * There are two kinds of modifier users: - * - * - Kernel and user-space drivers: for drivers it's important that modifiers - * don't alias, otherwise two drivers might support the same format but use - * different aliases, preventing them from sharing buffers in an efficient - * format. - * - Higher-level programs interfacing with KMS/GBM/EGL/Vulkan/etc: these users - * see modifiers as opaque tokens they can check for equality and intersect. - * These users mustn't need to know to reason about the modifier value - * (i.e. they are not expected to extract information out of the modifier). - * - * Vendors should document their modifier usage in as much detail as - * possible, to ensure maximum compatibility across devices, drivers and - * applications. - * - * The authoritative list of format modifier codes is found in - * `include/uapi/drm/drm_fourcc.h` - * - * Open Source User Waiver - * ----------------------- - * - * Because this is the authoritative source for pixel formats and modifiers - * referenced by GL, Vulkan extensions and other standards and hence used both - * by open source and closed source driver stacks, the usual requirement for an - * upstream in-kernel or open source userspace user does not apply. - * - * To ensure, as much as feasible, compatibility across stacks and avoid - * confusion with incompatible enumerations stakeholders for all relevant driver - * stacks should approve additions. - */ - -#define fourcc_code(a, b, c, d) ((__u32)(a) | ((__u32)(b) << 8) | \ - ((__u32)(c) << 16) | ((__u32)(d) << 24)) - -#define DRM_FORMAT_BIG_ENDIAN (1U<<31) /* format is big endian instead of little endian */ - -/* Reserve 0 for the invalid format specifier */ -#define DRM_FORMAT_INVALID 0 - -/* color index */ -#define DRM_FORMAT_C1 fourcc_code('C', '1', ' ', ' ') /* [7:0] C0:C1:C2:C3:C4:C5:C6:C7 1:1:1:1:1:1:1:1 eight pixels/byte */ -#define DRM_FORMAT_C2 fourcc_code('C', '2', ' ', ' ') /* [7:0] C0:C1:C2:C3 2:2:2:2 four pixels/byte */ -#define DRM_FORMAT_C4 fourcc_code('C', '4', ' ', ' ') /* [7:0] C0:C1 4:4 two pixels/byte */ -#define DRM_FORMAT_C8 fourcc_code('C', '8', ' ', ' ') /* [7:0] C */ - -/* 1 bpp Darkness (inverse relationship between channel value and brightness) */ -#define DRM_FORMAT_D1 fourcc_code('D', '1', ' ', ' ') /* [7:0] D0:D1:D2:D3:D4:D5:D6:D7 1:1:1:1:1:1:1:1 eight pixels/byte */ - -/* 2 bpp Darkness (inverse relationship between channel value and brightness) */ -#define DRM_FORMAT_D2 fourcc_code('D', '2', ' ', ' ') /* [7:0] D0:D1:D2:D3 2:2:2:2 four pixels/byte */ - -/* 4 bpp Darkness (inverse relationship between channel value and brightness) */ -#define DRM_FORMAT_D4 fourcc_code('D', '4', ' ', ' ') /* [7:0] D0:D1 4:4 two pixels/byte */ - -/* 8 bpp Darkness (inverse relationship between channel value and brightness) */ -#define DRM_FORMAT_D8 fourcc_code('D', '8', ' ', ' ') /* [7:0] D */ - -/* 1 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R1 fourcc_code('R', '1', ' ', ' ') /* [7:0] R0:R1:R2:R3:R4:R5:R6:R7 1:1:1:1:1:1:1:1 eight pixels/byte */ - -/* 2 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R2 fourcc_code('R', '2', ' ', ' ') /* [7:0] R0:R1:R2:R3 2:2:2:2 four pixels/byte */ - -/* 4 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R4 fourcc_code('R', '4', ' ', ' ') /* [7:0] R0:R1 4:4 two pixels/byte */ - -/* 8 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ - -/* 10 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R10 fourcc_code('R', '1', '0', ' ') /* [15:0] x:R 6:10 little endian */ - -/* 12 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R12 fourcc_code('R', '1', '2', ' ') /* [15:0] x:R 4:12 little endian */ - -/* 16 bpp Red (direct relationship between channel value and brightness) */ -#define DRM_FORMAT_R16 fourcc_code('R', '1', '6', ' ') /* [15:0] R little endian */ - -/* 16 bpp RG */ -#define DRM_FORMAT_RG88 fourcc_code('R', 'G', '8', '8') /* [15:0] R:G 8:8 little endian */ -#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ - -/* 32 bpp RG */ -#define DRM_FORMAT_RG1616 fourcc_code('R', 'G', '3', '2') /* [31:0] R:G 16:16 little endian */ -#define DRM_FORMAT_GR1616 fourcc_code('G', 'R', '3', '2') /* [31:0] G:R 16:16 little endian */ - -/* 8 bpp RGB */ -#define DRM_FORMAT_RGB332 fourcc_code('R', 'G', 'B', '8') /* [7:0] R:G:B 3:3:2 */ -#define DRM_FORMAT_BGR233 fourcc_code('B', 'G', 'R', '8') /* [7:0] B:G:R 2:3:3 */ - -/* 16 bpp RGB */ -#define DRM_FORMAT_XRGB4444 fourcc_code('X', 'R', '1', '2') /* [15:0] x:R:G:B 4:4:4:4 little endian */ -#define DRM_FORMAT_XBGR4444 fourcc_code('X', 'B', '1', '2') /* [15:0] x:B:G:R 4:4:4:4 little endian */ -#define DRM_FORMAT_RGBX4444 fourcc_code('R', 'X', '1', '2') /* [15:0] R:G:B:x 4:4:4:4 little endian */ -#define DRM_FORMAT_BGRX4444 fourcc_code('B', 'X', '1', '2') /* [15:0] B:G:R:x 4:4:4:4 little endian */ - -#define DRM_FORMAT_ARGB4444 fourcc_code('A', 'R', '1', '2') /* [15:0] A:R:G:B 4:4:4:4 little endian */ -#define DRM_FORMAT_ABGR4444 fourcc_code('A', 'B', '1', '2') /* [15:0] A:B:G:R 4:4:4:4 little endian */ -#define DRM_FORMAT_RGBA4444 fourcc_code('R', 'A', '1', '2') /* [15:0] R:G:B:A 4:4:4:4 little endian */ -#define DRM_FORMAT_BGRA4444 fourcc_code('B', 'A', '1', '2') /* [15:0] B:G:R:A 4:4:4:4 little endian */ - -#define DRM_FORMAT_XRGB1555 fourcc_code('X', 'R', '1', '5') /* [15:0] x:R:G:B 1:5:5:5 little endian */ -#define DRM_FORMAT_XBGR1555 fourcc_code('X', 'B', '1', '5') /* [15:0] x:B:G:R 1:5:5:5 little endian */ -#define DRM_FORMAT_RGBX5551 fourcc_code('R', 'X', '1', '5') /* [15:0] R:G:B:x 5:5:5:1 little endian */ -#define DRM_FORMAT_BGRX5551 fourcc_code('B', 'X', '1', '5') /* [15:0] B:G:R:x 5:5:5:1 little endian */ - -#define DRM_FORMAT_ARGB1555 fourcc_code('A', 'R', '1', '5') /* [15:0] A:R:G:B 1:5:5:5 little endian */ -#define DRM_FORMAT_ABGR1555 fourcc_code('A', 'B', '1', '5') /* [15:0] A:B:G:R 1:5:5:5 little endian */ -#define DRM_FORMAT_RGBA5551 fourcc_code('R', 'A', '1', '5') /* [15:0] R:G:B:A 5:5:5:1 little endian */ -#define DRM_FORMAT_BGRA5551 fourcc_code('B', 'A', '1', '5') /* [15:0] B:G:R:A 5:5:5:1 little endian */ - -#define DRM_FORMAT_RGB565 fourcc_code('R', 'G', '1', '6') /* [15:0] R:G:B 5:6:5 little endian */ -#define DRM_FORMAT_BGR565 fourcc_code('B', 'G', '1', '6') /* [15:0] B:G:R 5:6:5 little endian */ - -/* 24 bpp RGB */ -#define DRM_FORMAT_RGB888 fourcc_code('R', 'G', '2', '4') /* [23:0] R:G:B little endian */ -#define DRM_FORMAT_BGR888 fourcc_code('B', 'G', '2', '4') /* [23:0] B:G:R little endian */ - -/* 32 bpp RGB */ -#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */ -#define DRM_FORMAT_XBGR8888 fourcc_code('X', 'B', '2', '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */ -#define DRM_FORMAT_RGBX8888 fourcc_code('R', 'X', '2', '4') /* [31:0] R:G:B:x 8:8:8:8 little endian */ -#define DRM_FORMAT_BGRX8888 fourcc_code('B', 'X', '2', '4') /* [31:0] B:G:R:x 8:8:8:8 little endian */ - -#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */ -#define DRM_FORMAT_ABGR8888 fourcc_code('A', 'B', '2', '4') /* [31:0] A:B:G:R 8:8:8:8 little endian */ -#define DRM_FORMAT_RGBA8888 fourcc_code('R', 'A', '2', '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */ -#define DRM_FORMAT_BGRA8888 fourcc_code('B', 'A', '2', '4') /* [31:0] B:G:R:A 8:8:8:8 little endian */ - -#define DRM_FORMAT_XRGB2101010 fourcc_code('X', 'R', '3', '0') /* [31:0] x:R:G:B 2:10:10:10 little endian */ -#define DRM_FORMAT_XBGR2101010 fourcc_code('X', 'B', '3', '0') /* [31:0] x:B:G:R 2:10:10:10 little endian */ -#define DRM_FORMAT_RGBX1010102 fourcc_code('R', 'X', '3', '0') /* [31:0] R:G:B:x 10:10:10:2 little endian */ -#define DRM_FORMAT_BGRX1010102 fourcc_code('B', 'X', '3', '0') /* [31:0] B:G:R:x 10:10:10:2 little endian */ - -#define DRM_FORMAT_ARGB2101010 fourcc_code('A', 'R', '3', '0') /* [31:0] A:R:G:B 2:10:10:10 little endian */ -#define DRM_FORMAT_ABGR2101010 fourcc_code('A', 'B', '3', '0') /* [31:0] A:B:G:R 2:10:10:10 little endian */ -#define DRM_FORMAT_RGBA1010102 fourcc_code('R', 'A', '3', '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */ -#define DRM_FORMAT_BGRA1010102 fourcc_code('B', 'A', '3', '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */ - -/* 64 bpp RGB */ -#define DRM_FORMAT_XRGB16161616 fourcc_code('X', 'R', '4', '8') /* [63:0] x:R:G:B 16:16:16:16 little endian */ -#define DRM_FORMAT_XBGR16161616 fourcc_code('X', 'B', '4', '8') /* [63:0] x:B:G:R 16:16:16:16 little endian */ - -#define DRM_FORMAT_ARGB16161616 fourcc_code('A', 'R', '4', '8') /* [63:0] A:R:G:B 16:16:16:16 little endian */ -#define DRM_FORMAT_ABGR16161616 fourcc_code('A', 'B', '4', '8') /* [63:0] A:B:G:R 16:16:16:16 little endian */ - -/* - * Floating point 64bpp RGB - * IEEE 754-2008 binary16 half-precision float - * [15:0] sign:exponent:mantissa 1:5:10 - */ -#define DRM_FORMAT_XRGB16161616F fourcc_code('X', 'R', '4', 'H') /* [63:0] x:R:G:B 16:16:16:16 little endian */ -#define DRM_FORMAT_XBGR16161616F fourcc_code('X', 'B', '4', 'H') /* [63:0] x:B:G:R 16:16:16:16 little endian */ - -#define DRM_FORMAT_ARGB16161616F fourcc_code('A', 'R', '4', 'H') /* [63:0] A:R:G:B 16:16:16:16 little endian */ -#define DRM_FORMAT_ABGR16161616F fourcc_code('A', 'B', '4', 'H') /* [63:0] A:B:G:R 16:16:16:16 little endian */ - -/* - * RGBA format with 10-bit components packed in 64-bit per pixel, with 6 bits - * of unused padding per component: - */ -#define DRM_FORMAT_AXBXGXRX106106106106 fourcc_code('A', 'B', '1', '0') /* [63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian */ - -/* packed YCbCr */ -#define DRM_FORMAT_YUYV fourcc_code('Y', 'U', 'Y', 'V') /* [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian */ -#define DRM_FORMAT_YVYU fourcc_code('Y', 'V', 'Y', 'U') /* [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian */ -#define DRM_FORMAT_UYVY fourcc_code('U', 'Y', 'V', 'Y') /* [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian */ -#define DRM_FORMAT_VYUY fourcc_code('V', 'Y', 'U', 'Y') /* [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian */ - -#define DRM_FORMAT_AYUV fourcc_code('A', 'Y', 'U', 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */ -#define DRM_FORMAT_AVUY8888 fourcc_code('A', 'V', 'U', 'Y') /* [31:0] A:Cr:Cb:Y 8:8:8:8 little endian */ -#define DRM_FORMAT_XYUV8888 fourcc_code('X', 'Y', 'U', 'V') /* [31:0] X:Y:Cb:Cr 8:8:8:8 little endian */ -#define DRM_FORMAT_XVUY8888 fourcc_code('X', 'V', 'U', 'Y') /* [31:0] X:Cr:Cb:Y 8:8:8:8 little endian */ -#define DRM_FORMAT_VUY888 fourcc_code('V', 'U', '2', '4') /* [23:0] Cr:Cb:Y 8:8:8 little endian */ -#define DRM_FORMAT_VUY101010 fourcc_code('V', 'U', '3', '0') /* Y followed by U then V, 10:10:10. Non-linear modifier only */ - -/* - * packed Y2xx indicate for each component, xx valid data occupy msb - * 16-xx padding occupy lsb - */ -#define DRM_FORMAT_Y210 fourcc_code('Y', '2', '1', '0') /* [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels */ -#define DRM_FORMAT_Y212 fourcc_code('Y', '2', '1', '2') /* [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels */ -#define DRM_FORMAT_Y216 fourcc_code('Y', '2', '1', '6') /* [63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels */ - -/* - * packed Y4xx indicate for each component, xx valid data occupy msb - * 16-xx padding occupy lsb except Y410 - */ -#define DRM_FORMAT_Y410 fourcc_code('Y', '4', '1', '0') /* [31:0] A:Cr:Y:Cb 2:10:10:10 little endian */ -#define DRM_FORMAT_Y412 fourcc_code('Y', '4', '1', '2') /* [63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian */ -#define DRM_FORMAT_Y416 fourcc_code('Y', '4', '1', '6') /* [63:0] A:Cr:Y:Cb 16:16:16:16 little endian */ - -#define DRM_FORMAT_XVYU2101010 fourcc_code('X', 'V', '3', '0') /* [31:0] X:Cr:Y:Cb 2:10:10:10 little endian */ -#define DRM_FORMAT_XVYU12_16161616 fourcc_code('X', 'V', '3', '6') /* [63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian */ -#define DRM_FORMAT_XVYU16161616 fourcc_code('X', 'V', '4', '8') /* [63:0] X:Cr:Y:Cb 16:16:16:16 little endian */ - -/* - * packed YCbCr420 2x2 tiled formats - * first 64 bits will contain Y,Cb,Cr components for a 2x2 tile - */ -/* [63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian */ -#define DRM_FORMAT_Y0L0 fourcc_code('Y', '0', 'L', '0') -/* [63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian */ -#define DRM_FORMAT_X0L0 fourcc_code('X', '0', 'L', '0') - -/* [63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian */ -#define DRM_FORMAT_Y0L2 fourcc_code('Y', '0', 'L', '2') -/* [63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian */ -#define DRM_FORMAT_X0L2 fourcc_code('X', '0', 'L', '2') - -/* - * 1-plane YUV 4:2:0 - * In these formats, the component ordering is specified (Y, followed by U - * then V), but the exact Linear layout is undefined. - * These formats can only be used with a non-Linear modifier. - */ -#define DRM_FORMAT_YUV420_8BIT fourcc_code('Y', 'U', '0', '8') -#define DRM_FORMAT_YUV420_10BIT fourcc_code('Y', 'U', '1', '0') - -/* - * 2 plane RGB + A - * index 0 = RGB plane, same format as the corresponding non _A8 format has - * index 1 = A plane, [7:0] A - */ -#define DRM_FORMAT_XRGB8888_A8 fourcc_code('X', 'R', 'A', '8') -#define DRM_FORMAT_XBGR8888_A8 fourcc_code('X', 'B', 'A', '8') -#define DRM_FORMAT_RGBX8888_A8 fourcc_code('R', 'X', 'A', '8') -#define DRM_FORMAT_BGRX8888_A8 fourcc_code('B', 'X', 'A', '8') -#define DRM_FORMAT_RGB888_A8 fourcc_code('R', '8', 'A', '8') -#define DRM_FORMAT_BGR888_A8 fourcc_code('B', '8', 'A', '8') -#define DRM_FORMAT_RGB565_A8 fourcc_code('R', '5', 'A', '8') -#define DRM_FORMAT_BGR565_A8 fourcc_code('B', '5', 'A', '8') - -/* - * 2 plane YCbCr - * index 0 = Y plane, [7:0] Y - * index 1 = Cr:Cb plane, [15:0] Cr:Cb little endian - * or - * index 1 = Cb:Cr plane, [15:0] Cb:Cr little endian - */ -#define DRM_FORMAT_NV12 fourcc_code('N', 'V', '1', '2') /* 2x2 subsampled Cr:Cb plane */ -#define DRM_FORMAT_NV21 fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */ -#define DRM_FORMAT_NV16 fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */ -#define DRM_FORMAT_NV61 fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */ -#define DRM_FORMAT_NV24 fourcc_code('N', 'V', '2', '4') /* non-subsampled Cr:Cb plane */ -#define DRM_FORMAT_NV42 fourcc_code('N', 'V', '4', '2') /* non-subsampled Cb:Cr plane */ -/* - * 2 plane YCbCr - * index 0 = Y plane, [39:0] Y3:Y2:Y1:Y0 little endian - * index 1 = Cr:Cb plane, [39:0] Cr1:Cb1:Cr0:Cb0 little endian - */ -#define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5') /* 2x2 subsampled Cr:Cb plane */ -#define DRM_FORMAT_NV20 fourcc_code('N', 'V', '2', '0') /* 2x1 subsampled Cr:Cb plane */ -#define DRM_FORMAT_NV30 fourcc_code('N', 'V', '3', '0') /* non-subsampled Cr:Cb plane */ - -/* - * 2 plane YCbCr MSB aligned - * index 0 = Y plane, [15:0] Y:x [10:6] little endian - * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [10:6:10:6] little endian - */ -#define DRM_FORMAT_P210 fourcc_code('P', '2', '1', '0') /* 2x1 subsampled Cr:Cb plane, 10 bit per channel */ - -/* - * 2 plane YCbCr MSB aligned - * index 0 = Y plane, [15:0] Y:x [10:6] little endian - * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [10:6:10:6] little endian - */ -#define DRM_FORMAT_P010 fourcc_code('P', '0', '1', '0') /* 2x2 subsampled Cr:Cb plane 10 bits per channel */ - -/* - * 2 plane YCbCr MSB aligned - * index 0 = Y plane, [15:0] Y:x [12:4] little endian - * index 1 = Cr:Cb plane, [31:0] Cr:x:Cb:x [12:4:12:4] little endian - */ -#define DRM_FORMAT_P012 fourcc_code('P', '0', '1', '2') /* 2x2 subsampled Cr:Cb plane 12 bits per channel */ - -/* - * 2 plane YCbCr MSB aligned - * index 0 = Y plane, [15:0] Y little endian - * index 1 = Cr:Cb plane, [31:0] Cr:Cb [16:16] little endian - */ -#define DRM_FORMAT_P016 fourcc_code('P', '0', '1', '6') /* 2x2 subsampled Cr:Cb plane 16 bits per channel */ - -/* 2 plane YCbCr420. - * 3 10 bit components and 2 padding bits packed into 4 bytes. - * index 0 = Y plane, [31:0] x:Y2:Y1:Y0 2:10:10:10 little endian - * index 1 = Cr:Cb plane, [63:0] x:Cr2:Cb2:Cr1:x:Cb1:Cr0:Cb0 [2:10:10:10:2:10:10:10] little endian - */ -#define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') /* 2x2 subsampled Cr:Cb plane 10 bits per channel packed */ - -/* 3 plane non-subsampled (444) YCbCr - * 16 bits per component, but only 10 bits are used and 6 bits are padded - * index 0: Y plane, [15:0] Y:x [10:6] little endian - * index 1: Cb plane, [15:0] Cb:x [10:6] little endian - * index 2: Cr plane, [15:0] Cr:x [10:6] little endian - */ -#define DRM_FORMAT_Q410 fourcc_code('Q', '4', '1', '0') - -/* 3 plane non-subsampled (444) YCrCb - * 16 bits per component, but only 10 bits are used and 6 bits are padded - * index 0: Y plane, [15:0] Y:x [10:6] little endian - * index 1: Cr plane, [15:0] Cr:x [10:6] little endian - * index 2: Cb plane, [15:0] Cb:x [10:6] little endian - */ -#define DRM_FORMAT_Q401 fourcc_code('Q', '4', '0', '1') - -/* - * 3 plane YCbCr - * index 0: Y plane, [7:0] Y - * index 1: Cb plane, [7:0] Cb - * index 2: Cr plane, [7:0] Cr - * or - * index 1: Cr plane, [7:0] Cr - * index 2: Cb plane, [7:0] Cb - */ -#define DRM_FORMAT_YUV410 fourcc_code('Y', 'U', 'V', '9') /* 4x4 subsampled Cb (1) and Cr (2) planes */ -#define DRM_FORMAT_YVU410 fourcc_code('Y', 'V', 'U', '9') /* 4x4 subsampled Cr (1) and Cb (2) planes */ -#define DRM_FORMAT_YUV411 fourcc_code('Y', 'U', '1', '1') /* 4x1 subsampled Cb (1) and Cr (2) planes */ -#define DRM_FORMAT_YVU411 fourcc_code('Y', 'V', '1', '1') /* 4x1 subsampled Cr (1) and Cb (2) planes */ -#define DRM_FORMAT_YUV420 fourcc_code('Y', 'U', '1', '2') /* 2x2 subsampled Cb (1) and Cr (2) planes */ -#define DRM_FORMAT_YVU420 fourcc_code('Y', 'V', '1', '2') /* 2x2 subsampled Cr (1) and Cb (2) planes */ -#define DRM_FORMAT_YUV422 fourcc_code('Y', 'U', '1', '6') /* 2x1 subsampled Cb (1) and Cr (2) planes */ -#define DRM_FORMAT_YVU422 fourcc_code('Y', 'V', '1', '6') /* 2x1 subsampled Cr (1) and Cb (2) planes */ -#define DRM_FORMAT_YUV444 fourcc_code('Y', 'U', '2', '4') /* non-subsampled Cb (1) and Cr (2) planes */ -#define DRM_FORMAT_YVU444 fourcc_code('Y', 'V', '2', '4') /* non-subsampled Cr (1) and Cb (2) planes */ - - -/* - * Format Modifiers: - * - * Format modifiers describe, typically, a re-ordering or modification - * of the data in a plane of an FB. This can be used to express tiled/ - * swizzled formats, or compression, or a combination of the two. - * - * The upper 8 bits of the format modifier are a vendor-id as assigned - * below. The lower 56 bits are assigned as vendor sees fit. - */ - -/* Vendor Ids: */ -#define DRM_FORMAT_MOD_VENDOR_NONE 0 -#define DRM_FORMAT_MOD_VENDOR_INTEL 0x01 -#define DRM_FORMAT_MOD_VENDOR_AMD 0x02 -#define DRM_FORMAT_MOD_VENDOR_NVIDIA 0x03 -#define DRM_FORMAT_MOD_VENDOR_SAMSUNG 0x04 -#define DRM_FORMAT_MOD_VENDOR_QCOM 0x05 -#define DRM_FORMAT_MOD_VENDOR_VIVANTE 0x06 -#define DRM_FORMAT_MOD_VENDOR_BROADCOM 0x07 -#define DRM_FORMAT_MOD_VENDOR_ARM 0x08 -#define DRM_FORMAT_MOD_VENDOR_ALLWINNER 0x09 -#define DRM_FORMAT_MOD_VENDOR_AMLOGIC 0x0a - -/* add more to the end as needed */ - -#define DRM_FORMAT_RESERVED ((1ULL << 56) - 1) - -#define fourcc_mod_get_vendor(modifier) \ - (((modifier) >> 56) & 0xff) - -#define fourcc_mod_is_vendor(modifier, vendor) \ - (fourcc_mod_get_vendor(modifier) == DRM_FORMAT_MOD_VENDOR_## vendor) - -#define fourcc_mod_code(vendor, val) \ - ((((__u64)DRM_FORMAT_MOD_VENDOR_## vendor) << 56) | ((val) & 0x00ffffffffffffffULL)) - -/* - * Format Modifier tokens: - * - * When adding a new token please document the layout with a code comment, - * similar to the fourcc codes above. drm_fourcc.h is considered the - * authoritative source for all of these. - * - * Generic modifier names: - * - * DRM_FORMAT_MOD_GENERIC_* definitions are used to provide vendor-neutral names - * for layouts which are common across multiple vendors. To preserve - * compatibility, in cases where a vendor-specific definition already exists and - * a generic name for it is desired, the common name is a purely symbolic alias - * and must use the same numerical value as the original definition. - * - * Note that generic names should only be used for modifiers which describe - * generic layouts (such as pixel re-ordering), which may have - * independently-developed support across multiple vendors. - * - * In future cases where a generic layout is identified before merging with a - * vendor-specific modifier, a new 'GENERIC' vendor or modifier using vendor - * 'NONE' could be considered. This should only be for obvious, exceptional - * cases to avoid polluting the 'GENERIC' namespace with modifiers which only - * apply to a single vendor. - * - * Generic names should not be used for cases where multiple hardware vendors - * have implementations of the same standardised compression scheme (such as - * AFBC). In those cases, all implementations should use the same format - * modifier(s), reflecting the vendor of the standard. - */ - -#define DRM_FORMAT_MOD_GENERIC_16_16_TILE DRM_FORMAT_MOD_SAMSUNG_16_16_TILE - -/* - * Invalid Modifier - * - * This modifier can be used as a sentinel to terminate the format modifiers - * list, or to initialize a variable with an invalid modifier. It might also be - * used to report an error back to userspace for certain APIs. - */ -#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(NONE, DRM_FORMAT_RESERVED) - -/* - * Linear Layout - * - * Just plain linear layout. Note that this is different from no specifying any - * modifier (e.g. not setting DRM_MODE_FB_MODIFIERS in the DRM_ADDFB2 ioctl), - * which tells the driver to also take driver-internal information into account - * and so might actually result in a tiled framebuffer. - */ -#define DRM_FORMAT_MOD_LINEAR fourcc_mod_code(NONE, 0) - -/* - * Deprecated: use DRM_FORMAT_MOD_LINEAR instead - * - * The "none" format modifier doesn't actually mean that the modifier is - * implicit, instead it means that the layout is linear. Whether modifiers are - * used is out-of-band information carried in an API-specific way (e.g. in a - * flag for drm_mode_fb_cmd2). - */ -#define DRM_FORMAT_MOD_NONE 0 - -/* Intel framebuffer modifiers */ - -/* - * Intel X-tiling layout - * - * This is a tiled layout using 4Kb tiles (except on gen2 where the tiles 2Kb) - * in row-major layout. Within the tile bytes are laid out row-major, with - * a platform-dependent stride. On top of that the memory can apply - * platform-depending swizzling of some higher address bits into bit6. - * - * Note that this layout is only accurate on intel gen 8+ or valleyview chipsets. - * On earlier platforms the is highly platforms specific and not useful for - * cross-driver sharing. It exists since on a given platform it does uniquely - * identify the layout in a simple way for i915-specific userspace, which - * facilitated conversion of userspace to modifiers. Additionally the exact - * format on some really old platforms is not known. - */ -#define I915_FORMAT_MOD_X_TILED fourcc_mod_code(INTEL, 1) - -/* - * Intel Y-tiling layout - * - * This is a tiled layout using 4Kb tiles (except on gen2 where the tiles 2Kb) - * in row-major layout. Within the tile bytes are laid out in OWORD (16 bytes) - * chunks column-major, with a platform-dependent height. On top of that the - * memory can apply platform-depending swizzling of some higher address bits - * into bit6. - * - * Note that this layout is only accurate on intel gen 8+ or valleyview chipsets. - * On earlier platforms the is highly platforms specific and not useful for - * cross-driver sharing. It exists since on a given platform it does uniquely - * identify the layout in a simple way for i915-specific userspace, which - * facilitated conversion of userspace to modifiers. Additionally the exact - * format on some really old platforms is not known. - */ -#define I915_FORMAT_MOD_Y_TILED fourcc_mod_code(INTEL, 2) - -/* - * Intel Yf-tiling layout - * - * This is a tiled layout using 4Kb tiles in row-major layout. - * Within the tile pixels are laid out in 16 256 byte units / sub-tiles which - * are arranged in four groups (two wide, two high) with column-major layout. - * Each group therefore consists out of four 256 byte units, which are also laid - * out as 2x2 column-major. - * 256 byte units are made out of four 64 byte blocks of pixels, producing - * either a square block or a 2:1 unit. - * 64 byte blocks of pixels contain four pixel rows of 16 bytes, where the width - * in pixel depends on the pixel depth. - */ -#define I915_FORMAT_MOD_Yf_TILED fourcc_mod_code(INTEL, 3) - -/* - * Intel color control surface (CCS) for render compression - * - * The framebuffer format must be one of the 8:8:8:8 RGB formats. - * The main surface will be plane index 0 and must be Y/Yf-tiled, - * the CCS will be plane index 1. - * - * Each CCS tile matches a 1024x512 pixel area of the main surface. - * To match certain aspects of the 3D hardware the CCS is - * considered to be made up of normal 128Bx32 Y tiles, Thus - * the CCS pitch must be specified in multiples of 128 bytes. - * - * In reality the CCS tile appears to be a 64Bx64 Y tile, composed - * of QWORD (8 bytes) chunks instead of OWORD (16 bytes) chunks. - * But that fact is not relevant unless the memory is accessed - * directly. - */ -#define I915_FORMAT_MOD_Y_TILED_CCS fourcc_mod_code(INTEL, 4) -#define I915_FORMAT_MOD_Yf_TILED_CCS fourcc_mod_code(INTEL, 5) - -/* - * Intel color control surfaces (CCS) for Gen-12 render compression. - * - * The main surface is Y-tiled and at plane index 0, the CCS is linear and - * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in - * main surface. In other words, 4 bits in CCS map to a main surface cache - * line pair. The main surface pitch is required to be a multiple of four - * Y-tile widths. - */ -#define I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS fourcc_mod_code(INTEL, 6) - -/* - * Intel color control surfaces (CCS) for Gen-12 media compression - * - * The main surface is Y-tiled and at plane index 0, the CCS is linear and - * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in - * main surface. In other words, 4 bits in CCS map to a main surface cache - * line pair. The main surface pitch is required to be a multiple of four - * Y-tile widths. For semi-planar formats like NV12, CCS planes follow the - * Y and UV planes i.e., planes 0 and 1 are used for Y and UV surfaces, - * planes 2 and 3 for the respective CCS. - */ -#define I915_FORMAT_MOD_Y_TILED_GEN12_MC_CCS fourcc_mod_code(INTEL, 7) - -/* - * Intel Color Control Surface with Clear Color (CCS) for Gen-12 render - * compression. - * - * The main surface is Y-tiled and is at plane index 0 whereas CCS is linear - * and at index 1. The clear color is stored at index 2, and the pitch should - * be 64 bytes aligned. The clear color structure is 256 bits. The first 128 bits - * represents Raw Clear Color Red, Green, Blue and Alpha color each represented - * by 32 bits. The raw clear color is consumed by the 3d engine and generates - * the converted clear color of size 64 bits. The first 32 bits store the Lower - * Converted Clear Color value and the next 32 bits store the Higher Converted - * Clear Color value when applicable. The Converted Clear Color values are - * consumed by the DE. The last 64 bits are used to store Color Discard Enable - * and Depth Clear Value Valid which are ignored by the DE. A CCS cache line - * corresponds to an area of 4x1 tiles in the main surface. The main surface - * pitch is required to be a multiple of 4 tile widths. - */ -#define I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC fourcc_mod_code(INTEL, 8) - -/* - * Intel Tile 4 layout - * - * This is a tiled layout using 4KB tiles in a row-major layout. It has the same - * shape as Tile Y at two granularities: 4KB (128B x 32) and 64B (16B x 4). It - * only differs from Tile Y at the 256B granularity in between. At this - * granularity, Tile Y has a shape of 16B x 32 rows, but this tiling has a shape - * of 64B x 8 rows. - */ -#define I915_FORMAT_MOD_4_TILED fourcc_mod_code(INTEL, 9) - -/* - * Intel color control surfaces (CCS) for DG2 render compression. - * - * The main surface is Tile 4 and at plane index 0. The CCS data is stored - * outside of the GEM object in a reserved memory area dedicated for the - * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The - * main surface pitch is required to be a multiple of four Tile 4 widths. - */ -#define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS fourcc_mod_code(INTEL, 10) - -/* - * Intel color control surfaces (CCS) for DG2 media compression. - * - * The main surface is Tile 4 and at plane index 0. For semi-planar formats - * like NV12, the Y and UV planes are Tile 4 and are located at plane indices - * 0 and 1, respectively. The CCS for all planes are stored outside of the - * GEM object in a reserved memory area dedicated for the storage of the - * CCS data for all RC/RC_CC/MC compressible GEM objects. The main surface - * pitch is required to be a multiple of four Tile 4 widths. - */ -#define I915_FORMAT_MOD_4_TILED_DG2_MC_CCS fourcc_mod_code(INTEL, 11) - -/* - * Intel Color Control Surface with Clear Color (CCS) for DG2 render compression. - * - * The main surface is Tile 4 and at plane index 0. The CCS data is stored - * outside of the GEM object in a reserved memory area dedicated for the - * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The - * main surface pitch is required to be a multiple of four Tile 4 widths. The - * clear color is stored at plane index 1 and the pitch should be 64 bytes - * aligned. The format of the 256 bits of clear color data matches the one used - * for the I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description - * for details. - */ -#define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC fourcc_mod_code(INTEL, 12) - -/* - * Intel Color Control Surfaces (CCS) for display ver. 14 render compression. - * - * The main surface is tile4 and at plane index 0, the CCS is linear and - * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in - * main surface. In other words, 4 bits in CCS map to a main surface cache - * line pair. The main surface pitch is required to be a multiple of four - * tile4 widths. - */ -#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS fourcc_mod_code(INTEL, 13) - -/* - * Intel Color Control Surfaces (CCS) for display ver. 14 media compression - * - * The main surface is tile4 and at plane index 0, the CCS is linear and - * at index 1. A 64B CCS cache line corresponds to an area of 4x1 tiles in - * main surface. In other words, 4 bits in CCS map to a main surface cache - * line pair. The main surface pitch is required to be a multiple of four - * tile4 widths. For semi-planar formats like NV12, CCS planes follow the - * Y and UV planes i.e., planes 0 and 1 are used for Y and UV surfaces, - * planes 2 and 3 for the respective CCS. - */ -#define I915_FORMAT_MOD_4_TILED_MTL_MC_CCS fourcc_mod_code(INTEL, 14) - -/* - * Intel Color Control Surface with Clear Color (CCS) for display ver. 14 render - * compression. - * - * The main surface is tile4 and is at plane index 0 whereas CCS is linear - * and at index 1. The clear color is stored at index 2, and the pitch should - * be ignored. The clear color structure is 256 bits. The first 128 bits - * represents Raw Clear Color Red, Green, Blue and Alpha color each represented - * by 32 bits. The raw clear color is consumed by the 3d engine and generates - * the converted clear color of size 64 bits. The first 32 bits store the Lower - * Converted Clear Color value and the next 32 bits store the Higher Converted - * Clear Color value when applicable. The Converted Clear Color values are - * consumed by the DE. The last 64 bits are used to store Color Discard Enable - * and Depth Clear Value Valid which are ignored by the DE. A CCS cache line - * corresponds to an area of 4x1 tiles in the main surface. The main surface - * pitch is required to be a multiple of 4 tile widths. - */ -#define I915_FORMAT_MOD_4_TILED_MTL_RC_CCS_CC fourcc_mod_code(INTEL, 15) - -/* - * Intel Color Control Surfaces (CCS) for graphics ver. 20 unified compression - * on integrated graphics - * - * The main surface is Tile 4 and at plane index 0. For semi-planar formats - * like NV12, the Y and UV planes are Tile 4 and are located at plane indices - * 0 and 1, respectively. The CCS for all planes are stored outside of the - * GEM object in a reserved memory area dedicated for the storage of the - * CCS data for all compressible GEM objects. - */ -#define I915_FORMAT_MOD_4_TILED_LNL_CCS fourcc_mod_code(INTEL, 16) - -/* - * Intel Color Control Surfaces (CCS) for graphics ver. 20 unified compression - * on discrete graphics - * - * The main surface is Tile 4 and at plane index 0. For semi-planar formats - * like NV12, the Y and UV planes are Tile 4 and are located at plane indices - * 0 and 1, respectively. The CCS for all planes are stored outside of the - * GEM object in a reserved memory area dedicated for the storage of the - * CCS data for all compressible GEM objects. The GEM object must be stored in - * contiguous memory with a size aligned to 64KB - */ -#define I915_FORMAT_MOD_4_TILED_BMG_CCS fourcc_mod_code(INTEL, 17) - -/* - * Tiled, NV12MT, grouped in 64 (pixels) x 32 (lines) -sized macroblocks - * - * Macroblocks are laid in a Z-shape, and each pixel data is following the - * standard NV12 style. - * As for NV12, an image is the result of two frame buffers: one for Y, - * one for the interleaved Cb/Cr components (1/2 the height of the Y buffer). - * Alignment requirements are (for each buffer): - * - multiple of 128 pixels for the width - * - multiple of 32 pixels for the height - * - * For more information: see https://linuxtv.org/downloads/v4l-dvb-apis/re32.html - */ -#define DRM_FORMAT_MOD_SAMSUNG_64_32_TILE fourcc_mod_code(SAMSUNG, 1) - -/* - * Tiled, 16 (pixels) x 16 (lines) - sized macroblocks - * - * This is a simple tiled layout using tiles of 16x16 pixels in a row-major - * layout. For YCbCr formats Cb/Cr components are taken in such a way that - * they correspond to their 16x16 luma block. - */ -#define DRM_FORMAT_MOD_SAMSUNG_16_16_TILE fourcc_mod_code(SAMSUNG, 2) - -/* - * Qualcomm Compressed Format - * - * Refers to a compressed variant of the base format that is compressed. - * Implementation may be platform and base-format specific. - * - * Each macrotile consists of m x n (mostly 4 x 4) tiles. - * Pixel data pitch/stride is aligned with macrotile width. - * Pixel data height is aligned with macrotile height. - * Entire pixel data buffer is aligned with 4k(bytes). - */ -#define DRM_FORMAT_MOD_QCOM_COMPRESSED fourcc_mod_code(QCOM, 1) - -/* - * Qualcomm Tiled Format - * - * Similar to DRM_FORMAT_MOD_QCOM_COMPRESSED but not compressed. - * Implementation may be platform and base-format specific. - * - * Each macrotile consists of m x n (mostly 4 x 4) tiles. - * Pixel data pitch/stride is aligned with macrotile width. - * Pixel data height is aligned with macrotile height. - * Entire pixel data buffer is aligned with 4k(bytes). - */ -#define DRM_FORMAT_MOD_QCOM_TILED3 fourcc_mod_code(QCOM, 3) - -/* - * Qualcomm Alternate Tiled Format - * - * Alternate tiled format typically only used within GMEM. - * Implementation may be platform and base-format specific. - */ -#define DRM_FORMAT_MOD_QCOM_TILED2 fourcc_mod_code(QCOM, 2) - - -/* Vivante framebuffer modifiers */ - -/* - * Vivante 4x4 tiling layout - * - * This is a simple tiled layout using tiles of 4x4 pixels in a row-major - * layout. - */ -#define DRM_FORMAT_MOD_VIVANTE_TILED fourcc_mod_code(VIVANTE, 1) - -/* - * Vivante 64x64 super-tiling layout - * - * This is a tiled layout using 64x64 pixel super-tiles, where each super-tile - * contains 8x4 groups of 2x4 tiles of 4x4 pixels (like above) each, all in row- - * major layout. - * - * For more information: see - * https://github.com/etnaviv/etna_viv/blob/master/doc/hardware.md#texture-tiling - */ -#define DRM_FORMAT_MOD_VIVANTE_SUPER_TILED fourcc_mod_code(VIVANTE, 2) - -/* - * Vivante 4x4 tiling layout for dual-pipe - * - * Same as the 4x4 tiling layout, except every second 4x4 pixel tile starts at a - * different base address. Offsets from the base addresses are therefore halved - * compared to the non-split tiled layout. - */ -#define DRM_FORMAT_MOD_VIVANTE_SPLIT_TILED fourcc_mod_code(VIVANTE, 3) - -/* - * Vivante 64x64 super-tiling layout for dual-pipe - * - * Same as the 64x64 super-tiling layout, except every second 4x4 pixel tile - * starts at a different base address. Offsets from the base addresses are - * therefore halved compared to the non-split super-tiled layout. - */ -#define DRM_FORMAT_MOD_VIVANTE_SPLIT_SUPER_TILED fourcc_mod_code(VIVANTE, 4) - -/* - * Vivante TS (tile-status) buffer modifiers. They can be combined with all of - * the color buffer tiling modifiers defined above. When TS is present it's a - * separate buffer containing the clear/compression status of each tile. The - * modifiers are defined as VIVANTE_MOD_TS_c_s, where c is the color buffer - * tile size in bytes covered by one entry in the status buffer and s is the - * number of status bits per entry. - * We reserve the top 8 bits of the Vivante modifier space for tile status - * clear/compression modifiers, as future cores might add some more TS layout - * variations. - */ -#define VIVANTE_MOD_TS_64_4 (1ULL << 48) -#define VIVANTE_MOD_TS_64_2 (2ULL << 48) -#define VIVANTE_MOD_TS_128_4 (3ULL << 48) -#define VIVANTE_MOD_TS_256_4 (4ULL << 48) -#define VIVANTE_MOD_TS_MASK (0xfULL << 48) - -/* - * Vivante compression modifiers. Those depend on a TS modifier being present - * as the TS bits get reinterpreted as compression tags instead of simple - * clear markers when compression is enabled. - */ -#define VIVANTE_MOD_COMP_DEC400 (1ULL << 52) -#define VIVANTE_MOD_COMP_MASK (0xfULL << 52) - -/* Masking out the extension bits will yield the base modifier. */ -#define VIVANTE_MOD_EXT_MASK (VIVANTE_MOD_TS_MASK | \ - VIVANTE_MOD_COMP_MASK) - -/* NVIDIA frame buffer modifiers */ - -/* - * Tegra Tiled Layout, used by Tegra 2, 3 and 4. - * - * Pixels are arranged in simple tiles of 16 x 16 bytes. - */ -#define DRM_FORMAT_MOD_NVIDIA_TEGRA_TILED fourcc_mod_code(NVIDIA, 1) - -/* - * Generalized Block Linear layout, used by desktop GPUs starting with NV50/G80, - * and Tegra GPUs starting with Tegra K1. - * - * Pixels are arranged in Groups of Bytes (GOBs). GOB size and layout varies - * based on the architecture generation. GOBs themselves are then arranged in - * 3D blocks, with the block dimensions (in terms of GOBs) always being a power - * of two, and hence expressible as their log2 equivalent (E.g., "2" represents - * a block depth or height of "4"). - * - * Chapter 20 "Pixel Memory Formats" of the Tegra X1 TRM describes this format - * in full detail. - * - * Macro - * Bits Param Description - * ---- ----- ----------------------------------------------------------------- - * - * 3:0 h log2(height) of each block, in GOBs. Placed here for - * compatibility with the existing - * DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK()-based modifiers. - * - * 4:4 - Must be 1, to indicate block-linear layout. Necessary for - * compatibility with the existing - * DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK()-based modifiers. - * - * 8:5 - Reserved (To support 3D-surfaces with variable log2(depth) block - * size). Must be zero. - * - * Note there is no log2(width) parameter. Some portions of the - * hardware support a block width of two gobs, but it is impractical - * to use due to lack of support elsewhere, and has no known - * benefits. - * - * 11:9 - Reserved (To support 2D-array textures with variable array stride - * in blocks, specified via log2(tile width in blocks)). Must be - * zero. - * - * 19:12 k Page Kind. This value directly maps to a field in the page - * tables of all GPUs >= NV50. It affects the exact layout of bits - * in memory and can be derived from the tuple - * - * (format, GPU model, compression type, samples per pixel) - * - * Where compression type is defined below. If GPU model were - * implied by the format modifier, format, or memory buffer, page - * kind would not need to be included in the modifier itself, but - * since the modifier should define the layout of the associated - * memory buffer independent from any device or other context, it - * must be included here. - * - * 21:20 g GOB Height and Page Kind Generation. The height of a GOB changed - * starting with Fermi GPUs. Additionally, the mapping between page - * kind and bit layout has changed at various points. - * - * 0 = Gob Height 8, Fermi - Volta, Tegra K1+ Page Kind mapping - * 1 = Gob Height 4, G80 - GT2XX Page Kind mapping - * 2 = Gob Height 8, Turing+ Page Kind mapping - * 3 = Reserved for future use. - * - * 22:22 s Sector layout. On Tegra GPUs prior to Xavier, there is a further - * bit remapping step that occurs at an even lower level than the - * page kind and block linear swizzles. This causes the layout of - * surfaces mapped in those SOC's GPUs to be incompatible with the - * equivalent mapping on other GPUs in the same system. - * - * 0 = Tegra K1 - Tegra Parker/TX2 Layout. - * 1 = Desktop GPU and Tegra Xavier+ Layout - * - * 25:23 c Lossless Framebuffer Compression type. - * - * 0 = none - * 1 = ROP/3D, layout 1, exact compression format implied by Page - * Kind field - * 2 = ROP/3D, layout 2, exact compression format implied by Page - * Kind field - * 3 = CDE horizontal - * 4 = CDE vertical - * 5 = Reserved for future use - * 6 = Reserved for future use - * 7 = Reserved for future use - * - * 55:25 - Reserved for future use. Must be zero. - */ -#define DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(c, s, g, k, h) \ - fourcc_mod_code(NVIDIA, (0x10 | \ - ((h) & 0xf) | \ - (((k) & 0xff) << 12) | \ - (((g) & 0x3) << 20) | \ - (((s) & 0x1) << 22) | \ - (((c) & 0x7) << 23))) - -/* To grandfather in prior block linear format modifiers to the above layout, - * the page kind "0", which corresponds to "pitch/linear" and hence is unusable - * with block-linear layouts, is remapped within drivers to the value 0xfe, - * which corresponds to the "generic" kind used for simple single-sample - * uncompressed color formats on Fermi - Volta GPUs. - */ -static inline __u64 -drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier) -{ - if (!(modifier & 0x10) || (modifier & (0xff << 12))) - return modifier; - else - return modifier | (0xfe << 12); -} - -/* - * 16Bx2 Block Linear layout, used by Tegra K1 and later - * - * Pixels are arranged in 64x8 Groups Of Bytes (GOBs). GOBs are then stacked - * vertically by a power of 2 (1 to 32 GOBs) to form a block. - * - * Within a GOB, data is ordered as 16B x 2 lines sectors laid in Z-shape. - * - * Parameter 'v' is the log2 encoding of the number of GOBs stacked vertically. - * Valid values are: - * - * 0 == ONE_GOB - * 1 == TWO_GOBS - * 2 == FOUR_GOBS - * 3 == EIGHT_GOBS - * 4 == SIXTEEN_GOBS - * 5 == THIRTYTWO_GOBS - * - * Chapter 20 "Pixel Memory Formats" of the Tegra X1 TRM describes this format - * in full detail. - */ -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(v) \ - DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D(0, 0, 0, 0, (v)) - -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_ONE_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(0) -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_TWO_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(1) -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_FOUR_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(2) -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_EIGHT_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(3) -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_SIXTEEN_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(4) -#define DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK_THIRTYTWO_GOB \ - DRM_FORMAT_MOD_NVIDIA_16BX2_BLOCK(5) - -/* - * Some Broadcom modifiers take parameters, for example the number of - * vertical lines in the image. Reserve the lower 32 bits for modifier - * type, and the next 24 bits for parameters. Top 8 bits are the - * vendor code. - */ -#define __fourcc_mod_broadcom_param_shift 8 -#define __fourcc_mod_broadcom_param_bits 48 -#define fourcc_mod_broadcom_code(val, params) \ - fourcc_mod_code(BROADCOM, ((((__u64)params) << __fourcc_mod_broadcom_param_shift) | val)) -#define fourcc_mod_broadcom_param(m) \ - ((int)(((m) >> __fourcc_mod_broadcom_param_shift) & \ - ((1ULL << __fourcc_mod_broadcom_param_bits) - 1))) -#define fourcc_mod_broadcom_mod(m) \ - ((m) & ~(((1ULL << __fourcc_mod_broadcom_param_bits) - 1) << \ - __fourcc_mod_broadcom_param_shift)) - -/* - * Broadcom VC4 "T" format - * - * This is the primary layout that the V3D GPU can texture from (it - * can't do linear). The T format has: - * - * - 64b utiles of pixels in a raster-order grid according to cpp. It's 4x4 - * pixels at 32 bit depth. - * - * - 1k subtiles made of a 4x4 raster-order grid of 64b utiles (so usually - * 16x16 pixels). - * - * - 4k tiles made of a 2x2 grid of 1k subtiles (so usually 32x32 pixels). On - * even 4k tile rows, they're arranged as (BL, TL, TR, BR), and on odd rows - * they're (TR, BR, BL, TL), where bottom left is start of memory. - * - * - an image made of 4k tiles in rows either left-to-right (even rows of 4k - * tiles) or right-to-left (odd rows of 4k tiles). - */ -#define DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED fourcc_mod_code(BROADCOM, 1) - -/* - * Broadcom SAND format - * - * This is the native format that the H.264 codec block uses. For VC4 - * HVS, it is only valid for H.264 (NV12/21) and RGBA modes. - * - * The image can be considered to be split into columns, and the - * columns are placed consecutively into memory. The width of those - * columns can be either 32, 64, 128, or 256 pixels, but in practice - * only 128 pixel columns are used. - * - * The pitch between the start of each column is set to optimally - * switch between SDRAM banks. This is passed as the number of lines - * of column width in the modifier (we can't use the stride value due - * to various core checks that look at it , so you should set the - * stride to width*cpp). - * - * Note that the column height for this format modifier is the same - * for all of the planes, assuming that each column contains both Y - * and UV. Some SAND-using hardware stores UV in a separate tiled - * image from Y to reduce the column height, which is not supported - * with these modifiers. - * - * The DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT modifier is also - * supported for DRM_FORMAT_P030 where the columns remain as 128 bytes - * wide, but as this is a 10 bpp format that translates to 96 pixels. - */ - -#define DRM_FORMAT_MOD_BROADCOM_SAND32_COL_HEIGHT(v) \ - fourcc_mod_broadcom_code(2, v) -#define DRM_FORMAT_MOD_BROADCOM_SAND64_COL_HEIGHT(v) \ - fourcc_mod_broadcom_code(3, v) -#define DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(v) \ - fourcc_mod_broadcom_code(4, v) -#define DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(v) \ - fourcc_mod_broadcom_code(5, v) - -#define DRM_FORMAT_MOD_BROADCOM_SAND32 \ - DRM_FORMAT_MOD_BROADCOM_SAND32_COL_HEIGHT(0) -#define DRM_FORMAT_MOD_BROADCOM_SAND64 \ - DRM_FORMAT_MOD_BROADCOM_SAND64_COL_HEIGHT(0) -#define DRM_FORMAT_MOD_BROADCOM_SAND128 \ - DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0) -#define DRM_FORMAT_MOD_BROADCOM_SAND256 \ - DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(0) - -/* Broadcom UIF format - * - * This is the common format for the current Broadcom multimedia - * blocks, including V3D 3.x and newer, newer video codecs, and - * displays. - * - * The image consists of utiles (64b blocks), UIF blocks (2x2 utiles), - * and macroblocks (4x4 UIF blocks). Those 4x4 UIF block groups are - * stored in columns, with padding between the columns to ensure that - * moving from one column to the next doesn't hit the same SDRAM page - * bank. - * - * To calculate the padding, it is assumed that each hardware block - * and the software driving it knows the platform's SDRAM page size, - * number of banks, and XOR address, and that it's identical between - * all blocks using the format. This tiling modifier will use XOR as - * necessary to reduce the padding. If a hardware block can't do XOR, - * the assumption is that a no-XOR tiling modifier will be created. - */ -#define DRM_FORMAT_MOD_BROADCOM_UIF fourcc_mod_code(BROADCOM, 6) - -/* - * Arm Framebuffer Compression (AFBC) modifiers - * - * AFBC is a proprietary lossless image compression protocol and format. - * It provides fine-grained random access and minimizes the amount of data - * transferred between IP blocks. - * - * AFBC has several features which may be supported and/or used, which are - * represented using bits in the modifier. Not all combinations are valid, - * and different devices or use-cases may support different combinations. - * - * Further information on the use of AFBC modifiers can be found in - * Documentation/gpu/afbc.rst - */ - -/* - * The top 4 bits (out of the 56 bits allotted for specifying vendor specific - * modifiers) denote the category for modifiers. Currently we have three - * categories of modifiers ie AFBC, MISC and AFRC. We can have a maximum of - * sixteen different categories. - */ -#define DRM_FORMAT_MOD_ARM_CODE(__type, __val) \ - fourcc_mod_code(ARM, ((__u64)(__type) << 52) | ((__val) & 0x000fffffffffffffULL)) - -#define DRM_FORMAT_MOD_ARM_TYPE_AFBC 0x00 -#define DRM_FORMAT_MOD_ARM_TYPE_MISC 0x01 - -#define DRM_FORMAT_MOD_ARM_AFBC(__afbc_mode) \ - DRM_FORMAT_MOD_ARM_CODE(DRM_FORMAT_MOD_ARM_TYPE_AFBC, __afbc_mode) - -/* - * AFBC superblock size - * - * Indicates the superblock size(s) used for the AFBC buffer. The buffer - * size (in pixels) must be aligned to a multiple of the superblock size. - * Four lowest significant bits(LSBs) are reserved for block size. - * - * Where one superblock size is specified, it applies to all planes of the - * buffer (e.g. 16x16, 32x8). When multiple superblock sizes are specified, - * the first applies to the Luma plane and the second applies to the Chroma - * plane(s). e.g. (32x8_64x4 means 32x8 Luma, with 64x4 Chroma). - * Multiple superblock sizes are only valid for multi-plane YCbCr formats. - */ -#define AFBC_FORMAT_MOD_BLOCK_SIZE_MASK 0xf -#define AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 (1ULL) -#define AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 (2ULL) -#define AFBC_FORMAT_MOD_BLOCK_SIZE_64x4 (3ULL) -#define AFBC_FORMAT_MOD_BLOCK_SIZE_32x8_64x4 (4ULL) - -/* - * AFBC lossless colorspace transform - * - * Indicates that the buffer makes use of the AFBC lossless colorspace - * transform. - */ -#define AFBC_FORMAT_MOD_YTR (1ULL << 4) - -/* - * AFBC block-split - * - * Indicates that the payload of each superblock is split. The second - * half of the payload is positioned at a predefined offset from the start - * of the superblock payload. - */ -#define AFBC_FORMAT_MOD_SPLIT (1ULL << 5) - -/* - * AFBC sparse layout - * - * This flag indicates that the payload of each superblock must be stored at a - * predefined position relative to the other superblocks in the same AFBC - * buffer. This order is the same order used by the header buffer. In this mode - * each superblock is given the same amount of space as an uncompressed - * superblock of the particular format would require, rounding up to the next - * multiple of 128 bytes in size. - */ -#define AFBC_FORMAT_MOD_SPARSE (1ULL << 6) - -/* - * AFBC copy-block restrict - * - * Buffers with this flag must obey the copy-block restriction. The restriction - * is such that there are no copy-blocks referring across the border of 8x8 - * blocks. For the subsampled data the 8x8 limitation is also subsampled. - */ -#define AFBC_FORMAT_MOD_CBR (1ULL << 7) - -/* - * AFBC tiled layout - * - * The tiled layout groups superblocks in 8x8 or 4x4 tiles, where all - * superblocks inside a tile are stored together in memory. 8x8 tiles are used - * for pixel formats up to and including 32 bpp while 4x4 tiles are used for - * larger bpp formats. The order between the tiles is scan line. - * When the tiled layout is used, the buffer size (in pixels) must be aligned - * to the tile size. - */ -#define AFBC_FORMAT_MOD_TILED (1ULL << 8) - -/* - * AFBC solid color blocks - * - * Indicates that the buffer makes use of solid-color blocks, whereby bandwidth - * can be reduced if a whole superblock is a single color. - */ -#define AFBC_FORMAT_MOD_SC (1ULL << 9) - -/* - * AFBC double-buffer - * - * Indicates that the buffer is allocated in a layout safe for front-buffer - * rendering. - */ -#define AFBC_FORMAT_MOD_DB (1ULL << 10) - -/* - * AFBC buffer content hints - * - * Indicates that the buffer includes per-superblock content hints. - */ -#define AFBC_FORMAT_MOD_BCH (1ULL << 11) - -/* AFBC uncompressed storage mode - * - * Indicates that the buffer is using AFBC uncompressed storage mode. - * In this mode all superblock payloads in the buffer use the uncompressed - * storage mode, which is usually only used for data which cannot be compressed. - * The buffer layout is the same as for AFBC buffers without USM set, this only - * affects the storage mode of the individual superblocks. Note that even a - * buffer without USM set may use uncompressed storage mode for some or all - * superblocks, USM just guarantees it for all. - */ -#define AFBC_FORMAT_MOD_USM (1ULL << 12) - -/* - * Arm Fixed-Rate Compression (AFRC) modifiers - * - * AFRC is a proprietary fixed rate image compression protocol and format, - * designed to provide guaranteed bandwidth and memory footprint - * reductions in graphics and media use-cases. - * - * AFRC buffers consist of one or more planes, with the same components - * and meaning as an uncompressed buffer using the same pixel format. - * - * Within each plane, the pixel/luma/chroma values are grouped into - * "coding unit" blocks which are individually compressed to a - * fixed size (in bytes). All coding units within a given plane of a buffer - * store the same number of values, and have the same compressed size. - * - * The coding unit size is configurable, allowing different rates of compression. - * - * The start of each AFRC buffer plane must be aligned to an alignment granule which - * depends on the coding unit size. - * - * Coding Unit Size Plane Alignment - * ---------------- --------------- - * 16 bytes 1024 bytes - * 24 bytes 512 bytes - * 32 bytes 2048 bytes - * - * Coding units are grouped into paging tiles. AFRC buffer dimensions must be aligned - * to a multiple of the paging tile dimensions. - * The dimensions of each paging tile depend on whether the buffer is optimised for - * scanline (SCAN layout) or rotated (ROT layout) access. - * - * Layout Paging Tile Width Paging Tile Height - * ------ ----------------- ------------------ - * SCAN 16 coding units 4 coding units - * ROT 8 coding units 8 coding units - * - * The dimensions of each coding unit depend on the number of components - * in the compressed plane and whether the buffer is optimised for - * scanline (SCAN layout) or rotated (ROT layout) access. - * - * Number of Components in Plane Layout Coding Unit Width Coding Unit Height - * ----------------------------- --------- ----------------- ------------------ - * 1 SCAN 16 samples 4 samples - * Example: 16x4 luma samples in a 'Y' plane - * 16x4 chroma 'V' values, in the 'V' plane of a fully-planar YUV buffer - * ----------------------------- --------- ----------------- ------------------ - * 1 ROT 8 samples 8 samples - * Example: 8x8 luma samples in a 'Y' plane - * 8x8 chroma 'V' values, in the 'V' plane of a fully-planar YUV buffer - * ----------------------------- --------- ----------------- ------------------ - * 2 DONT CARE 8 samples 4 samples - * Example: 8x4 chroma pairs in the 'UV' plane of a semi-planar YUV buffer - * ----------------------------- --------- ----------------- ------------------ - * 3 DONT CARE 4 samples 4 samples - * Example: 4x4 pixels in an RGB buffer without alpha - * ----------------------------- --------- ----------------- ------------------ - * 4 DONT CARE 4 samples 4 samples - * Example: 4x4 pixels in an RGB buffer with alpha - */ - -#define DRM_FORMAT_MOD_ARM_TYPE_AFRC 0x02 - -#define DRM_FORMAT_MOD_ARM_AFRC(__afrc_mode) \ - DRM_FORMAT_MOD_ARM_CODE(DRM_FORMAT_MOD_ARM_TYPE_AFRC, __afrc_mode) - -/* - * AFRC coding unit size modifier. - * - * Indicates the number of bytes used to store each compressed coding unit for - * one or more planes in an AFRC encoded buffer. The coding unit size for chrominance - * is the same for both Cb and Cr, which may be stored in separate planes. - * - * AFRC_FORMAT_MOD_CU_SIZE_P0 indicates the number of bytes used to store - * each compressed coding unit in the first plane of the buffer. For RGBA buffers - * this is the only plane, while for semi-planar and fully-planar YUV buffers, - * this corresponds to the luma plane. - * - * AFRC_FORMAT_MOD_CU_SIZE_P12 indicates the number of bytes used to store - * each compressed coding unit in the second and third planes in the buffer. - * For semi-planar and fully-planar YUV buffers, this corresponds to the chroma plane(s). - * - * For single-plane buffers, AFRC_FORMAT_MOD_CU_SIZE_P0 must be specified - * and AFRC_FORMAT_MOD_CU_SIZE_P12 must be zero. - * For semi-planar and fully-planar buffers, both AFRC_FORMAT_MOD_CU_SIZE_P0 and - * AFRC_FORMAT_MOD_CU_SIZE_P12 must be specified. - */ -#define AFRC_FORMAT_MOD_CU_SIZE_MASK 0xf -#define AFRC_FORMAT_MOD_CU_SIZE_16 (1ULL) -#define AFRC_FORMAT_MOD_CU_SIZE_24 (2ULL) -#define AFRC_FORMAT_MOD_CU_SIZE_32 (3ULL) - -#define AFRC_FORMAT_MOD_CU_SIZE_P0(__afrc_cu_size) (__afrc_cu_size) -#define AFRC_FORMAT_MOD_CU_SIZE_P12(__afrc_cu_size) ((__afrc_cu_size) << 4) - -/* - * AFRC scanline memory layout. - * - * Indicates if the buffer uses the scanline-optimised layout - * for an AFRC encoded buffer, otherwise, it uses the rotation-optimised layout. - * The memory layout is the same for all planes. - */ -#define AFRC_FORMAT_MOD_LAYOUT_SCAN (1ULL << 8) - -/* - * Arm 16x16 Block U-Interleaved modifier - * - * This is used by Arm Mali Utgard and Midgard GPUs. It divides the image - * into 16x16 pixel blocks. Blocks are stored linearly in order, but pixels - * in the block are reordered. - */ -#define DRM_FORMAT_MOD_ARM_16X16_BLOCK_U_INTERLEAVED \ - DRM_FORMAT_MOD_ARM_CODE(DRM_FORMAT_MOD_ARM_TYPE_MISC, 1ULL) - -/* - * Allwinner tiled modifier - * - * This tiling mode is implemented by the VPU found on all Allwinner platforms, - * codenamed sunxi. It is associated with a YUV format that uses either 2 or 3 - * planes. - * - * With this tiling, the luminance samples are disposed in tiles representing - * 32x32 pixels and the chrominance samples in tiles representing 32x64 pixels. - * The pixel order in each tile is linear and the tiles are disposed linearly, - * both in row-major order. - */ -#define DRM_FORMAT_MOD_ALLWINNER_TILED fourcc_mod_code(ALLWINNER, 1) - -/* - * Amlogic Video Framebuffer Compression modifiers - * - * Amlogic uses a proprietary lossless image compression protocol and format - * for their hardware video codec accelerators, either video decoders or - * video input encoders. - * - * It considerably reduces memory bandwidth while writing and reading - * frames in memory. - * - * The underlying storage is considered to be 3 components, 8bit or 10-bit - * per component YCbCr 420, single plane : - * - DRM_FORMAT_YUV420_8BIT - * - DRM_FORMAT_YUV420_10BIT - * - * The first 8 bits of the mode defines the layout, then the following 8 bits - * defines the options changing the layout. - * - * Not all combinations are valid, and different SoCs may support different - * combinations of layout and options. - */ -#define __fourcc_mod_amlogic_layout_mask 0xff -#define __fourcc_mod_amlogic_options_shift 8 -#define __fourcc_mod_amlogic_options_mask 0xff - -#define DRM_FORMAT_MOD_AMLOGIC_FBC(__layout, __options) \ - fourcc_mod_code(AMLOGIC, \ - ((__layout) & __fourcc_mod_amlogic_layout_mask) | \ - (((__options) & __fourcc_mod_amlogic_options_mask) \ - << __fourcc_mod_amlogic_options_shift)) - -/* Amlogic FBC Layouts */ - -/* - * Amlogic FBC Basic Layout - * - * The basic layout is composed of: - * - a body content organized in 64x32 superblocks with 4096 bytes per - * superblock in default mode. - * - a 32 bytes per 128x64 header block - * - * This layout is transferrable between Amlogic SoCs supporting this modifier. - */ -#define AMLOGIC_FBC_LAYOUT_BASIC (1ULL) - -/* - * Amlogic FBC Scatter Memory layout - * - * Indicates the header contains IOMMU references to the compressed - * frames content to optimize memory access and layout. - * - * In this mode, only the header memory address is needed, thus the - * content memory organization is tied to the current producer - * execution and cannot be saved/dumped neither transferrable between - * Amlogic SoCs supporting this modifier. - * - * Due to the nature of the layout, these buffers are not expected to - * be accessible by the user-space clients, but only accessible by the - * hardware producers and consumers. - * - * The user-space clients should expect a failure while trying to mmap - * the DMA-BUF handle returned by the producer. - */ -#define AMLOGIC_FBC_LAYOUT_SCATTER (2ULL) - -/* Amlogic FBC Layout Options Bit Mask */ - -/* - * Amlogic FBC Memory Saving mode - * - * Indicates the storage is packed when pixel size is multiple of word - * boundaries, i.e. 8bit should be stored in this mode to save allocation - * memory. - * - * This mode reduces body layout to 3072 bytes per 64x32 superblock with - * the basic layout and 3200 bytes per 64x32 superblock combined with - * the scatter layout. - */ -#define AMLOGIC_FBC_OPTION_MEM_SAVING (1ULL << 0) - -/* - * AMD modifiers - * - * Memory layout: - * - * without DCC: - * - main surface - * - * with DCC & without DCC_RETILE: - * - main surface in plane 0 - * - DCC surface in plane 1 (RB-aligned, pipe-aligned if DCC_PIPE_ALIGN is set) - * - * with DCC & DCC_RETILE: - * - main surface in plane 0 - * - displayable DCC surface in plane 1 (not RB-aligned & not pipe-aligned) - * - pipe-aligned DCC surface in plane 2 (RB-aligned & pipe-aligned) - * - * For multi-plane formats the above surfaces get merged into one plane for - * each format plane, based on the required alignment only. - * - * Bits Parameter Notes - * ----- ------------------------ --------------------------------------------- - * - * 7:0 TILE_VERSION Values are AMD_FMT_MOD_TILE_VER_* - * 12:8 TILE Values are AMD_FMT_MOD_TILE__* - * 13 DCC - * 14 DCC_RETILE - * 15 DCC_PIPE_ALIGN - * 16 DCC_INDEPENDENT_64B - * 17 DCC_INDEPENDENT_128B - * 19:18 DCC_MAX_COMPRESSED_BLOCK Values are AMD_FMT_MOD_DCC_BLOCK_* - * 20 DCC_CONSTANT_ENCODE - * 23:21 PIPE_XOR_BITS Only for some chips - * 26:24 BANK_XOR_BITS Only for some chips - * 29:27 PACKERS Only for some chips - * 32:30 RB Only for some chips - * 35:33 PIPE Only for some chips - * 55:36 - Reserved for future use, must be zero - */ -#define AMD_FMT_MOD fourcc_mod_code(AMD, 0) - -#define IS_AMD_FMT_MOD(val) (((val) >> 56) == DRM_FORMAT_MOD_VENDOR_AMD) - -/* Reserve 0 for GFX8 and older */ -#define AMD_FMT_MOD_TILE_VER_GFX9 1 -#define AMD_FMT_MOD_TILE_VER_GFX10 2 -#define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3 -#define AMD_FMT_MOD_TILE_VER_GFX11 4 -#define AMD_FMT_MOD_TILE_VER_GFX12 5 - -/* - * 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical - * version. - */ -#define AMD_FMT_MOD_TILE_GFX9_64K_S 9 - -/* - * 64K_D for non-32 bpp is the same for GFX9/GFX10/GFX10_RBPLUS and hence has - * GFX9 as canonical version. - * - * 64K_D_2D on GFX12 is identical to 64K_D on GFX11. - */ -#define AMD_FMT_MOD_TILE_GFX9_64K_D 10 -#define AMD_FMT_MOD_TILE_GFX9_64K_S_X 25 -#define AMD_FMT_MOD_TILE_GFX9_64K_D_X 26 -#define AMD_FMT_MOD_TILE_GFX9_64K_R_X 27 -#define AMD_FMT_MOD_TILE_GFX11_256K_R_X 31 - -/* Gfx12 swizzle modes: - * 0 - LINEAR - * 1 - 256B_2D - 2D block dimensions - * 2 - 4KB_2D - * 3 - 64KB_2D - * 4 - 256KB_2D - * 5 - 4KB_3D - 3D block dimensions - * 6 - 64KB_3D - * 7 - 256KB_3D - */ -#define AMD_FMT_MOD_TILE_GFX12_256B_2D 1 -#define AMD_FMT_MOD_TILE_GFX12_4K_2D 2 -#define AMD_FMT_MOD_TILE_GFX12_64K_2D 3 -#define AMD_FMT_MOD_TILE_GFX12_256K_2D 4 - -#define AMD_FMT_MOD_DCC_BLOCK_64B 0 -#define AMD_FMT_MOD_DCC_BLOCK_128B 1 -#define AMD_FMT_MOD_DCC_BLOCK_256B 2 - -#define AMD_FMT_MOD_TILE_VERSION_SHIFT 0 -#define AMD_FMT_MOD_TILE_VERSION_MASK 0xFF -#define AMD_FMT_MOD_TILE_SHIFT 8 -#define AMD_FMT_MOD_TILE_MASK 0x1F - -/* Whether DCC compression is enabled. */ -#define AMD_FMT_MOD_DCC_SHIFT 13 -#define AMD_FMT_MOD_DCC_MASK 0x1 - -/* - * Whether to include two DCC surfaces, one which is rb & pipe aligned, and - * one which is not-aligned. - */ -#define AMD_FMT_MOD_DCC_RETILE_SHIFT 14 -#define AMD_FMT_MOD_DCC_RETILE_MASK 0x1 - -/* Only set if DCC_RETILE = false */ -#define AMD_FMT_MOD_DCC_PIPE_ALIGN_SHIFT 15 -#define AMD_FMT_MOD_DCC_PIPE_ALIGN_MASK 0x1 - -#define AMD_FMT_MOD_DCC_INDEPENDENT_64B_SHIFT 16 -#define AMD_FMT_MOD_DCC_INDEPENDENT_64B_MASK 0x1 -#define AMD_FMT_MOD_DCC_INDEPENDENT_128B_SHIFT 17 -#define AMD_FMT_MOD_DCC_INDEPENDENT_128B_MASK 0x1 -#define AMD_FMT_MOD_DCC_MAX_COMPRESSED_BLOCK_SHIFT 18 -#define AMD_FMT_MOD_DCC_MAX_COMPRESSED_BLOCK_MASK 0x3 - -/* - * DCC supports embedding some clear colors directly in the DCC surface. - * However, on older GPUs the rendering HW ignores the embedded clear color - * and prefers the driver provided color. This necessitates doing a fastclear - * eliminate operation before a process transfers control. - * - * If this bit is set that means the fastclear eliminate is not needed for these - * embeddable colors. - */ -#define AMD_FMT_MOD_DCC_CONSTANT_ENCODE_SHIFT 20 -#define AMD_FMT_MOD_DCC_CONSTANT_ENCODE_MASK 0x1 - -/* - * The below fields are for accounting for per GPU differences. These are only - * relevant for GFX9 and later and if the tile field is *_X/_T. - * - * PIPE_XOR_BITS = always needed - * BANK_XOR_BITS = only for TILE_VER_GFX9 - * PACKERS = only for TILE_VER_GFX10_RBPLUS - * RB = only for TILE_VER_GFX9 & DCC - * PIPE = only for TILE_VER_GFX9 & DCC & (DCC_RETILE | DCC_PIPE_ALIGN) - */ -#define AMD_FMT_MOD_PIPE_XOR_BITS_SHIFT 21 -#define AMD_FMT_MOD_PIPE_XOR_BITS_MASK 0x7 -#define AMD_FMT_MOD_BANK_XOR_BITS_SHIFT 24 -#define AMD_FMT_MOD_BANK_XOR_BITS_MASK 0x7 -#define AMD_FMT_MOD_PACKERS_SHIFT 27 -#define AMD_FMT_MOD_PACKERS_MASK 0x7 -#define AMD_FMT_MOD_RB_SHIFT 30 -#define AMD_FMT_MOD_RB_MASK 0x7 -#define AMD_FMT_MOD_PIPE_SHIFT 33 -#define AMD_FMT_MOD_PIPE_MASK 0x7 - -#define AMD_FMT_MOD_SET(field, value) \ - ((__u64)(value) << AMD_FMT_MOD_##field##_SHIFT) -#define AMD_FMT_MOD_GET(field, value) \ - (((value) >> AMD_FMT_MOD_##field##_SHIFT) & AMD_FMT_MOD_##field##_MASK) -#define AMD_FMT_MOD_CLEAR(field) \ - (~((__u64)AMD_FMT_MOD_##field##_MASK << AMD_FMT_MOD_##field##_SHIFT)) - -#if defined(__cplusplus) -} -#endif - -#endif /* DRM_FOURCC_H */ diff --git a/libc/musl-overlay/include/drm/drm_mode.h b/libc/musl-overlay/include/drm/drm_mode.h deleted file mode 100644 index c082810c08..0000000000 --- a/libc/musl-overlay/include/drm/drm_mode.h +++ /dev/null @@ -1,1362 +0,0 @@ -/* - * Copyright (c) 2007 Dave Airlie - * Copyright (c) 2007 Jakob Bornecrantz - * Copyright (c) 2008 Red Hat Inc. - * Copyright (c) 2007-2008 Tungsten Graphics, Inc., Cedar Park, TX., USA - * Copyright (c) 2007-2008 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef _DRM_MODE_H -#define _DRM_MODE_H - -#include "drm.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -/** - * DOC: overview - * - * DRM exposes many UAPI and structure definitions to have a consistent - * and standardized interface with users. - * Userspace can refer to these structure definitions and UAPI formats - * to communicate to drivers. - */ - -#define DRM_CONNECTOR_NAME_LEN 32 -#define DRM_DISPLAY_MODE_LEN 32 -#define DRM_PROP_NAME_LEN 32 - -#define DRM_MODE_TYPE_BUILTIN (1<<0) /* deprecated */ -#define DRM_MODE_TYPE_CLOCK_C ((1<<1) | DRM_MODE_TYPE_BUILTIN) /* deprecated */ -#define DRM_MODE_TYPE_CRTC_C ((1<<2) | DRM_MODE_TYPE_BUILTIN) /* deprecated */ -#define DRM_MODE_TYPE_PREFERRED (1<<3) -#define DRM_MODE_TYPE_DEFAULT (1<<4) /* deprecated */ -#define DRM_MODE_TYPE_USERDEF (1<<5) -#define DRM_MODE_TYPE_DRIVER (1<<6) - -#define DRM_MODE_TYPE_ALL (DRM_MODE_TYPE_PREFERRED | \ - DRM_MODE_TYPE_USERDEF | \ - DRM_MODE_TYPE_DRIVER) - -/* Video mode flags */ -/* bit compatible with the xrandr RR_ definitions (bits 0-13) - * - * ABI warning: Existing userspace really expects - * the mode flags to match the xrandr definitions. Any - * changes that don't match the xrandr definitions will - * likely need a new client cap or some other mechanism - * to avoid breaking existing userspace. This includes - * allocating new flags in the previously unused bits! - */ -#define DRM_MODE_FLAG_PHSYNC (1<<0) -#define DRM_MODE_FLAG_NHSYNC (1<<1) -#define DRM_MODE_FLAG_PVSYNC (1<<2) -#define DRM_MODE_FLAG_NVSYNC (1<<3) -#define DRM_MODE_FLAG_INTERLACE (1<<4) -#define DRM_MODE_FLAG_DBLSCAN (1<<5) -#define DRM_MODE_FLAG_CSYNC (1<<6) -#define DRM_MODE_FLAG_PCSYNC (1<<7) -#define DRM_MODE_FLAG_NCSYNC (1<<8) -#define DRM_MODE_FLAG_HSKEW (1<<9) /* hskew provided */ -#define DRM_MODE_FLAG_BCAST (1<<10) /* deprecated */ -#define DRM_MODE_FLAG_PIXMUX (1<<11) /* deprecated */ -#define DRM_MODE_FLAG_DBLCLK (1<<12) -#define DRM_MODE_FLAG_CLKDIV2 (1<<13) - /* - * When adding a new stereo mode don't forget to adjust DRM_MODE_FLAGS_3D_MAX - * (define not exposed to user space). - */ -#define DRM_MODE_FLAG_3D_MASK (0x1f<<14) -#define DRM_MODE_FLAG_3D_NONE (0<<14) -#define DRM_MODE_FLAG_3D_FRAME_PACKING (1<<14) -#define DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE (2<<14) -#define DRM_MODE_FLAG_3D_LINE_ALTERNATIVE (3<<14) -#define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL (4<<14) -#define DRM_MODE_FLAG_3D_L_DEPTH (5<<14) -#define DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH (6<<14) -#define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14) -#define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14) - -/* Picture aspect ratio options */ -#define DRM_MODE_PICTURE_ASPECT_NONE 0 -#define DRM_MODE_PICTURE_ASPECT_4_3 1 -#define DRM_MODE_PICTURE_ASPECT_16_9 2 -#define DRM_MODE_PICTURE_ASPECT_64_27 3 -#define DRM_MODE_PICTURE_ASPECT_256_135 4 - -/* Content type options */ -#define DRM_MODE_CONTENT_TYPE_NO_DATA 0 -#define DRM_MODE_CONTENT_TYPE_GRAPHICS 1 -#define DRM_MODE_CONTENT_TYPE_PHOTO 2 -#define DRM_MODE_CONTENT_TYPE_CINEMA 3 -#define DRM_MODE_CONTENT_TYPE_GAME 4 - -/* Aspect ratio flag bitmask (4 bits 22:19) */ -#define DRM_MODE_FLAG_PIC_AR_MASK (0x0F<<19) -#define DRM_MODE_FLAG_PIC_AR_NONE \ - (DRM_MODE_PICTURE_ASPECT_NONE<<19) -#define DRM_MODE_FLAG_PIC_AR_4_3 \ - (DRM_MODE_PICTURE_ASPECT_4_3<<19) -#define DRM_MODE_FLAG_PIC_AR_16_9 \ - (DRM_MODE_PICTURE_ASPECT_16_9<<19) -#define DRM_MODE_FLAG_PIC_AR_64_27 \ - (DRM_MODE_PICTURE_ASPECT_64_27<<19) -#define DRM_MODE_FLAG_PIC_AR_256_135 \ - (DRM_MODE_PICTURE_ASPECT_256_135<<19) - -#define DRM_MODE_FLAG_ALL (DRM_MODE_FLAG_PHSYNC | \ - DRM_MODE_FLAG_NHSYNC | \ - DRM_MODE_FLAG_PVSYNC | \ - DRM_MODE_FLAG_NVSYNC | \ - DRM_MODE_FLAG_INTERLACE | \ - DRM_MODE_FLAG_DBLSCAN | \ - DRM_MODE_FLAG_CSYNC | \ - DRM_MODE_FLAG_PCSYNC | \ - DRM_MODE_FLAG_NCSYNC | \ - DRM_MODE_FLAG_HSKEW | \ - DRM_MODE_FLAG_DBLCLK | \ - DRM_MODE_FLAG_CLKDIV2 | \ - DRM_MODE_FLAG_3D_MASK) - -/* DPMS flags */ -/* bit compatible with the xorg definitions. */ -#define DRM_MODE_DPMS_ON 0 -#define DRM_MODE_DPMS_STANDBY 1 -#define DRM_MODE_DPMS_SUSPEND 2 -#define DRM_MODE_DPMS_OFF 3 - -/* Scaling mode options */ -#define DRM_MODE_SCALE_NONE 0 /* Unmodified timing (display or - software can still scale) */ -#define DRM_MODE_SCALE_FULLSCREEN 1 /* Full screen, ignore aspect */ -#define DRM_MODE_SCALE_CENTER 2 /* Centered, no scaling */ -#define DRM_MODE_SCALE_ASPECT 3 /* Full screen, preserve aspect */ - -/* Dithering mode options */ -#define DRM_MODE_DITHERING_OFF 0 -#define DRM_MODE_DITHERING_ON 1 -#define DRM_MODE_DITHERING_AUTO 2 - -/* Dirty info options */ -#define DRM_MODE_DIRTY_OFF 0 -#define DRM_MODE_DIRTY_ON 1 -#define DRM_MODE_DIRTY_ANNOTATE 2 - -/* Link Status options */ -#define DRM_MODE_LINK_STATUS_GOOD 0 -#define DRM_MODE_LINK_STATUS_BAD 1 - -/* - * DRM_MODE_ROTATE_ - * - * Signals that a drm plane is been rotated degrees in counter - * clockwise direction. - * - * This define is provided as a convenience, looking up the property id - * using the name->prop id lookup is the preferred method. - */ -#define DRM_MODE_ROTATE_0 (1<<0) -#define DRM_MODE_ROTATE_90 (1<<1) -#define DRM_MODE_ROTATE_180 (1<<2) -#define DRM_MODE_ROTATE_270 (1<<3) - -/* - * DRM_MODE_ROTATE_MASK - * - * Bitmask used to look for drm plane rotations. - */ -#define DRM_MODE_ROTATE_MASK (\ - DRM_MODE_ROTATE_0 | \ - DRM_MODE_ROTATE_90 | \ - DRM_MODE_ROTATE_180 | \ - DRM_MODE_ROTATE_270) - -/* - * DRM_MODE_REFLECT_ - * - * Signals that the contents of a drm plane is reflected along the axis, - * in the same way as mirroring. - * See kerneldoc chapter "Plane Composition Properties" for more details. - * - * This define is provided as a convenience, looking up the property id - * using the name->prop id lookup is the preferred method. - */ -#define DRM_MODE_REFLECT_X (1<<4) -#define DRM_MODE_REFLECT_Y (1<<5) - -/* - * DRM_MODE_REFLECT_MASK - * - * Bitmask used to look for drm plane reflections. - */ -#define DRM_MODE_REFLECT_MASK (\ - DRM_MODE_REFLECT_X | \ - DRM_MODE_REFLECT_Y) - -/* Content Protection Flags */ -#define DRM_MODE_CONTENT_PROTECTION_UNDESIRED 0 -#define DRM_MODE_CONTENT_PROTECTION_DESIRED 1 -#define DRM_MODE_CONTENT_PROTECTION_ENABLED 2 - -/** - * struct drm_mode_modeinfo - Display mode information. - * @clock: pixel clock in kHz - * @hdisplay: horizontal display size - * @hsync_start: horizontal sync start - * @hsync_end: horizontal sync end - * @htotal: horizontal total size - * @hskew: horizontal skew - * @vdisplay: vertical display size - * @vsync_start: vertical sync start - * @vsync_end: vertical sync end - * @vtotal: vertical total size - * @vscan: vertical scan - * @vrefresh: approximate vertical refresh rate in Hz - * @flags: bitmask of misc. flags, see DRM_MODE_FLAG_* defines - * @type: bitmask of type flags, see DRM_MODE_TYPE_* defines - * @name: string describing the mode resolution - * - * This is the user-space API display mode information structure. For the - * kernel version see struct drm_display_mode. - */ -struct drm_mode_modeinfo { - __u32 clock; - __u16 hdisplay; - __u16 hsync_start; - __u16 hsync_end; - __u16 htotal; - __u16 hskew; - __u16 vdisplay; - __u16 vsync_start; - __u16 vsync_end; - __u16 vtotal; - __u16 vscan; - - __u32 vrefresh; - - __u32 flags; - __u32 type; - char name[DRM_DISPLAY_MODE_LEN]; -}; - -struct drm_mode_card_res { - __u64 fb_id_ptr; - __u64 crtc_id_ptr; - __u64 connector_id_ptr; - __u64 encoder_id_ptr; - __u32 count_fbs; - __u32 count_crtcs; - __u32 count_connectors; - __u32 count_encoders; - __u32 min_width; - __u32 max_width; - __u32 min_height; - __u32 max_height; -}; - -struct drm_mode_crtc { - __u64 set_connectors_ptr; - __u32 count_connectors; - - __u32 crtc_id; /**< Id */ - __u32 fb_id; /**< Id of framebuffer */ - - __u32 x; /**< x Position on the framebuffer */ - __u32 y; /**< y Position on the framebuffer */ - - __u32 gamma_size; - __u32 mode_valid; - struct drm_mode_modeinfo mode; -}; - -#define DRM_MODE_PRESENT_TOP_FIELD (1<<0) -#define DRM_MODE_PRESENT_BOTTOM_FIELD (1<<1) - -/* Planes blend with or override other bits on the CRTC */ -struct drm_mode_set_plane { - __u32 plane_id; - __u32 crtc_id; - __u32 fb_id; /* fb object contains surface format type */ - __u32 flags; /* see above flags */ - - /* Signed dest location allows it to be partially off screen */ - __s32 crtc_x; - __s32 crtc_y; - __u32 crtc_w; - __u32 crtc_h; - - /* Source values are 16.16 fixed point */ - __u32 src_x; - __u32 src_y; - __u32 src_h; - __u32 src_w; -}; - -/** - * struct drm_mode_get_plane - Get plane metadata. - * - * Userspace can perform a GETPLANE ioctl to retrieve information about a - * plane. - * - * To retrieve the number of formats supported, set @count_format_types to zero - * and call the ioctl. @count_format_types will be updated with the value. - * - * To retrieve these formats, allocate an array with the memory needed to store - * @count_format_types formats. Point @format_type_ptr to this array and call - * the ioctl again (with @count_format_types still set to the value returned in - * the first ioctl call). - */ -struct drm_mode_get_plane { - /** - * @plane_id: Object ID of the plane whose information should be - * retrieved. Set by caller. - */ - __u32 plane_id; - - /** @crtc_id: Object ID of the current CRTC. */ - __u32 crtc_id; - /** @fb_id: Object ID of the current fb. */ - __u32 fb_id; - - /** - * @possible_crtcs: Bitmask of CRTC's compatible with the plane. CRTC's - * are created and they receive an index, which corresponds to their - * position in the bitmask. Bit N corresponds to - * :ref:`CRTC index` N. - */ - __u32 possible_crtcs; - /** @gamma_size: Never used. */ - __u32 gamma_size; - - /** @count_format_types: Number of formats. */ - __u32 count_format_types; - /** - * @format_type_ptr: Pointer to ``__u32`` array of formats that are - * supported by the plane. These formats do not require modifiers. - */ - __u64 format_type_ptr; -}; - -struct drm_mode_get_plane_res { - __u64 plane_id_ptr; - __u32 count_planes; -}; - -#define DRM_MODE_ENCODER_NONE 0 -#define DRM_MODE_ENCODER_DAC 1 -#define DRM_MODE_ENCODER_TMDS 2 -#define DRM_MODE_ENCODER_LVDS 3 -#define DRM_MODE_ENCODER_TVDAC 4 -#define DRM_MODE_ENCODER_VIRTUAL 5 -#define DRM_MODE_ENCODER_DSI 6 -#define DRM_MODE_ENCODER_DPMST 7 -#define DRM_MODE_ENCODER_DPI 8 - -struct drm_mode_get_encoder { - __u32 encoder_id; - __u32 encoder_type; - - __u32 crtc_id; /**< Id of crtc */ - - __u32 possible_crtcs; - __u32 possible_clones; -}; - -/* This is for connectors with multiple signal types. */ -/* Try to match DRM_MODE_CONNECTOR_X as closely as possible. */ -enum drm_mode_subconnector { - DRM_MODE_SUBCONNECTOR_Automatic = 0, /* DVI-I, TV */ - DRM_MODE_SUBCONNECTOR_Unknown = 0, /* DVI-I, TV, DP */ - DRM_MODE_SUBCONNECTOR_VGA = 1, /* DP */ - DRM_MODE_SUBCONNECTOR_DVID = 3, /* DVI-I DP */ - DRM_MODE_SUBCONNECTOR_DVIA = 4, /* DVI-I */ - DRM_MODE_SUBCONNECTOR_Composite = 5, /* TV */ - DRM_MODE_SUBCONNECTOR_SVIDEO = 6, /* TV */ - DRM_MODE_SUBCONNECTOR_Component = 8, /* TV */ - DRM_MODE_SUBCONNECTOR_SCART = 9, /* TV */ - DRM_MODE_SUBCONNECTOR_DisplayPort = 10, /* DP */ - DRM_MODE_SUBCONNECTOR_HDMIA = 11, /* DP */ - DRM_MODE_SUBCONNECTOR_Native = 15, /* DP */ - DRM_MODE_SUBCONNECTOR_Wireless = 18, /* DP */ -}; - -#define DRM_MODE_CONNECTOR_Unknown 0 -#define DRM_MODE_CONNECTOR_VGA 1 -#define DRM_MODE_CONNECTOR_DVII 2 -#define DRM_MODE_CONNECTOR_DVID 3 -#define DRM_MODE_CONNECTOR_DVIA 4 -#define DRM_MODE_CONNECTOR_Composite 5 -#define DRM_MODE_CONNECTOR_SVIDEO 6 -#define DRM_MODE_CONNECTOR_LVDS 7 -#define DRM_MODE_CONNECTOR_Component 8 -#define DRM_MODE_CONNECTOR_9PinDIN 9 -#define DRM_MODE_CONNECTOR_DisplayPort 10 -#define DRM_MODE_CONNECTOR_HDMIA 11 -#define DRM_MODE_CONNECTOR_HDMIB 12 -#define DRM_MODE_CONNECTOR_TV 13 -#define DRM_MODE_CONNECTOR_eDP 14 -#define DRM_MODE_CONNECTOR_VIRTUAL 15 -#define DRM_MODE_CONNECTOR_DSI 16 -#define DRM_MODE_CONNECTOR_DPI 17 -#define DRM_MODE_CONNECTOR_WRITEBACK 18 -#define DRM_MODE_CONNECTOR_SPI 19 -#define DRM_MODE_CONNECTOR_USB 20 - -/** - * struct drm_mode_get_connector - Get connector metadata. - * - * User-space can perform a GETCONNECTOR ioctl to retrieve information about a - * connector. User-space is expected to retrieve encoders, modes and properties - * by performing this ioctl at least twice: the first time to retrieve the - * number of elements, the second time to retrieve the elements themselves. - * - * To retrieve the number of elements, set @count_props and @count_encoders to - * zero, set @count_modes to 1, and set @modes_ptr to a temporary struct - * drm_mode_modeinfo element. - * - * To retrieve the elements, allocate arrays for @encoders_ptr, @modes_ptr, - * @props_ptr and @prop_values_ptr, then set @count_modes, @count_props and - * @count_encoders to their capacity. - * - * Performing the ioctl only twice may be racy: the number of elements may have - * changed with a hotplug event in-between the two ioctls. User-space is - * expected to retry the last ioctl until the number of elements stabilizes. - * The kernel won't fill any array which doesn't have the expected length. - * - * **Force-probing a connector** - * - * If the @count_modes field is set to zero and the DRM client is the current - * DRM master, the kernel will perform a forced probe on the connector to - * refresh the connector status, modes and EDID. A forced-probe can be slow, - * might cause flickering and the ioctl will block. - * - * User-space needs to force-probe connectors to ensure their metadata is - * up-to-date at startup and after receiving a hot-plug event. User-space - * may perform a forced-probe when the user explicitly requests it. User-space - * shouldn't perform a forced-probe in other situations. - */ -struct drm_mode_get_connector { - /** @encoders_ptr: Pointer to ``__u32`` array of object IDs. */ - __u64 encoders_ptr; - /** @modes_ptr: Pointer to struct drm_mode_modeinfo array. */ - __u64 modes_ptr; - /** @props_ptr: Pointer to ``__u32`` array of property IDs. */ - __u64 props_ptr; - /** @prop_values_ptr: Pointer to ``__u64`` array of property values. */ - __u64 prop_values_ptr; - - /** @count_modes: Number of modes. */ - __u32 count_modes; - /** @count_props: Number of properties. */ - __u32 count_props; - /** @count_encoders: Number of encoders. */ - __u32 count_encoders; - - /** @encoder_id: Object ID of the current encoder. */ - __u32 encoder_id; - /** @connector_id: Object ID of the connector. */ - __u32 connector_id; - /** - * @connector_type: Type of the connector. - * - * See DRM_MODE_CONNECTOR_* defines. - */ - __u32 connector_type; - /** - * @connector_type_id: Type-specific connector number. - * - * This is not an object ID. This is a per-type connector number. Each - * (type, type_id) combination is unique across all connectors of a DRM - * device. - * - * The (type, type_id) combination is not a stable identifier: the - * type_id can change depending on the driver probe order. - */ - __u32 connector_type_id; - - /** - * @connection: Status of the connector. - * - * See enum drm_connector_status. - */ - __u32 connection; - /** @mm_width: Width of the connected sink in millimeters. */ - __u32 mm_width; - /** @mm_height: Height of the connected sink in millimeters. */ - __u32 mm_height; - /** - * @subpixel: Subpixel order of the connected sink. - * - * See enum subpixel_order. - */ - __u32 subpixel; - - /** @pad: Padding, must be zero. */ - __u32 pad; -}; - -#define DRM_MODE_PROP_PENDING (1<<0) /* deprecated, do not use */ -#define DRM_MODE_PROP_RANGE (1<<1) -#define DRM_MODE_PROP_IMMUTABLE (1<<2) -#define DRM_MODE_PROP_ENUM (1<<3) /* enumerated type with text strings */ -#define DRM_MODE_PROP_BLOB (1<<4) -#define DRM_MODE_PROP_BITMASK (1<<5) /* bitmask of enumerated types */ - -/* non-extended types: legacy bitmask, one bit per type: */ -#define DRM_MODE_PROP_LEGACY_TYPE ( \ - DRM_MODE_PROP_RANGE | \ - DRM_MODE_PROP_ENUM | \ - DRM_MODE_PROP_BLOB | \ - DRM_MODE_PROP_BITMASK) - -/* extended-types: rather than continue to consume a bit per type, - * grab a chunk of the bits to use as integer type id. - */ -#define DRM_MODE_PROP_EXTENDED_TYPE 0x0000ffc0 -#define DRM_MODE_PROP_TYPE(n) ((n) << 6) -#define DRM_MODE_PROP_OBJECT DRM_MODE_PROP_TYPE(1) -#define DRM_MODE_PROP_SIGNED_RANGE DRM_MODE_PROP_TYPE(2) - -/* the PROP_ATOMIC flag is used to hide properties from userspace that - * is not aware of atomic properties. This is mostly to work around - * older userspace (DDX drivers) that read/write each prop they find, - * without being aware that this could be triggering a lengthy modeset. - */ -#define DRM_MODE_PROP_ATOMIC 0x80000000 - -/** - * struct drm_mode_property_enum - Description for an enum/bitfield entry. - * @value: numeric value for this enum entry. - * @name: symbolic name for this enum entry. - * - * See struct drm_property_enum for details. - */ -struct drm_mode_property_enum { - __u64 value; - char name[DRM_PROP_NAME_LEN]; -}; - -/** - * struct drm_mode_get_property - Get property metadata. - * - * User-space can perform a GETPROPERTY ioctl to retrieve information about a - * property. The same property may be attached to multiple objects, see - * "Modeset Base Object Abstraction". - * - * The meaning of the @values_ptr field changes depending on the property type. - * See &drm_property.flags for more details. - * - * The @enum_blob_ptr and @count_enum_blobs fields are only meaningful when the - * property has the type &DRM_MODE_PROP_ENUM or &DRM_MODE_PROP_BITMASK. For - * backwards compatibility, the kernel will always set @count_enum_blobs to - * zero when the property has the type &DRM_MODE_PROP_BLOB. User-space must - * ignore these two fields if the property has a different type. - * - * User-space is expected to retrieve values and enums by performing this ioctl - * at least twice: the first time to retrieve the number of elements, the - * second time to retrieve the elements themselves. - * - * To retrieve the number of elements, set @count_values and @count_enum_blobs - * to zero, then call the ioctl. @count_values will be updated with the number - * of elements. If the property has the type &DRM_MODE_PROP_ENUM or - * &DRM_MODE_PROP_BITMASK, @count_enum_blobs will be updated as well. - * - * To retrieve the elements themselves, allocate an array for @values_ptr and - * set @count_values to its capacity. If the property has the type - * &DRM_MODE_PROP_ENUM or &DRM_MODE_PROP_BITMASK, allocate an array for - * @enum_blob_ptr and set @count_enum_blobs to its capacity. Calling the ioctl - * again will fill the arrays. - */ -struct drm_mode_get_property { - /** @values_ptr: Pointer to a ``__u64`` array. */ - __u64 values_ptr; - /** @enum_blob_ptr: Pointer to a struct drm_mode_property_enum array. */ - __u64 enum_blob_ptr; - - /** - * @prop_id: Object ID of the property which should be retrieved. Set - * by the caller. - */ - __u32 prop_id; - /** - * @flags: ``DRM_MODE_PROP_*`` bitfield. See &drm_property.flags for - * a definition of the flags. - */ - __u32 flags; - /** - * @name: Symbolic property name. User-space should use this field to - * recognize properties. - */ - char name[DRM_PROP_NAME_LEN]; - - /** @count_values: Number of elements in @values_ptr. */ - __u32 count_values; - /** @count_enum_blobs: Number of elements in @enum_blob_ptr. */ - __u32 count_enum_blobs; -}; - -struct drm_mode_connector_set_property { - __u64 value; - __u32 prop_id; - __u32 connector_id; -}; - -#define DRM_MODE_OBJECT_CRTC 0xcccccccc -#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0 -#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0 -#define DRM_MODE_OBJECT_MODE 0xdededede -#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0 -#define DRM_MODE_OBJECT_FB 0xfbfbfbfb -#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb -#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee -#define DRM_MODE_OBJECT_ANY 0 - -struct drm_mode_obj_get_properties { - __u64 props_ptr; - __u64 prop_values_ptr; - __u32 count_props; - __u32 obj_id; - __u32 obj_type; -}; - -struct drm_mode_obj_set_property { - __u64 value; - __u32 prop_id; - __u32 obj_id; - __u32 obj_type; -}; - -struct drm_mode_get_blob { - __u32 blob_id; - __u32 length; - __u64 data; -}; - -struct drm_mode_fb_cmd { - __u32 fb_id; - __u32 width; - __u32 height; - __u32 pitch; - __u32 bpp; - __u32 depth; - /* driver specific handle */ - __u32 handle; -}; - -#define DRM_MODE_FB_INTERLACED (1<<0) /* for interlaced framebuffers */ -#define DRM_MODE_FB_MODIFIERS (1<<1) /* enables ->modifier[] */ - -/** - * struct drm_mode_fb_cmd2 - Frame-buffer metadata. - * - * This struct holds frame-buffer metadata. There are two ways to use it: - * - * - User-space can fill this struct and perform a &DRM_IOCTL_MODE_ADDFB2 - * ioctl to register a new frame-buffer. The new frame-buffer object ID will - * be set by the kernel in @fb_id. - * - User-space can set @fb_id and perform a &DRM_IOCTL_MODE_GETFB2 ioctl to - * fetch metadata about an existing frame-buffer. - * - * In case of planar formats, this struct allows up to 4 buffer objects with - * offsets and pitches per plane. The pitch and offset order are dictated by - * the format FourCC as defined by ``drm_fourcc.h``, e.g. NV12 is described as: - * - * YUV 4:2:0 image with a plane of 8-bit Y samples followed by an - * interleaved U/V plane containing 8-bit 2x2 subsampled colour difference - * samples. - * - * So it would consist of a Y plane at ``offsets[0]`` and a UV plane at - * ``offsets[1]``. - * - * To accommodate tiled, compressed, etc formats, a modifier can be specified. - * For more information see the "Format Modifiers" section. Note that even - * though it looks like we have a modifier per-plane, we in fact do not. The - * modifier for each plane must be identical. Thus all combinations of - * different data layouts for multi-plane formats must be enumerated as - * separate modifiers. - * - * All of the entries in @handles, @pitches, @offsets and @modifier must be - * zero when unused. Warning, for @offsets and @modifier zero can't be used to - * figure out whether the entry is used or not since it's a valid value (a zero - * offset is common, and a zero modifier is &DRM_FORMAT_MOD_LINEAR). - */ -struct drm_mode_fb_cmd2 { - /** @fb_id: Object ID of the frame-buffer. */ - __u32 fb_id; - /** @width: Width of the frame-buffer. */ - __u32 width; - /** @height: Height of the frame-buffer. */ - __u32 height; - /** - * @pixel_format: FourCC format code, see ``DRM_FORMAT_*`` constants in - * ``drm_fourcc.h``. - */ - __u32 pixel_format; - /** - * @flags: Frame-buffer flags (see &DRM_MODE_FB_INTERLACED and - * &DRM_MODE_FB_MODIFIERS). - */ - __u32 flags; - - /** - * @handles: GEM buffer handle, one per plane. Set to 0 if the plane is - * unused. The same handle can be used for multiple planes. - */ - __u32 handles[4]; - /** @pitches: Pitch (aka. stride) in bytes, one per plane. */ - __u32 pitches[4]; - /** @offsets: Offset into the buffer in bytes, one per plane. */ - __u32 offsets[4]; - /** - * @modifier: Format modifier, one per plane. See ``DRM_FORMAT_MOD_*`` - * constants in ``drm_fourcc.h``. All planes must use the same - * modifier. Ignored unless &DRM_MODE_FB_MODIFIERS is set in @flags. - */ - __u64 modifier[4]; -}; - -#define DRM_MODE_FB_DIRTY_ANNOTATE_COPY 0x01 -#define DRM_MODE_FB_DIRTY_ANNOTATE_FILL 0x02 -#define DRM_MODE_FB_DIRTY_FLAGS 0x03 - -#define DRM_MODE_FB_DIRTY_MAX_CLIPS 256 - -/* - * Mark a region of a framebuffer as dirty. - * - * Some hardware does not automatically update display contents - * as a hardware or software draw to a framebuffer. This ioctl - * allows userspace to tell the kernel and the hardware what - * regions of the framebuffer have changed. - * - * The kernel or hardware is free to update more then just the - * region specified by the clip rects. The kernel or hardware - * may also delay and/or coalesce several calls to dirty into a - * single update. - * - * Userspace may annotate the updates, the annotates are a - * promise made by the caller that the change is either a copy - * of pixels or a fill of a single color in the region specified. - * - * If the DRM_MODE_FB_DIRTY_ANNOTATE_COPY flag is given then - * the number of updated regions are half of num_clips given, - * where the clip rects are paired in src and dst. The width and - * height of each one of the pairs must match. - * - * If the DRM_MODE_FB_DIRTY_ANNOTATE_FILL flag is given the caller - * promises that the region specified of the clip rects is filled - * completely with a single color as given in the color argument. - */ - -struct drm_mode_fb_dirty_cmd { - __u32 fb_id; - __u32 flags; - __u32 color; - __u32 num_clips; - __u64 clips_ptr; -}; - -struct drm_mode_mode_cmd { - __u32 connector_id; - struct drm_mode_modeinfo mode; -}; - -#define DRM_MODE_CURSOR_BO 0x01 -#define DRM_MODE_CURSOR_MOVE 0x02 -#define DRM_MODE_CURSOR_FLAGS 0x03 - -/* - * depending on the value in flags different members are used. - * - * CURSOR_BO uses - * crtc_id - * width - * height - * handle - if 0 turns the cursor off - * - * CURSOR_MOVE uses - * crtc_id - * x - * y - */ -struct drm_mode_cursor { - __u32 flags; - __u32 crtc_id; - __s32 x; - __s32 y; - __u32 width; - __u32 height; - /* driver specific handle */ - __u32 handle; -}; - -struct drm_mode_cursor2 { - __u32 flags; - __u32 crtc_id; - __s32 x; - __s32 y; - __u32 width; - __u32 height; - /* driver specific handle */ - __u32 handle; - __s32 hot_x; - __s32 hot_y; -}; - -struct drm_mode_crtc_lut { - __u32 crtc_id; - __u32 gamma_size; - - /* pointers to arrays */ - __u64 red; - __u64 green; - __u64 blue; -}; - -struct drm_color_ctm { - /* - * Conversion matrix in S31.32 sign-magnitude - * (not two's complement!) format. - * - * out matrix in - * |R| |0 1 2| |R| - * |G| = |3 4 5| x |G| - * |B| |6 7 8| |B| - */ - __u64 matrix[9]; -}; - -struct drm_color_lut { - /* - * Values are mapped linearly to 0.0 - 1.0 range, with 0x0 == 0.0 and - * 0xffff == 1.0. - */ - __u16 red; - __u16 green; - __u16 blue; - __u16 reserved; -}; - -/** - * struct drm_plane_size_hint - Plane size hints - * @width: The width of the plane in pixel - * @height: The height of the plane in pixel - * - * The plane SIZE_HINTS property blob contains an - * array of struct drm_plane_size_hint. - */ -struct drm_plane_size_hint { - __u16 width; - __u16 height; -}; - -/** - * struct hdr_metadata_infoframe - HDR Metadata Infoframe Data. - * - * HDR Metadata Infoframe as per CTA 861.G spec. This is expected - * to match exactly with the spec. - * - * Userspace is expected to pass the metadata information as per - * the format described in this structure. - */ -struct hdr_metadata_infoframe { - /** - * @eotf: Electro-Optical Transfer Function (EOTF) - * used in the stream. - */ - __u8 eotf; - /** - * @metadata_type: Static_Metadata_Descriptor_ID. - */ - __u8 metadata_type; - /** - * @display_primaries: Color Primaries of the Data. - * These are coded as unsigned 16-bit values in units of - * 0.00002, where 0x0000 represents zero and 0xC350 - * represents 1.0000. - * @display_primaries.x: X coordinate of color primary. - * @display_primaries.y: Y coordinate of color primary. - */ - struct { - __u16 x, y; - } display_primaries[3]; - /** - * @white_point: White Point of Colorspace Data. - * These are coded as unsigned 16-bit values in units of - * 0.00002, where 0x0000 represents zero and 0xC350 - * represents 1.0000. - * @white_point.x: X coordinate of whitepoint of color primary. - * @white_point.y: Y coordinate of whitepoint of color primary. - */ - struct { - __u16 x, y; - } white_point; - /** - * @max_display_mastering_luminance: Max Mastering Display Luminance. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - __u16 max_display_mastering_luminance; - /** - * @min_display_mastering_luminance: Min Mastering Display Luminance. - * This value is coded as an unsigned 16-bit value in units of - * 0.0001 cd/m2, where 0x0001 represents 0.0001 cd/m2 and 0xFFFF - * represents 6.5535 cd/m2. - */ - __u16 min_display_mastering_luminance; - /** - * @max_cll: Max Content Light Level. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - __u16 max_cll; - /** - * @max_fall: Max Frame Average Light Level. - * This value is coded as an unsigned 16-bit value in units of 1 cd/m2, - * where 0x0001 represents 1 cd/m2 and 0xFFFF represents 65535 cd/m2. - */ - __u16 max_fall; -}; - -/** - * struct hdr_output_metadata - HDR output metadata - * - * Metadata Information to be passed from userspace - */ -struct hdr_output_metadata { - /** - * @metadata_type: Static_Metadata_Descriptor_ID. - */ - __u32 metadata_type; - /** - * @hdmi_metadata_type1: HDR Metadata Infoframe. - */ - union { - struct hdr_metadata_infoframe hdmi_metadata_type1; - }; -}; - -/** - * DRM_MODE_PAGE_FLIP_EVENT - * - * Request that the kernel sends back a vblank event (see - * struct drm_event_vblank) with the &DRM_EVENT_FLIP_COMPLETE type when the - * page-flip is done. - */ -#define DRM_MODE_PAGE_FLIP_EVENT 0x01 -/** - * DRM_MODE_PAGE_FLIP_ASYNC - * - * Request that the page-flip is performed as soon as possible, ie. with no - * delay due to waiting for vblank. This may cause tearing to be visible on - * the screen. - * - * When used with atomic uAPI, the driver will return an error if the hardware - * doesn't support performing an asynchronous page-flip for this update. - * User-space should handle this, e.g. by falling back to a regular page-flip. - * - * Note, some hardware might need to perform one last synchronous page-flip - * before being able to switch to asynchronous page-flips. As an exception, - * the driver will return success even though that first page-flip is not - * asynchronous. - */ -#define DRM_MODE_PAGE_FLIP_ASYNC 0x02 -#define DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE 0x4 -#define DRM_MODE_PAGE_FLIP_TARGET_RELATIVE 0x8 -#define DRM_MODE_PAGE_FLIP_TARGET (DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE | \ - DRM_MODE_PAGE_FLIP_TARGET_RELATIVE) -/** - * DRM_MODE_PAGE_FLIP_FLAGS - * - * Bitmask of flags suitable for &drm_mode_crtc_page_flip_target.flags. - */ -#define DRM_MODE_PAGE_FLIP_FLAGS (DRM_MODE_PAGE_FLIP_EVENT | \ - DRM_MODE_PAGE_FLIP_ASYNC | \ - DRM_MODE_PAGE_FLIP_TARGET) - -/* - * Request a page flip on the specified crtc. - * - * This ioctl will ask KMS to schedule a page flip for the specified - * crtc. Once any pending rendering targeting the specified fb (as of - * ioctl time) has completed, the crtc will be reprogrammed to display - * that fb after the next vertical refresh. The ioctl returns - * immediately, but subsequent rendering to the current fb will block - * in the execbuffer ioctl until the page flip happens. If a page - * flip is already pending as the ioctl is called, EBUSY will be - * returned. - * - * Flag DRM_MODE_PAGE_FLIP_EVENT requests that drm sends back a vblank - * event (see drm.h: struct drm_event_vblank) when the page flip is - * done. The user_data field passed in with this ioctl will be - * returned as the user_data field in the vblank event struct. - * - * Flag DRM_MODE_PAGE_FLIP_ASYNC requests that the flip happen - * 'as soon as possible', meaning that it not delay waiting for vblank. - * This may cause tearing on the screen. - * - * The reserved field must be zero. - */ - -struct drm_mode_crtc_page_flip { - __u32 crtc_id; - __u32 fb_id; - __u32 flags; - __u32 reserved; - __u64 user_data; -}; - -/* - * Request a page flip on the specified crtc. - * - * Same as struct drm_mode_crtc_page_flip, but supports new flags and - * re-purposes the reserved field: - * - * The sequence field must be zero unless either of the - * DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE/RELATIVE flags is specified. When - * the ABSOLUTE flag is specified, the sequence field denotes the absolute - * vblank sequence when the flip should take effect. When the RELATIVE - * flag is specified, the sequence field denotes the relative (to the - * current one when the ioctl is called) vblank sequence when the flip - * should take effect. NOTE: DRM_IOCTL_WAIT_VBLANK must still be used to - * make sure the vblank sequence before the target one has passed before - * calling this ioctl. The purpose of the - * DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE/RELATIVE flags is merely to clarify - * the target for when code dealing with a page flip runs during a - * vertical blank period. - */ - -struct drm_mode_crtc_page_flip_target { - __u32 crtc_id; - __u32 fb_id; - __u32 flags; - __u32 sequence; - __u64 user_data; -}; - -/** - * struct drm_mode_create_dumb - Create a KMS dumb buffer for scanout. - * @height: buffer height in pixels - * @width: buffer width in pixels - * @bpp: bits per pixel - * @flags: must be zero - * @handle: buffer object handle - * @pitch: number of bytes between two consecutive lines - * @size: size of the whole buffer in bytes - * - * User-space fills @height, @width, @bpp and @flags. If the IOCTL succeeds, - * the kernel fills @handle, @pitch and @size. - */ -struct drm_mode_create_dumb { - __u32 height; - __u32 width; - __u32 bpp; - __u32 flags; - - __u32 handle; - __u32 pitch; - __u64 size; -}; - -/* set up for mmap of a dumb scanout buffer */ -struct drm_mode_map_dumb { - /** Handle for the object being mapped. */ - __u32 handle; - __u32 pad; - /** - * Fake offset to use for subsequent mmap call - * - * This is a fixed-size type for 32/64 compatibility. - */ - __u64 offset; -}; - -struct drm_mode_destroy_dumb { - __u32 handle; -}; - -/** - * DRM_MODE_ATOMIC_TEST_ONLY - * - * Do not apply the atomic commit, instead check whether the hardware supports - * this configuration. - * - * See &drm_mode_config_funcs.atomic_check for more details on test-only - * commits. - */ -#define DRM_MODE_ATOMIC_TEST_ONLY 0x0100 -/** - * DRM_MODE_ATOMIC_NONBLOCK - * - * Do not block while applying the atomic commit. The &DRM_IOCTL_MODE_ATOMIC - * IOCTL returns immediately instead of waiting for the changes to be applied - * in hardware. Note, the driver will still check that the update can be - * applied before retuning. - */ -#define DRM_MODE_ATOMIC_NONBLOCK 0x0200 -/** - * DRM_MODE_ATOMIC_ALLOW_MODESET - * - * Allow the update to result in temporary or transient visible artifacts while - * the update is being applied. Applying the update may also take significantly - * more time than a page flip. All visual artifacts will disappear by the time - * the update is completed, as signalled through the vblank event's timestamp - * (see struct drm_event_vblank). - * - * This flag must be set when the KMS update might cause visible artifacts. - * Without this flag such KMS update will return a EINVAL error. What kind of - * update may cause visible artifacts depends on the driver and the hardware. - * User-space that needs to know beforehand if an update might cause visible - * artifacts can use &DRM_MODE_ATOMIC_TEST_ONLY without - * &DRM_MODE_ATOMIC_ALLOW_MODESET to see if it fails. - * - * To the best of the driver's knowledge, visual artifacts are guaranteed to - * not appear when this flag is not set. Some sinks might display visual - * artifacts outside of the driver's control. - */ -#define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400 - -/** - * DRM_MODE_ATOMIC_FLAGS - * - * Bitfield of flags accepted by the &DRM_IOCTL_MODE_ATOMIC IOCTL in - * &drm_mode_atomic.flags. - */ -#define DRM_MODE_ATOMIC_FLAGS (\ - DRM_MODE_PAGE_FLIP_EVENT |\ - DRM_MODE_PAGE_FLIP_ASYNC |\ - DRM_MODE_ATOMIC_TEST_ONLY |\ - DRM_MODE_ATOMIC_NONBLOCK |\ - DRM_MODE_ATOMIC_ALLOW_MODESET) - -struct drm_mode_atomic { - __u32 flags; - __u32 count_objs; - __u64 objs_ptr; - __u64 count_props_ptr; - __u64 props_ptr; - __u64 prop_values_ptr; - __u64 reserved; - __u64 user_data; -}; - -struct drm_format_modifier_blob { -#define FORMAT_BLOB_CURRENT 1 - /* Version of this blob format */ - __u32 version; - - /* Flags */ - __u32 flags; - - /* Number of fourcc formats supported */ - __u32 count_formats; - - /* Where in this blob the formats exist (in bytes) */ - __u32 formats_offset; - - /* Number of drm_format_modifiers */ - __u32 count_modifiers; - - /* Where in this blob the modifiers exist (in bytes) */ - __u32 modifiers_offset; - - /* __u32 formats[] */ - /* struct drm_format_modifier modifiers[] */ -}; - -struct drm_format_modifier { - /* Bitmask of formats in get_plane format list this info applies to. The - * offset allows a sliding window of which 64 formats (bits). - * - * Some examples: - * In today's world with < 65 formats, and formats 0, and 2 are - * supported - * 0x0000000000000005 - * ^-offset = 0, formats = 5 - * - * If the number formats grew to 128, and formats 98-102 are - * supported with the modifier: - * - * 0x0000007c00000000 0000000000000000 - * ^ - * |__offset = 64, formats = 0x7c00000000 - * - */ - __u64 formats; - __u32 offset; - __u32 pad; - - /* The modifier that applies to the >get_plane format list bitmask. */ - __u64 modifier; -}; - -/** - * struct drm_mode_create_blob - Create New blob property - * - * Create a new 'blob' data property, copying length bytes from data pointer, - * and returning new blob ID. - */ -struct drm_mode_create_blob { - /** @data: Pointer to data to copy. */ - __u64 data; - /** @length: Length of data to copy. */ - __u32 length; - /** @blob_id: Return: new property ID. */ - __u32 blob_id; -}; - -/** - * struct drm_mode_destroy_blob - Destroy user blob - * @blob_id: blob_id to destroy - * - * Destroy a user-created blob property. - * - * User-space can release blobs as soon as they do not need to refer to them by - * their blob object ID. For instance, if you are using a MODE_ID blob in an - * atomic commit and you will not make another commit re-using the same ID, you - * can destroy the blob as soon as the commit has been issued, without waiting - * for it to complete. - */ -struct drm_mode_destroy_blob { - __u32 blob_id; -}; - -/** - * struct drm_mode_create_lease - Create lease - * - * Lease mode resources, creating another drm_master. - * - * The @object_ids array must reference at least one CRTC, one connector and - * one plane if &DRM_CLIENT_CAP_UNIVERSAL_PLANES is enabled. Alternatively, - * the lease can be completely empty. - */ -struct drm_mode_create_lease { - /** @object_ids: Pointer to array of object ids (__u32) */ - __u64 object_ids; - /** @object_count: Number of object ids */ - __u32 object_count; - /** @flags: flags for new FD (O_CLOEXEC, etc) */ - __u32 flags; - - /** @lessee_id: Return: unique identifier for lessee. */ - __u32 lessee_id; - /** @fd: Return: file descriptor to new drm_master file */ - __u32 fd; -}; - -/** - * struct drm_mode_list_lessees - List lessees - * - * List lesses from a drm_master. - */ -struct drm_mode_list_lessees { - /** - * @count_lessees: Number of lessees. - * - * On input, provides length of the array. - * On output, provides total number. No - * more than the input number will be written - * back, so two calls can be used to get - * the size and then the data. - */ - __u32 count_lessees; - /** @pad: Padding. */ - __u32 pad; - - /** - * @lessees_ptr: Pointer to lessees. - * - * Pointer to __u64 array of lessee ids - */ - __u64 lessees_ptr; -}; - -/** - * struct drm_mode_get_lease - Get Lease - * - * Get leased objects. - */ -struct drm_mode_get_lease { - /** - * @count_objects: Number of leased objects. - * - * On input, provides length of the array. - * On output, provides total number. No - * more than the input number will be written - * back, so two calls can be used to get - * the size and then the data. - */ - __u32 count_objects; - /** @pad: Padding. */ - __u32 pad; - - /** - * @objects_ptr: Pointer to objects. - * - * Pointer to __u32 array of object ids. - */ - __u64 objects_ptr; -}; - -/** - * struct drm_mode_revoke_lease - Revoke lease - */ -struct drm_mode_revoke_lease { - /** @lessee_id: Unique ID of lessee */ - __u32 lessee_id; -}; - -/** - * struct drm_mode_rect - Two dimensional rectangle. - * @x1: Horizontal starting coordinate (inclusive). - * @y1: Vertical starting coordinate (inclusive). - * @x2: Horizontal ending coordinate (exclusive). - * @y2: Vertical ending coordinate (exclusive). - * - * With drm subsystem using struct drm_rect to manage rectangular area this - * export it to user-space. - * - * Currently used by drm_mode_atomic blob property FB_DAMAGE_CLIPS. - */ -struct drm_mode_rect { - __s32 x1; - __s32 y1; - __s32 x2; - __s32 y2; -}; - -/** - * struct drm_mode_closefb - * @fb_id: Framebuffer ID. - * @pad: Must be zero. - */ -struct drm_mode_closefb { - __u32 fb_id; - __u32 pad; -}; - -#if defined(__cplusplus) -} -#endif - -#endif diff --git a/libc/musl-overlay/include/linux/input-event-codes.h b/libc/musl-overlay/include/linux/input-event-codes.h index 31d318d4e5..25329cc0a7 100644 --- a/libc/musl-overlay/include/linux/input-event-codes.h +++ b/libc/musl-overlay/include/linux/input-event-codes.h @@ -8,7 +8,9 @@ * `KeyboardEvent.code`; values >248 (KEY_BUTTONCONFIG, KEY_VENDOR * range, etc.) are not browser-reachable and aren't vendored. * - * Any change here is part of the kernel ABI — bump ABI_VERSION. + * Any change here is part of the kernel ABI. Adding a code is additive + * and needs no ABI_VERSION bump; changing or removing one does. See + * docs/abi-versioning.md. */ #ifndef _LINUX_INPUT_EVENT_CODES_H #define _LINUX_INPUT_EVENT_CODES_H 1 @@ -21,6 +23,12 @@ #define EV_ABS 0x03 #define EV_MSC 0x04 +/* The `*_MAX` values size the capability bitsets an evdev client reads + * with EVIOCGBIT. They describe the code space, not what this kernel + * synthesises, so they carry upstream's numbers even where the range + * above them is not vendored. */ +#define EV_MAX 0x1f + /* --- SYN codes (struct input_event.code when type == EV_SYN) --------- */ #define SYN_REPORT 0 @@ -194,13 +202,35 @@ #define KEY_BRIGHTNESSUP 225 #define KEY_MICMUTE 248 +/* Above the browser-reachable range. SDL2 matches against these when it + * classifies a device's key bitset. */ +#define KEY_OK 0x160 +#define KEY_ALS_TOGGLE 0x230 +#define KEY_MAX 0x2ff + /* --- BTN_* codes (button class; reuse the EV_KEY event type) --------- */ +/* `BTN_MISC` and `BTN_MOUSE` are upstream's names for the first code in + * their class, so they share a value with the button that follows. */ +#define BTN_MISC 0x100 +#define BTN_1 0x101 +#define BTN_MOUSE 0x110 #define BTN_LEFT 0x110 #define BTN_RIGHT 0x111 #define BTN_MIDDLE 0x112 #define BTN_SIDE 0x113 #define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 +#define BTN_TRIGGER 0x120 +#define BTN_A 0x130 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_DPAD_UP 0x220 +#define BTN_TRIGGER_HAPPY 0x2c0 /* --- REL_* codes (relative axes; EV_REL records carry these) --------- */ @@ -208,11 +238,29 @@ #define REL_Y 0x01 #define REL_HWHEEL 0x06 #define REL_WHEEL 0x08 +#define REL_WHEEL_HI_RES 0x0b +#define REL_HWHEEL_HI_RES 0x0c +#define REL_MAX 0x0f /* --- ABS_* codes (absolute axes; EV_ABS records carry these) --------- */ #define ABS_X 0x00 #define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_MT_SLOT 0x2f +#define ABS_MT_POSITION_X 0x35 +#define ABS_MT_POSITION_Y 0x36 +#define ABS_MT_TRACKING_ID 0x39 +#define ABS_MT_PRESSURE 0x3a +#define ABS_MAX 0x3f /* --- BUS_* constants (subset) ---------------------------------------- */ diff --git a/libc/musl-overlay/include/xf86drm.h b/libc/musl-overlay/include/xf86drm.h deleted file mode 100644 index 81cec87057..0000000000 --- a/libc/musl-overlay/include/xf86drm.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Minimal for wasm-posix-kernel. - * - * Declares the libdrm user-space entry points implemented by - * libc/glue/libdrm_stub.c. The shape mirrors upstream libdrm - * (https://gitlab.freedesktop.org/mesa/drm) so user programs that - * `#include ` and `-ldrm` against this sysroot compile - * unchanged from their Linux counterpart. - * - * Phase C of the DRI buffer-sharing plan - * (docs/plans/2026-05-25-dri-buffer-sharing-plan.md §C). The shim - * surface is intentionally small — Phase A wired the kernel ioctl - * surface, so we only need wrappers, not the full libdrm. - */ - -#ifndef _XF86DRM_H -#define _XF86DRM_H 1 - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Upstream libdrm uses `int` (signed) here, even though the wire - * format's `struct drm_version` uses `__kernel_size_t` (unsigned). - * Match upstream so source code is portable byte-for-byte. */ -typedef struct _drmVersion { - int version_major; - int version_minor; - int version_patchlevel; - int name_len; - char *name; - int date_len; - char *date; - int desc_len; - char *desc; -} drmVersion, *drmVersionPtr; - -/* EINTR-retrying wrapper around ioctl(2). Returns whatever the - * underlying ioctl(3) returns; never returns -1/EINTR. */ -int drmIoctl(int fd, unsigned long request, void *arg); - -/* DRM_IOCTL_VERSION wrapper. Returns a heap-allocated drmVersion; - * caller must drmFreeVersion(). Returns NULL on ioctl/alloc error - * (errno preserved). The version triple comes from the kernel; the - * name/desc/date strings are synthesised client-side (the kernel - * returns zero-length strings — see Q2 in - * docs/plans/2026-05-20-dri-session-handoff-19.md). */ -drmVersionPtr drmGetVersion(int fd); - -/* Frees a drmVersionPtr previously returned by drmGetVersion(). */ -void drmFreeVersion(drmVersionPtr v); - -/* DRM_IOCTL_GEM_CLOSE wrapper. Closes a GEM buffer object handle - * (per-fd, allocated by DRM_IOCTL_MODE_CREATE_DUMB). Returns 0 on - * success, -1/errno on failure. */ -int drmCloseBufferHandle(int fd, uint32_t handle); - -/* DRM_IOCTL_SET_MASTER / DRM_IOCTL_DROP_MASTER wrappers. */ -int drmSetMaster(int fd); -int drmDropMaster(int fd); - -#ifdef __cplusplus -} -#endif - -#endif /* _XF86DRM_H */ diff --git a/libc/musl-overlay/include/xf86drmMode.h b/libc/musl-overlay/include/xf86drmMode.h deleted file mode 100644 index 4ca5a8f9f9..0000000000 --- a/libc/musl-overlay/include/xf86drmMode.h +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Minimal for wasm-posix-kernel. - * - * KMS userland API mirroring upstream libdrm. Wrappers live in - * libc/glue/libdrm_stub.c; the underlying ioctls are handled by - * crates/kernel/src/syscalls.rs::handle_dri_card_ioctl (Phase A of - * docs/plans/2026-06-08-dri-kms-plan.md). - */ - -#ifndef _XF86DRMMODE_H -#define _XF86DRMMODE_H 1 - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - DRM_MODE_CONNECTED = 1, - DRM_MODE_DISCONNECTED = 2, - DRM_MODE_UNKNOWNCONNECTION = 3, -} drmModeConnection; - -typedef enum { - DRM_MODE_SUBPIXEL_UNKNOWN = 1, - DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2, - DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3, - DRM_MODE_SUBPIXEL_VERTICAL_RGB = 4, - DRM_MODE_SUBPIXEL_VERTICAL_BGR = 5, - DRM_MODE_SUBPIXEL_NONE = 6, -} drmModeSubPixel; - -typedef struct _drmModeModeInfo { - uint32_t clock; - uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; - uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; - uint32_t vrefresh; - uint32_t flags; - uint32_t type; - char name[DRM_DISPLAY_MODE_LEN]; -} drmModeModeInfo, *drmModeModeInfoPtr; - -typedef struct _drmModeRes { - int count_fbs; - uint32_t *fbs; - int count_crtcs; - uint32_t *crtcs; - int count_connectors; - uint32_t *connectors; - int count_encoders; - uint32_t *encoders; - uint32_t min_width, max_width; - uint32_t min_height, max_height; -} drmModeRes, *drmModeResPtr; - -typedef struct _drmModeCrtc { - uint32_t crtc_id; - uint32_t buffer_id; - uint32_t x, y; - uint32_t width, height; - int mode_valid; - drmModeModeInfo mode; - int gamma_size; -} drmModeCrtc, *drmModeCrtcPtr; - -typedef struct _drmModeEncoder { - uint32_t encoder_id; - uint32_t encoder_type; - uint32_t crtc_id; - uint32_t possible_crtcs; - uint32_t possible_clones; -} drmModeEncoder, *drmModeEncoderPtr; - -typedef struct _drmModeConnector { - uint32_t connector_id; - uint32_t encoder_id; - uint32_t connector_type; - uint32_t connector_type_id; - drmModeConnection connection; - uint32_t mmWidth, mmHeight; - drmModeSubPixel subpixel; - int count_modes; - drmModeModeInfoPtr modes; - int count_encoders; - uint32_t *encoders; -} drmModeConnector, *drmModeConnectorPtr; - -typedef union _drmVBlank { - struct drm_wait_vblank_request request; - struct drm_wait_vblank_reply reply; -} drmVBlank, *drmVBlankPtr; - -typedef struct _drmEventContext { - int version; - void (*vblank_handler)(int fd, - unsigned int sequence, - unsigned int tv_sec, - unsigned int tv_usec, - void *user_data); - void (*page_flip_handler)(int fd, - unsigned int sequence, - unsigned int tv_sec, - unsigned int tv_usec, - void *user_data); - void (*page_flip_handler2)(int fd, - unsigned int sequence, - unsigned int tv_sec, - unsigned int tv_usec, - unsigned int crtc_id, - void *user_data); - void (*sequence_handler)(int fd, - uint64_t sequence, - uint64_t ns, - uint64_t user_data); -} drmEventContext, *drmEventContextPtr; - -drmModeResPtr drmModeGetResources(int fd); -void drmModeFreeResources(drmModeResPtr ptr); - -drmModeCrtcPtr drmModeGetCrtc(int fd, uint32_t crtc_id); -void drmModeFreeCrtc(drmModeCrtcPtr ptr); -int drmModeSetCrtc(int fd, uint32_t crtc_id, - uint32_t fb_id, - uint32_t x, uint32_t y, - uint32_t *connectors, int count, - drmModeModeInfoPtr mode); - -drmModeConnectorPtr drmModeGetConnector(int fd, uint32_t connector_id); -void drmModeFreeConnector(drmModeConnectorPtr ptr); - -drmModeEncoderPtr drmModeGetEncoder(int fd, uint32_t encoder_id); -void drmModeFreeEncoder(drmModeEncoderPtr ptr); - -int drmModeAddFB2(int fd, - uint32_t width, uint32_t height, - uint32_t pixel_format, - uint32_t handles[4], - uint32_t pitches[4], - uint32_t offsets[4], - uint32_t *buf_id, uint32_t flags); -int drmModeRmFB(int fd, uint32_t bufferId); - -int drmModePageFlip(int fd, uint32_t crtc_id, - uint32_t fb_id, - uint32_t flags, void *user_data); - -int drmWaitVBlank(int fd, drmVBlankPtr vbl); -int drmHandleEvent(int fd, drmEventContextPtr ctx); - -#ifdef __cplusplus -} -#endif - -#endif /* _XF86DRMMODE_H */ diff --git a/packages/registry/evdev-demo/build-evdev-demo.sh b/packages/registry/evdev-demo/build-evdev-demo.sh deleted file mode 100755 index 31e017e973..0000000000 --- a/packages/registry/evdev-demo/build-evdev-demo.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -HERE="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$HERE/../../.." && pwd)" -# shellcheck source=/dev/null -source "$REPO_ROOT/scripts/package-build-roots.sh" -kandelo_package_prepare_build_roots "$HERE" wasm32 -kandelo_package_select_source_root "$REPO_ROOT" -SOURCE_ROOT="$KANDELO_PACKAGE_SOURCE_ROOT" -EVDEV_DEMO_SOURCE="$SOURCE_ROOT/programs/evdev_demo.c" -WORK_DIR="$KANDELO_PACKAGE_WORK_DIR" -OUT_BIN="$WORK_DIR/evdev_demo.wasm" - -if [ ! -f "$EVDEV_DEMO_SOURCE" ] || [ -L "$EVDEV_DEMO_SOURCE" ]; then - echo "ERROR: evdev_demo source must be a regular file: $EVDEV_DEMO_SOURCE" >&2 - exit 1 -fi - -# A resolver/Formula caller owns the declared work and output roots. Keep the -# reviewed checkout read-only and suppress the developer-only local mirror. -if [ -n "${WASM_POSIX_DEP_WORK_DIR:-}" ] && [ -n "${WASM_POSIX_DEP_OUT_DIR:-}" ]; then - export WASM_POSIX_INSTALL_LOCAL_MIRROR=0 - export WASM_POSIX_INSTALL_FORK_INSTRUMENTATION=auto -fi - -source "$REPO_ROOT/sdk/activate.sh" -export WASM_POSIX_SYSROOT="$REPO_ROOT/sysroot" - -if [ ! -f "$WASM_POSIX_SYSROOT/include/linux/input.h" ]; then - echo "ERROR: the vendored evdev headers are missing from the sysroot." >&2 - echo "Run: scripts/dev-shell.sh bash scripts/build-musl.sh" >&2 - exit 1 -fi - -echo "==> Building evdev_demo..." -wasm32posix-cc \ - -std=c11 \ - -O2 \ - -Wall \ - -Wextra \ - -Wno-unused-parameter \ - -D_DEFAULT_SOURCE \ - "$EVDEV_DEMO_SOURCE" \ - -o "$OUT_BIN" - -cd "$REPO_ROOT" -source "$REPO_ROOT/scripts/install-local-binary.sh" -install_local_binary evdev-demo "$OUT_BIN" evdev_demo.wasm diff --git a/packages/registry/evdev-demo/build.toml b/packages/registry/evdev-demo/build.toml deleted file mode 100644 index eab84942c0..0000000000 --- a/packages/registry/evdev-demo/build.toml +++ /dev/null @@ -1,12 +0,0 @@ -script_path = "packages/registry/evdev-demo/build-evdev-demo.sh" -inputs = [ - "packages/registry/evdev-demo/build-evdev-demo.sh", - "programs/evdev_demo.c", - "libc/musl-overlay/include/linux/input.h", - "libc/musl-overlay/include/linux/input-event-codes.h", - "scripts/build-musl.sh", - "scripts/package-build-roots.sh", -] -repo_url = "https://github.com/Automattic/kandelo.git" -commit = "" -revision = 1 diff --git a/packages/registry/evdev-demo/package.toml b/packages/registry/evdev-demo/package.toml deleted file mode 100644 index 87994ad491..0000000000 --- a/packages/registry/evdev-demo/package.toml +++ /dev/null @@ -1,27 +0,0 @@ -kind = "program" -# Runtime backing for `/?demo=evdev`. The browser app imports the output -# through `@binaries`, so it needs a registry owner like every other -# product artifact — see scripts/browser-binary-package-roots.mjs. The -# source stays in programs/ so scripts/build-programs.sh keeps building -# the local test fixture, the same split modeset uses. -name = "evdev-demo" -version = "0.1.0" -kernel_abi = 43 -depends_on = [] -arches = ["wasm32"] - -[source] -url = "https://github.com/Automattic/kandelo" -sha256 = "0000000000000000000000000000000000000000000000000000000000000000" -provider = "repository" - -[license] -spdx = "GPL-2.0-or-later" -url = "https://github.com/Automattic/kandelo/blob/main/COPYING" - -[build] -script_path = "packages/registry/evdev-demo/build-evdev-demo.sh" - -[[outputs]] -name = "evdev_demo" -wasm = "evdev_demo.wasm" diff --git a/packages/registry/libdrm/build-libdrm.sh b/packages/registry/libdrm/build-libdrm.sh new file mode 100755 index 0000000000..d087dd63dc --- /dev/null +++ b/packages/registry/libdrm/build-libdrm.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# Build libdrm (libdrm.a) — KMS-side subset only — for Kandelo. +# +# Wraps the kernel's DRM_IOCTL_MODE_* surface in the standard libdrm +# API (drmModeGetResources, drmModeAddFB2, drmModePageFlip, +# drmHandleEvent) that SDL2's KMSDRM video backend +# (`src/video/kmsdrm/SDL_kmsdrmvideo.c`) calls. +# +# We bypass upstream's meson and skip the per-vendor subdirs +# (libdrm_amdgpu, libdrm_radeon, libdrm_intel, libdrm_nouveau, …) — +# none of those run on the wasm32 kernel and they pull in +# vendor-specific ioctl tables we don't ship. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/kandelo-libdrm.XXXXXX")" +trap 'rm -rf "$WORK_DIR"' EXIT + +# shellcheck source=/dev/null +source "$REPO_ROOT/sdk/activate.sh" + +LIBDRM_VERSION="${WASM_POSIX_DEP_VERSION:-2.4.120}" +SOURCE_URL="${WASM_POSIX_DEP_SOURCE_URL:-https://dri.freedesktop.org/libdrm/libdrm-${LIBDRM_VERSION}.tar.xz}" +SOURCE_SHA256="${WASM_POSIX_DEP_SOURCE_SHA256:-3bf55363f76c7250946441ab51d3a6cc0ae518055c0ff017324ab76cdefb327a}" +INSTALL_DIR="${WASM_POSIX_DEP_OUT_DIR:?WASM_POSIX_DEP_OUT_DIR must name the resolver staging directory}" +TARGET_ARCH="${WASM_POSIX_DEP_TARGET_ARCH:-wasm32}" + +if [ "$TARGET_ARCH" != "wasm32" ]; then + echo "ERROR: libdrm currently supports only wasm32, got $TARGET_ARCH" >&2 + exit 1 +fi + +export WASM_POSIX_SYSROOT="${WASM_POSIX_SYSROOT:-$REPO_ROOT/sysroot}" +CC=wasm32posix-cc +AR=wasm32posix-ar +for tool in "$CC" "$AR" curl tar shasum python3; do + command -v "$tool" >/dev/null || { + echo "ERROR: required build tool not found: $tool" >&2 + exit 1 + } +done + +TARBALL="$WORK_DIR/libdrm.tar.xz" +SRC_DIR="$WORK_DIR/source" +BUILD_DIR="$WORK_DIR/build" +REPRO_FLAGS="-ffile-prefix-map=$WORK_DIR=/usr/src/libdrm -fdebug-prefix-map=$WORK_DIR=/usr/src/libdrm -fmacro-prefix-map=$WORK_DIR=/usr/src/libdrm" + +echo "==> Downloading libdrm $LIBDRM_VERSION..." +curl --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors \ + -fsSL "$SOURCE_URL" -o "$TARBALL" +echo "$SOURCE_SHA256 $TARBALL" | shasum -a 256 -c - +mkdir -p "$SRC_DIR" "$BUILD_DIR" "$INSTALL_DIR/lib" \ + "$INSTALL_DIR/include/drm" "$INSTALL_DIR/include/libdrm" +tar xJf "$TARBALL" -C "$SRC_DIR" --strip-components=1 + +# Stage the KMS-side sources into a flat build dir — only the four +# files SDL2's KMSDRM backend pulls in transitively, plus the UAPI +# headers they include as `` / ``. +echo "==> Staging the libdrm KMS subset..." +for f in xf86drm.c xf86drmMode.c xf86drmHash.c xf86drmRandom.c \ + xf86drm.h xf86drmMode.h xf86drmHash.h xf86drmRandom.h \ + libdrm_macros.h util_math.h \ + include/drm/drm.h include/drm/drm_mode.h \ + include/drm/drm_fourcc.h; do + cp "$SRC_DIR/$f" "$BUILD_DIR/" +done + +python3 "$SRC_DIR/gen_table_fourcc.py" \ + "$BUILD_DIR/drm_fourcc.h" \ + "$BUILD_DIR/generated_static_table_fourcc.h" + +# The DRM UAPI headers open with `#include ` for the +# kernel-style __u8/__u32/__s64 typedefs and `#include ` +# for the _IOC()/_IO*() macros. The Linux kernel ships both; musl does +# not, since they are UAPI and not libc surface. Drop forwarding shims +# so the kernel-side scalar names map onto C99 fixed-width types and +# resolves to the same macros bits/ioctl.h already +# defines. +mkdir -p "$BUILD_DIR/linux" "$BUILD_DIR/asm" +cat > "$BUILD_DIR/asm/ioctl.h" <<'EOF' +#ifndef _ASM_IOCTL_H_LIBDRM_SHIM +#define _ASM_IOCTL_H_LIBDRM_SHIM +#include +#endif +EOF +cat > "$BUILD_DIR/linux/types.h" <<'EOF' +#ifndef _LINUX_TYPES_H_LIBDRM_SHIM +#define _LINUX_TYPES_H_LIBDRM_SHIM +#include +#include +typedef uint8_t __u8; +typedef int8_t __s8; +typedef uint16_t __u16; +typedef int16_t __s16; +typedef uint32_t __u32; +typedef int32_t __s32; +typedef uint64_t __u64; +typedef int64_t __s64; +typedef uint16_t __le16; +typedef uint16_t __be16; +typedef uint32_t __le32; +typedef uint32_t __be32; +typedef uint64_t __le64; +typedef uint64_t __be64; +typedef size_t __kernel_size_t; +typedef ssize_t __kernel_ssize_t; +typedef long __kernel_long_t; +typedef unsigned long __kernel_ulong_t; +#endif +EOF + +# Feature-test macros that: +# * select libdrm's Linux ioctl-macro flavour — xf86drm.h gates +# DRM_IOC_READ / DRM_IOC_WRITE on __linux__, and the BSD branch +# references IOC_OUT / IOC_IN from , which musl does +# not ship; +# * point libdrm at musl's for major()/minor() — +# xf86drm.c only pulls those macros in when one of +# MAJOR_IN_MKDEV / MAJOR_IN_SYSMACROS is defined, and musl has the +# sysmacros form; +# * disable the host-OS detection paths libdrm invokes only from +# error and init code (sysctl-based device discovery via +# HAVE_SYS_SYSCTL_H). The wasm sysroot has no PCI bus, no +# sysctl(), and no /dev/pci, so those probes are dead code here. +cat > "$BUILD_DIR/xf86drm_compat.h" <<'EOF' +/* libdrm-KMS feature-test compat header — see build-libdrm.sh. */ + +#ifndef __linux__ +#define __linux__ 1 +#endif + +#define MAJOR_IN_SYSMACROS 1 +#define HAVE_SYS_SYSCTL_H 0 +#define HAVE_VISIBILITY 1 +EOF + +echo "==> Compiling the libdrm KMS subset..." +( + cd "$BUILD_DIR" + CFLAGS=( + -O2 -std=gnu11 + -include xf86drm_compat.h + -I. + $REPRO_FLAGS + ) + for src in xf86drm.c xf86drmMode.c xf86drmHash.c xf86drmRandom.c; do + "$CC" -c "${CFLAGS[@]}" "$src" -o "${src%.c}.o" + done + "$AR" rcs "$INSTALL_DIR/lib/libdrm.a" \ + xf86drm.o xf86drmMode.o xf86drmHash.o xf86drmRandom.o +) + +# UAPI headers under include/drm/ (consumers: #include , +# , ). Public libdrm headers under +# include/libdrm/ (consumers: #include , ) — +# the split matches upstream's pkg-config --cflags. +echo "==> Installing headers..." +cp "$SRC_DIR/include/drm/drm.h" "$INSTALL_DIR/include/drm/" +cp "$SRC_DIR/include/drm/drm_mode.h" "$INSTALL_DIR/include/drm/" +cp "$SRC_DIR/include/drm/drm_fourcc.h" "$INSTALL_DIR/include/drm/" +cp "$SRC_DIR/xf86drm.h" "$INSTALL_DIR/include/libdrm/" +cp "$SRC_DIR/xf86drmMode.h" "$INSTALL_DIR/include/libdrm/" + +test -f "$INSTALL_DIR/lib/libdrm.a" +test -f "$INSTALL_DIR/include/libdrm/xf86drmMode.h" +test -f "$INSTALL_DIR/include/drm/drm_fourcc.h" +echo "==> libdrm $LIBDRM_VERSION KMS-subset package complete" diff --git a/packages/registry/libdrm/build.toml b/packages/registry/libdrm/build.toml new file mode 100644 index 0000000000..1672e25e7e --- /dev/null +++ b/packages/registry/libdrm/build.toml @@ -0,0 +1,7 @@ +script_path = "packages/registry/libdrm/build-libdrm.sh" +inputs = [ + "packages/registry/libdrm/build-libdrm.sh", +] +repo_url = "https://github.com/Automattic/kandelo.git" +commit = "UNPUBLISHED" +revision = 1 diff --git a/packages/registry/libdrm/package.toml b/packages/registry/libdrm/package.toml new file mode 100644 index 0000000000..1f5d1c8b78 --- /dev/null +++ b/packages/registry/libdrm/package.toml @@ -0,0 +1,74 @@ +# Per-library manifest for libdrm. See docs/package-management.md. +# +# This is the KMS-side subset of libdrm — only the four C files +# (xf86drm.c, xf86drmMode.c, xf86drmHash.c, xf86drmRandom.c) plus +# the include/drm/ and xf86drm{,Mode}.h headers SDL2's KMSDRM video +# backend (`src/video/kmsdrm/SDL_kmsdrmvideo.c`) actually calls. +# +# We deliberately skip the GPU-specific subdirs (libdrm_amdgpu, +# libdrm_radeon, libdrm_intel, libdrm_nouveau, libdrm_etnaviv, +# libdrm_exynos, libdrm_freedreno, libdrm_omap, libdrm_tegra, +# libdrm_vc4) — none of those run on the wasm32 kernel and they +# pull in vendor-specific ioctl tables we don't ship. +# +# libdrm-KMS has no link-time deps. It wraps the kernel's KMS ioctl +# surface (the DRM_IOCTL_MODE_* family in crates/kernel/src/dri/) +# through the standard drmModeGetResources / drmModeAddFB2 / +# drmModePageFlip API SDL2 expects. No external libraries are +# required. + +kind = "library" + +name = "libdrm" +version = "2.4.120" +kernel_abi = 43 +depends_on = [] + +[source] +url = "https://dri.freedesktop.org/libdrm/libdrm-2.4.120.tar.xz" +sha256 = "3bf55363f76c7250946441ab51d3a6cc0ae518055c0ff017324ab76cdefb327a" +provider = "archive" + +[license] +spdx = "MIT" +url = "https://gitlab.freedesktop.org/mesa/drm/-/blob/libdrm-2.4.120/README" + +[build] +script_path = "packages/registry/libdrm/build-libdrm.sh" + +[[host_tools]] +name = "curl" +version_constraint = ">=7.71.0" +probe = { args = ["--version"], version_regex = "curl (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "tar" +version_constraint = ">=1.30" +probe = { args = ["--version"], version_regex = "tar.*?(\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[host_tools]] +name = "shasum" +version_constraint = ">=6.0" +probe = { args = ["--version"], version_regex = "(\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +# gen_table_fourcc.py turns drm_fourcc.h into the static format table +# xf86drm.c includes; upstream runs it from meson, we run it directly. +[[host_tools]] +name = "python3" +version_constraint = ">=3.8" +probe = { args = ["--version"], version_regex = "Python (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[outputs] +# KMS-only subset; the per-vendor libdrm_* archives (libdrm_amdgpu.a +# etc.) are deliberately not built. +libs = ["lib/libdrm.a"] +# UAPI headers live under include/drm/ (drm.h, drm_mode.h, +# drm_fourcc.h); libdrm public headers live under include/libdrm/ +# (xf86drm.h, xf86drmMode.h). Consumers link with +# `-I$prefix/include -I$prefix/include/libdrm` and +# `#include ` matches upstream's pkg-config flags. +headers = ["include/drm", "include/libdrm"] diff --git a/packages/registry/modeset/build.toml b/packages/registry/modeset/build.toml index 24eaca8231..b73a299fff 100644 --- a/packages/registry/modeset/build.toml +++ b/packages/registry/modeset/build.toml @@ -2,7 +2,9 @@ script_path = "packages/registry/modeset/build-modeset.sh" inputs = [ "packages/registry/modeset/build-modeset.sh", "programs/modeset.c", - "libc/glue/libdrm_stub.c", + "packages/registry/libdrm/package.toml", + "packages/registry/libdrm/build.toml", + "packages/registry/libdrm/build-libdrm.sh", "libc/glue/libgbm_stub.c", "libc/glue/libegl_stub.c", "libc/glue/libglesv2_stub.c", @@ -15,4 +17,4 @@ inputs = [ ] repo_url = "https://github.com/Automattic/kandelo.git" commit = "8c53383229fab78f97b098c3207a655159c03041" -revision = 1 +revision = 2 diff --git a/packages/registry/sdl2-demo/build-sdl2-demo.sh b/packages/registry/sdl2-demo/build-sdl2-demo.sh new file mode 100755 index 0000000000..399ca12b02 --- /dev/null +++ b/packages/registry/sdl2-demo/build-sdl2-demo.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$HERE/../../.." && pwd)" +# shellcheck source=/dev/null +source "$REPO_ROOT/scripts/package-build-roots.sh" +kandelo_package_prepare_build_roots "$HERE" wasm32 +kandelo_package_select_source_root "$REPO_ROOT" +SOURCE_ROOT="$KANDELO_PACKAGE_SOURCE_ROOT" +SDL2_DEMO_DIR="$SOURCE_ROOT/programs/sdl2" +WORK_DIR="$KANDELO_PACKAGE_WORK_DIR" +OUT_BIN="$WORK_DIR/sdl2.wasm" + +for f in main.c audio.c editor.c renderer.c sound_shader.c; do + if [ ! -f "$SDL2_DEMO_DIR/$f" ] || [ -L "$SDL2_DEMO_DIR/$f" ]; then + echo "ERROR: sdl2 source must be a regular file: $SDL2_DEMO_DIR/$f" >&2 + exit 1 + fi +done + +# A resolver/Formula caller owns the declared work and output roots. Keep the +# reviewed checkout read-only and suppress the developer-only local mirror. +if [ -n "${WASM_POSIX_DEP_WORK_DIR:-}" ] && [ -n "${WASM_POSIX_DEP_OUT_DIR:-}" ]; then + export WASM_POSIX_INSTALL_LOCAL_MIRROR=0 + export WASM_POSIX_INSTALL_FORK_INSTRUMENTATION=auto +fi + +# The resolver builds the sdl2 library first (depends_on) and hands its +# staged prefix over; libgbm / libdrm / libEGL / libGLESv2 are sysroot +# libraries scripts/build-musl.sh installs. +SDL2_PREFIX="${WASM_POSIX_DEP_SDL2_DIR:?WASM_POSIX_DEP_SDL2_DIR must name the staged sdl2 prefix (resolve sdl2-demo through cargo xtask build-deps)}" + +source "$REPO_ROOT/sdk/activate.sh" +export WASM_POSIX_SYSROOT="$REPO_ROOT/sysroot" + +if [ ! -f "$WASM_POSIX_SYSROOT/lib/libdrm.a" ] || + [ ! -f "$WASM_POSIX_SYSROOT/lib/libgbm.a" ] || + [ ! -f "$WASM_POSIX_SYSROOT/lib/libEGL.a" ] || + [ ! -f "$WASM_POSIX_SYSROOT/lib/libGLESv2.a" ]; then + echo "ERROR: DRI/EGL/GLES sysroot libraries are missing." >&2 + echo "Run: scripts/dev-shell.sh bash scripts/build-musl.sh" >&2 + exit 1 +fi + +# renderer.c includes "third_party/inconsolata_ttf.h", generated from the +# checked-in .ttf. The checkout stays read-only, so the header lands in +# the work dir and -I"$WORK_DIR" resolves the quote-include. +echo "==> Generating inconsolata_ttf.h..." +mkdir -p "$WORK_DIR/third_party" +python3 - "$SDL2_DEMO_DIR/third_party/Inconsolata-Regular.ttf" \ + "$WORK_DIR/third_party/inconsolata_ttf.h" <<'PY' +import sys, pathlib +src = pathlib.Path(sys.argv[1]).read_bytes() +dst = pathlib.Path(sys.argv[2]) +PER_LINE = 16 +lines = [ + ",".join(f"0x{b:02x}" for b in src[i:i + PER_LINE]) + for i in range(0, len(src), PER_LINE) +] +dst.write_text( + "/* Auto-generated from Inconsolata-Regular.ttf by " + "build-sdl2-demo.sh. */\n" + "/* See programs/sdl2/third_party/NOTICE.md for license. */\n" + "#pragma once\n" + f"static const unsigned char inconsolata_ttf[] = {{\n" + + ",\n".join(lines) + "\n};\n" + f"static const unsigned int inconsolata_ttf_len = {len(src)};\n" +) +PY + +PKG_CFLAGS="$(wasm32posix-pkg-config --cflags gbm libdrm egl glesv2)" +PKG_LIBS="$(wasm32posix-pkg-config --libs gbm libdrm egl glesv2)" + +echo "==> Building the SDL2 GLSL playground..." +wasm32posix-cc \ + -std=c11 \ + -O2 \ + -Wall \ + -Wextra \ + -Wno-unused-parameter \ + -D_DEFAULT_SOURCE \ + -I"$SDL2_PREFIX/include" \ + -I"$WORK_DIR" \ + $PKG_CFLAGS \ + "$SDL2_DEMO_DIR/main.c" \ + "$SDL2_DEMO_DIR/audio.c" \ + "$SDL2_DEMO_DIR/editor.c" \ + "$SDL2_DEMO_DIR/renderer.c" \ + "$SDL2_DEMO_DIR/sound_shader.c" \ + -L"$SDL2_PREFIX/lib" \ + -lSDL2 \ + $PKG_LIBS \ + -lm \ + -o "$OUT_BIN" + +cd "$REPO_ROOT" +source "$REPO_ROOT/scripts/install-local-binary.sh" +install_local_binary sdl2-demo "$OUT_BIN" sdl2.wasm diff --git a/packages/registry/sdl2-demo/build.toml b/packages/registry/sdl2-demo/build.toml new file mode 100644 index 0000000000..b6a8dc54ca --- /dev/null +++ b/packages/registry/sdl2-demo/build.toml @@ -0,0 +1,27 @@ +script_path = "packages/registry/sdl2-demo/build-sdl2-demo.sh" +inputs = [ + "packages/registry/sdl2-demo/build-sdl2-demo.sh", + "programs/sdl2/main.c", + "programs/sdl2/audio.c", + "programs/sdl2/audio.h", + "programs/sdl2/editor.c", + "programs/sdl2/editor.h", + "programs/sdl2/renderer.c", + "programs/sdl2/renderer.h", + "programs/sdl2/sound_shader.c", + "programs/sdl2/sound_shader.h", + "programs/sdl2/third_party/stb_truetype.h", + "programs/sdl2/third_party/Inconsolata-Regular.ttf", + "libc/glue/libgbm_stub.c", + "libc/glue/libegl_stub.c", + "libc/glue/libglesv2_stub.c", + "libc/glue/gl_abi.h", + "libc/musl-overlay/include/linux/input-event-codes.h", + "scripts/build-musl.sh", + "scripts/build-dri-stubs.sh", + "scripts/build-gles-stubs.sh", + "scripts/package-build-roots.sh", +] +repo_url = "https://github.com/Automattic/kandelo.git" +commit = "" +revision = 1 diff --git a/packages/registry/sdl2-demo/package.toml b/packages/registry/sdl2-demo/package.toml new file mode 100644 index 0000000000..7f5e9ba49f --- /dev/null +++ b/packages/registry/sdl2-demo/package.toml @@ -0,0 +1,35 @@ +kind = "program" +# Runtime backing for `/?demo=sdl2`. The browser app imports the output +# through `@binaries`, so it needs a registry owner like every other +# product artifact — see scripts/browser-binary-package-roots.mjs. The +# sources stay in programs/sdl2/ so scripts/build-programs.sh keeps +# building the local test fixture, the same split modeset uses. +name = "sdl2-demo" +version = "0.1.0" +kernel_abi = 43 +depends_on = ["sdl2@2.32.10"] +arches = ["wasm32"] + +[source] +url = "https://github.com/Automattic/kandelo" +sha256 = "0000000000000000000000000000000000000000000000000000000000000000" +provider = "repository" + +[license] +spdx = "GPL-2.0-or-later" +url = "https://github.com/Automattic/kandelo/blob/main/COPYING" + +[build] +script_path = "packages/registry/sdl2-demo/build-sdl2-demo.sh" + +# scripts/build-programs.sh regenerates the same TTF→C byte-array header +# this build generates into its work dir. +[[host_tools]] +name = "python3" +version_constraint = ">=3.8" +probe = { args = ["--version"], version_regex = "Python (\\d+\\.\\d+(?:\\.\\d+)?)" } +install_hints = { darwin = "run through scripts/dev-shell.sh", linux = "run through scripts/dev-shell.sh" } + +[[outputs]] +name = "sdl2" +wasm = "sdl2.wasm" diff --git a/packages/registry/sdl2/build-sdl2.sh b/packages/registry/sdl2/build-sdl2.sh index b5708e0d01..c19b26ebfa 100644 --- a/packages/registry/sdl2/build-sdl2.sh +++ b/packages/registry/sdl2/build-sdl2.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Build upstream SDL 2 with its unmodified OSS dsp backend for Kandelo. +# Build upstream SDL 2 for Kandelo with its unmodified OSS dsp audio +# backend, its KMSDRM video backend, and its direct evdev input path. set -euo pipefail @@ -23,6 +24,12 @@ if [ "$TARGET_ARCH" != "wasm32" ]; then fi export WASM_POSIX_SYSROOT="${WASM_POSIX_SYSROOT:-$REPO_ROOT/sysroot}" + +# The KMSDRM backend links libdrm and libgbm. libdrm is a package the +# resolver stages for us; libgbm is a sysroot library scripts/ +# build-dri-stubs.sh builds, so it is read from the sysroot directly. +LIBDRM_PREFIX="${WASM_POSIX_DEP_LIBDRM_DIR:?WASM_POSIX_DEP_LIBDRM_DIR must name the staged libdrm prefix (resolve sdl2 through cargo xtask build-deps)}" + CC=wasm32posix-cc CXX=wasm32posix-c++ AR=wasm32posix-ar @@ -52,14 +59,30 @@ tar xzf "$TARBALL" -C "$SRC_DIR" --strip-components=1 echo "==> Applying the Kandelo platform-classification patch..." patch -d "$SRC_DIR" -p1 < "$SCRIPT_DIR/patches/0001-recognize-kandelo-as-unix.patch" -echo "==> Configuring SDL2 with only the OSS playback backend..." +echo "==> Configuring SDL2 with the OSS, KMSDRM and evdev backends..." # Kandelo exposes neither the non-POSIX sysctl header nor its matching API. # Pin the cross-compile probe so SDL uses its portable sysconf path. # Executable links intentionally permit unresolved host imports, so link-only # Autoconf probes cannot prove optional functions. Pin only helpers absent from # the Kandelo musl headers/library; SDL provides portable fallbacks for them. +# +# libdrm and gbm ship no .pc files in the Kandelo sysroot. SDL consults +# pkg-config only to populate CFLAGS/LIBS, so presetting the four +# variables short-circuits the lookup (acinclude/pkg.m4, _PKG_CONFIG's +# first branch). +# +# SDL_VIDEO_STATIC_ANGLE forces src/video/SDL_egl.c's LOAD_FUNC macro +# down its static-link branch, so `_this->egl_data->eglFoo` binds to the +# libEGL.a symbol instead of going through SDL_LoadFunction. With +# --disable-loadso that loader returns NULL and EGL init fails before a +# window can exist. The ANGLE in the name means "EGL symbols are linked +# in, not dlopened" — the same path the Vita and WinRT builds take. ( cd "$BUILD_DIR" + LIBDRM_CFLAGS="-I$LIBDRM_PREFIX/include -I$LIBDRM_PREFIX/include/libdrm -I$LIBDRM_PREFIX/include/drm" \ + LIBDRM_LIBS="-L$LIBDRM_PREFIX/lib -ldrm" \ + LIBGBM_CFLAGS="-I$WASM_POSIX_SYSROOT/include" \ + LIBGBM_LIBS="-L$WASM_POSIX_SYSROOT/lib -lgbm" \ "$SRC_DIR/configure" \ --host=wasm32-unknown-none \ --prefix="$INSTALL_DIR" \ @@ -79,7 +102,20 @@ echo "==> Configuring SDL2 with only the OSS playback backend..." --disable-libsamplerate \ --disable-diskaudio \ --disable-dummyaudio \ - --disable-video \ + --enable-video \ + --enable-video-kmsdrm \ + --disable-kmsdrm-shared \ + --disable-video-x11 \ + --disable-video-wayland \ + --disable-video-vivante \ + --disable-video-cocoa \ + --disable-video-directfb \ + --disable-video-offscreen \ + --disable-video-dummy \ + --disable-video-opengl \ + --enable-video-opengl-es2 \ + --enable-events \ + --enable-input-events \ --disable-render \ --disable-joystick \ --disable-haptic \ @@ -95,7 +131,9 @@ echo "==> Configuring SDL2 with only the OSS playback backend..." --disable-assembly \ CC="$CC" CXX="$CXX" AR="$AR" RANLIB="$RANLIB" \ NM="$NM" STRIP="$STRIP" \ - CFLAGS="-O2 $REPRO_FLAGS" \ + CFLAGS="-O2 $REPRO_FLAGS -DSDL_VIDEO_STATIC_ANGLE=1" \ + CPPFLAGS="-I$LIBDRM_PREFIX/include -I$LIBDRM_PREFIX/include/libdrm -I$LIBDRM_PREFIX/include/drm" \ + LDFLAGS="-L$LIBDRM_PREFIX/lib -L$WASM_POSIX_SYSROOT/lib" \ ac_cv_func_dlopen=no \ ac_cv_func_sysctlbyname=no \ ac_cv_func_elf_aux_info=no \ @@ -130,4 +168,14 @@ rm -f "$INSTALL_DIR/lib/"*.la test -f "$INSTALL_DIR/lib/libSDL2.a" test -f "$INSTALL_DIR/include/SDL2/SDL.h" test -f "$INSTALL_DIR/lib/pkgconfig/sdl2.pc" -echo "==> SDL2 OSS-only static package complete" + +# Autoconf silently drops a backend whose probe fails, which would leave a +# library that links but cannot open a window. Fail the build instead. +for feature in SDL_VIDEO_DRIVER_KMSDRM SDL_VIDEO_OPENGL_ES2 \ + SDL_VIDEO_OPENGL_EGL SDL_INPUT_LINUXEV SDL_AUDIO_DRIVER_OSS; do + grep -q "^#define $feature 1" "$INSTALL_DIR/include/SDL2/SDL_config.h" || { + echo "ERROR: configure did not enable $feature" >&2 + exit 1 + } +done +echo "==> SDL2 static package complete (KMSDRM video, evdev input, OSS audio)" diff --git a/packages/registry/sdl2/build.toml b/packages/registry/sdl2/build.toml index 1142569c45..a10883cd56 100644 --- a/packages/registry/sdl2/build.toml +++ b/packages/registry/sdl2/build.toml @@ -2,7 +2,13 @@ script_path = "packages/registry/sdl2/build-sdl2.sh" inputs = [ "packages/registry/sdl2/build-sdl2.sh", "packages/registry/sdl2/patches/0001-recognize-kandelo-as-unix.patch", + "libc/glue/libgbm_stub.c", + "libc/glue/libegl_stub.c", + "libc/glue/libglesv2_stub.c", + "libc/glue/gl_abi.h", + "scripts/build-dri-stubs.sh", + "scripts/build-gles-stubs.sh", ] repo_url = "https://github.com/Automattic/kandelo.git" commit = "UNPUBLISHED" -revision = 1 +revision = 2 diff --git a/packages/registry/sdl2/package.toml b/packages/registry/sdl2/package.toml index bc7f77406b..4958a5b598 100644 --- a/packages/registry/sdl2/package.toml +++ b/packages/registry/sdl2/package.toml @@ -3,7 +3,7 @@ kind = "library" name = "sdl2" version = "2.32.10" kernel_abi = 43 -depends_on = [] +depends_on = ["libdrm@2.4.120"] arches = ["wasm32"] [source] diff --git a/packages/sets/local-supported.toml b/packages/sets/local-supported.toml index 4d2db75755..edf6e76716 100644 --- a/packages/sets/local-supported.toml +++ b/packages/sets/local-supported.toml @@ -75,10 +75,6 @@ class = "user-software" name = "espeak-ng" class = "user-software" -[[packages]] -name = "evdev-demo" -class = "user-software" - [[packages]] name = "fbdoom" class = "user-software" @@ -135,6 +131,10 @@ class = "platform" name = "libcxx" class = "platform" +[[packages]] +name = "libdrm" +class = "platform" + [[packages]] name = "libiconv" class = "platform" @@ -287,6 +287,10 @@ class = "test-support" name = "sdl2" class = "platform" +[[packages]] +name = "sdl2-demo" +class = "user-software" + [[packages]] name = "sdl2-mixer-playwave" class = "user-software" diff --git a/programs/evdev_demo.c b/programs/evdev_demo.c deleted file mode 100644 index 1f2c53a8a4..0000000000 --- a/programs/evdev_demo.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * evdev_demo — runtime backing for `/?demo=evdev`. - * - * Only in-tree consumer of ``: the _Static_assert below - * fires at build time if the vendored header drifts away from the - * kernel-side WpkInputEvent layout. input-evdev-smoke.c inlines its own - * definitions on purpose, so that fixture's ABI check stays independent - * of this header. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -_Static_assert(sizeof(struct input_event) == 24, - "struct input_event must be 24 bytes on wasm32 (musl 64-bit time_t)"); - -#define EV_BATCH 16 - -static void log_kbd(const struct input_event *e) { - if (e->type == EV_KEY) { - const char *state = e->value == 0 ? "up" - : e->value == 1 ? "down" - : "repeat"; - printf("key %s: code=%u\n", state, (unsigned) e->code); - fflush(stdout); - } -} - -static void log_ptr(const struct input_event *e) { - if (e->type == EV_REL) { - printf("ptr rel code=%u value=%d\n", - (unsigned) e->code, (int) e->value); - fflush(stdout); - } else if (e->type == EV_ABS) { - printf("ptr abs code=%u value=%d\n", - (unsigned) e->code, (int) e->value); - fflush(stdout); - } -} - -int main(void) { - int kbd = open("/dev/input/event0", O_RDONLY | O_CLOEXEC); - if (kbd < 0) { perror("open /dev/input/event0"); return 1; } - int ptr = open("/dev/input/event1", O_RDONLY | O_CLOEXEC); - if (ptr < 0) { perror("open /dev/input/event1"); return 1; } - - char name[64] = {0}; - if (ioctl(kbd, EVIOCGNAME(sizeof name), name) < 0) { - perror("EVIOCGNAME event0"); return 1; - } - printf("kbd: %s\n", name); - if (ioctl(ptr, EVIOCGNAME(sizeof name), name) < 0) { - perror("EVIOCGNAME event1"); return 1; - } - printf("ptr: %s\n", name); - printf("ready: type or move the mouse over the canvas\n"); - fflush(stdout); - - struct pollfd pfds[2] = { - { .fd = kbd, .events = POLLIN }, - { .fd = ptr, .events = POLLIN }, - }; - - for (;;) { - int n = poll(pfds, 2, -1); - if (n < 0) { - if (errno == EINTR) continue; - perror("poll"); return 1; - } - struct input_event evs[EV_BATCH]; - if (pfds[0].revents & POLLIN) { - ssize_t r = read(kbd, evs, sizeof evs); - for (ssize_t i = 0; i < r / (ssize_t) sizeof(evs[0]); i++) { - log_kbd(&evs[i]); - } - } - if (pfds[1].revents & POLLIN) { - ssize_t r = read(ptr, evs, sizeof evs); - for (ssize_t i = 0; i < r / (ssize_t) sizeof(evs[0]); i++) { - log_ptr(&evs[i]); - } - } - } -} diff --git a/programs/gbm_surface_smoke.c b/programs/gbm_surface_smoke.c new file mode 100644 index 0000000000..80f297c0d5 --- /dev/null +++ b/programs/gbm_surface_smoke.c @@ -0,0 +1,89 @@ +/* + * Exercises the libgbm 2-BO ring (libc/glue/libgbm_stub.c): + * - lock_front_buffer hands out distinct BOs in turn, + * - lock-when-both-in-use returns NULL+EBUSY, + * - release_buffer marks the slot free again, + * - has_free_buffers reports the remaining-free count, + * - destroy with locked BOs unwinds cleanly. + */ +#include +#include +#include +#include +#include + +int main(void) +{ + int fd = open("/dev/dri/card0", O_RDWR); + if (fd < 0) { + fprintf(stderr, "FAIL: open /dev/dri/card0: %d\n", errno); + return 1; + } + struct gbm_device *dev = gbm_create_device(fd); + if (!dev) { + fprintf(stderr, "FAIL: gbm_create_device\n"); + close(fd); + return 1; + } + struct gbm_surface *s = gbm_surface_create( + dev, 320, 240, GBM_FORMAT_XRGB8888, + GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!s) { + fprintf(stderr, "FAIL: gbm_surface_create: %d\n", errno); + gbm_device_destroy(dev); + close(fd); + return 1; + } + + int free_before = gbm_surface_has_free_buffers(s); + if (free_before != 2) { + fprintf(stderr, "FAIL: expected 2 free BOs, got %d\n", free_before); + return 1; + } + + struct gbm_bo *bo_a = gbm_surface_lock_front_buffer(s); + if (!bo_a) { + fprintf(stderr, "FAIL: first lock returned NULL\n"); + return 1; + } + if (gbm_surface_has_free_buffers(s) != 1) { + fprintf(stderr, "FAIL: expected 1 free after one lock\n"); + return 1; + } + + struct gbm_bo *bo_b = gbm_surface_lock_front_buffer(s); + if (!bo_b || bo_b == bo_a) { + fprintf(stderr, "FAIL: second lock must return a distinct BO\n"); + return 1; + } + if (gbm_surface_has_free_buffers(s) != 0) { + fprintf(stderr, "FAIL: expected 0 free after two locks\n"); + return 1; + } + + errno = 0; + struct gbm_bo *bo_c = gbm_surface_lock_front_buffer(s); + if (bo_c != NULL || errno != EBUSY) { + fprintf(stderr, "FAIL: third lock must NULL+EBUSY, got bo=%p errno=%d\n", + (void *) bo_c, errno); + return 1; + } + + gbm_surface_release_buffer(s, bo_a); + if (gbm_surface_has_free_buffers(s) != 1) { + fprintf(stderr, "FAIL: expected 1 free after release of bo_a\n"); + return 1; + } + + struct gbm_bo *bo_d = gbm_surface_lock_front_buffer(s); + if (!bo_d) { + fprintf(stderr, "FAIL: lock after release returned NULL\n"); + return 1; + } + + gbm_surface_destroy(s); + gbm_device_destroy(dev); + close(fd); + printf("OK\n"); + return 0; +} diff --git a/programs/sdl2/audio.c b/programs/sdl2/audio.c new file mode 100644 index 0000000000..417d67f5e2 --- /dev/null +++ b/programs/sdl2/audio.c @@ -0,0 +1,314 @@ +/* + * programs/sdl2/audio.c — chip-synth + FFT analysis. See audio.h for the + * public surface and the threading model (single-threaded, polled). + */ + +#include "audio.h" + +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* ----- analysis ring --------------------------------------------- */ + +/* Power-of-two FFT window. The ring holds exactly one window of the most + * recent mono samples; `g_ring_pos` is the index the next sample writes + * to, so the oldest sample is at g_ring_pos and the newest at + * g_ring_pos-1 (mod N). */ +#define FFT_N 1024 + +static float g_ring[FFT_N]; +static int g_ring_pos = 0; + +/* ----- device config --------------------------------------------- */ + +static int g_rate = 48000; +static int g_channels = 2; +static int g_muted = 0; + +/* ----- sound-shader playback source ------------------------------ */ + +/* When g_sound_pcm is non-NULL the callback plays this looped S16 stereo + * buffer instead of the chip synth. Owned by sound_shader.c; we only read + * it. g_sound_pos is the current frame cursor into it. */ +static const int16_t *g_sound_pcm = NULL; +static int g_sound_frames = 0; +static int g_sound_pos = 0; + +/* ----- synth state ----------------------------------------------- */ + +/* 16-row looping pattern. Entries are MIDI note numbers; 0 = rest / + * continue. The noise row uses 1 = hit, 0 = silent. The four voices are + * a square lead, a square/triangle-ish bass, a saw pad, and a noise hat + * — the classic 4-op chiptune palette. */ +#define PATTERN_ROWS 16 + +static const int PAT_LEAD[PATTERN_ROWS] = { + 72, 0, 76, 0, 79, 0, 76, 0, 74, 0, 71, 0, 67, 0, 0, 0, +}; +static const int PAT_BASS[PATTERN_ROWS] = { + 48, 0, 0, 0, 53, 0, 0, 0, 55, 0, 0, 0, 53, 0, 48, 0, +}; +static const int PAT_PAD[PATTERN_ROWS] = { + 60, 0, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, +}; +static const int PAT_HAT[PATTERN_ROWS] = { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, +}; + +/* Per-voice oscillator + envelope state. `phase` is in [0,1); `amp` is + * the current envelope level, reset to `gain` on a note trigger and + * decayed multiplicatively each sample by `decay`. */ +typedef struct { + float phase; + float inc; /* phase increment per sample = freq / rate */ + float amp; + float gain; /* peak level on trigger */ + float decay; /* per-sample multiplier (set from a decay time) */ +} Voice; + +enum { V_LEAD, V_BASS, V_PAD, V_HAT, V_COUNT }; +static Voice g_voice[V_COUNT]; + +static int g_row = 0; +static int g_samples_in_row = 0; +static int g_samples_per_row = 6000; + +/* 15-bit LFSR for the noise voice (Galois form). */ +static uint16_t g_lfsr = 0xACE1u; + +static int g_render_logged = 0; + +static float midi_to_freq(int note) { + /* A4 (MIDI 69) = 440 Hz. */ + return 440.0f * powf(2.0f, (float)(note - 69) / 12.0f); +} + +/* Set a voice's per-sample decay multiplier from a decay time in seconds: + * amp falls to ~37% (1/e) after `seconds`. */ +static float decay_per_sample(float seconds) { + if (seconds <= 0.0f || g_rate <= 0) return 0.0f; + return expf(-1.0f / (seconds * (float)g_rate)); +} + +static void trigger_voice(Voice *v, int note, float gain, float decay_sec) { + v->inc = midi_to_freq(note) / (float)g_rate; + v->amp = gain; + v->gain = gain; + v->decay = decay_per_sample(decay_sec); +} + +static void advance_row(void) { + g_row = (g_row + 1) % PATTERN_ROWS; + + if (PAT_LEAD[g_row]) trigger_voice(&g_voice[V_LEAD], PAT_LEAD[g_row], + 0.28f, 0.18f); + if (PAT_BASS[g_row]) trigger_voice(&g_voice[V_BASS], PAT_BASS[g_row], + 0.30f, 0.30f); + if (PAT_PAD[g_row]) trigger_voice(&g_voice[V_PAD], PAT_PAD[g_row], + 0.18f, 0.45f); + if (PAT_HAT[g_row]) { + /* The hat is pitch-less noise; only its envelope matters. */ + g_voice[V_HAT].amp = 0.22f; + g_voice[V_HAT].decay = decay_per_sample(0.05f); + } +} + +void audio_synth_init(int sample_rate, int channels) { + g_rate = sample_rate > 0 ? sample_rate : 48000; + g_channels = channels > 0 ? channels : 2; + + /* ~8.7 rows/sec ≈ 130 BPM at 4 rows/beat — a brisk chiptune feel. */ + g_samples_per_row = (int)((float)g_rate * 60.0f / (130.0f * 4.0f)); + if (g_samples_per_row < 1) g_samples_per_row = 1; + + memset(g_voice, 0, sizeof g_voice); + memset(g_ring, 0, sizeof g_ring); + g_ring_pos = 0; + g_row = PATTERN_ROWS - 1; /* so the first advance lands row 0 */ + g_samples_in_row = g_samples_per_row; /* force an immediate advance */ + g_lfsr = 0xACE1u; + g_muted = 0; + g_render_logged = 0; + + printf("sdl2: audio synth rate=%d ch=%d rows/loop=%d\n", + g_rate, g_channels, PATTERN_ROWS); +} + +/* Produce one mono sample, advancing all oscillator/envelope state. */ +static float synth_next_sample(void) { + if (g_samples_in_row >= g_samples_per_row) { + g_samples_in_row = 0; + advance_row(); + } + g_samples_in_row++; + + /* Square lead. */ + Voice *lead = &g_voice[V_LEAD]; + float lead_s = (lead->phase < 0.5f ? 1.0f : -1.0f) * lead->amp; + lead->phase += lead->inc; + if (lead->phase >= 1.0f) lead->phase -= 1.0f; + lead->amp *= lead->decay; + + /* Square bass (narrower 25% duty for a punchier low end). */ + Voice *bass = &g_voice[V_BASS]; + float bass_s = (bass->phase < 0.25f ? 1.0f : -1.0f) * bass->amp; + bass->phase += bass->inc; + if (bass->phase >= 1.0f) bass->phase -= 1.0f; + bass->amp *= bass->decay; + + /* Saw pad. */ + Voice *pad = &g_voice[V_PAD]; + float pad_s = (2.0f * pad->phase - 1.0f) * pad->amp; + pad->phase += pad->inc; + if (pad->phase >= 1.0f) pad->phase -= 1.0f; + pad->amp *= pad->decay; + + /* Noise hat: Galois LFSR, one step per sample. */ + Voice *hat = &g_voice[V_HAT]; + unsigned lsb = g_lfsr & 1u; + g_lfsr >>= 1; + if (lsb) g_lfsr ^= 0xB400u; + float hat_s = ((g_lfsr & 1u) ? 1.0f : -1.0f) * hat->amp; + hat->amp *= hat->decay; + + float mix = (lead_s + bass_s + pad_s + hat_s) * 0.6f; + if (mix > 1.0f) mix = 1.0f; + if (mix < -1.0f) mix = -1.0f; + return mix; +} + +void audio_synth_render(uint8_t *stream, int len) { + int16_t *out = (int16_t *)stream; + int frames = len / (int)(sizeof(int16_t) * (size_t)g_channels); + + if (!g_render_logged) { + printf("sdl2: audio render first-callback frames=%d\n", frames); + g_render_logged = 1; + } + + int use_sound = (g_sound_pcm != NULL && g_sound_frames > 0); + + for (int f = 0; f < frames; f++) { + float left, right; + if (use_sound) { + /* Loop the rendered sound-shader buffer. The synth pattern is + * not advanced while the sound source is active. */ + left = (float) g_sound_pcm[g_sound_pos * 2 + 0] / 32768.0f; + right = (float) g_sound_pcm[g_sound_pos * 2 + 1] / 32768.0f; + g_sound_pos++; + if (g_sound_pos >= g_sound_frames) g_sound_pos = 0; + } else { + left = right = synth_next_sample(); + } + if (g_muted) { left = 0.0f; right = 0.0f; } + + /* Feed the analysis ring with the (possibly muted) mono mix so the + * visualizer reflects what is actually audible. */ + g_ring[g_ring_pos] = 0.5f * (left + right); + g_ring_pos = (g_ring_pos + 1) % FFT_N; + + int16_t lv = (int16_t)(left * 32767.0f); + int16_t rv = (int16_t)(right * 32767.0f); + for (int c = 0; c < g_channels; c++) { + out[f * g_channels + c] = (c == 0) ? lv : rv; + } + } +} + +/* ----- FFT (iterative radix-2 Cooley–Tukey, in place) ------------ */ + +static void fft_radix2(float *re, float *im, int n) { + /* Bit-reversal permutation. */ + for (int i = 1, j = 0; i < n; i++) { + int bit = n >> 1; + for (; j & bit; bit >>= 1) j ^= bit; + j ^= bit; + if (i < j) { + float tr = re[i]; re[i] = re[j]; re[j] = tr; + float ti = im[i]; im[i] = im[j]; im[j] = ti; + } + } + /* Butterflies. */ + for (int len = 2; len <= n; len <<= 1) { + float ang = -2.0f * (float)M_PI / (float)len; + float wr = cosf(ang), wi = sinf(ang); + for (int i = 0; i < n; i += len) { + float cwr = 1.0f, cwi = 0.0f; + for (int k = 0; k < len / 2; k++) { + int a = i + k; + int b = i + k + len / 2; + float vr = re[b] * cwr - im[b] * cwi; + float vi = re[b] * cwi + im[b] * cwr; + re[b] = re[a] - vr; im[b] = im[a] - vi; + re[a] += vr; im[a] += vi; + float ncwr = cwr * wr - cwi * wi; + cwi = cwr * wi + cwi * wr; + cwr = ncwr; + } + } + } +} + +void audio_compute_spectrum(uint8_t bins[AUDIO_SPECTRUM_BINS]) { + float re[FFT_N]; + float im[FFT_N]; + + /* Copy the ring oldest→newest and apply a Hann window to suppress + * spectral leakage from the rectangular frame. */ + for (int i = 0; i < FFT_N; i++) { + int idx = (g_ring_pos + i) % FFT_N; + float w = 0.5f - 0.5f * cosf(2.0f * (float)M_PI * (float)i + / (float)(FFT_N - 1)); + re[i] = g_ring[idx] * w; + im[i] = 0.0f; + } + + fft_radix2(re, im, FFT_N); + + /* Usable spectrum is bins 1..N/2. Map the 128 output bins + * logarithmically across that range — log spacing matches both human + * pitch perception and the spread of energy in the chiptune. Each + * output bin takes the peak magnitude across its source range. */ + const int half = FFT_N / 2; + for (int b = 0; b < AUDIO_SPECTRUM_BINS; b++) { + float t0 = (float)b / (float)AUDIO_SPECTRUM_BINS; + float t1 = (float)(b + 1) / (float)AUDIO_SPECTRUM_BINS; + int lo = (int)powf((float)half, t0); + int hi = (int)powf((float)half, t1); + if (lo < 1) lo = 1; + if (hi <= lo) hi = lo + 1; + if (hi > half) hi = half; + + float peak = 0.0f; + for (int k = lo; k < hi; k++) { + float mag = sqrtf(re[k] * re[k] + im[k] * im[k]); + if (mag > peak) peak = mag; + } + /* Normalize against the window's coherent gain (~N/4 after Hann) + * and compress with a square root so quiet detail stays visible. */ + float v = peak / (float)(FFT_N / 4); + v = sqrtf(v); + if (v > 1.0f) v = 1.0f; + bins[b] = (uint8_t)(v * 255.0f); + } +} + +void audio_set_muted(int muted) { g_muted = muted ? 1 : 0; } +int audio_muted(void) { return g_muted; } + +void audio_set_sound_pcm(const int16_t *pcm, int frames) { + if (pcm && frames > 0) { + g_sound_pcm = pcm; + g_sound_frames = frames; + g_sound_pos = 0; + } else { + g_sound_pcm = NULL; + g_sound_frames = 0; + g_sound_pos = 0; + } +} diff --git a/programs/sdl2/audio.h b/programs/sdl2/audio.h new file mode 100644 index 0000000000..a4219ee3de --- /dev/null +++ b/programs/sdl2/audio.h @@ -0,0 +1,56 @@ +/* + * programs/sdl2/audio.h — chip-synth + FFT analysis for the SDL2 GLSL + * playground: the chip synth, the FFT and the iAudio uniform. + * + * Two responsibilities: + * + * 1. A 4-channel chiptune synth (square / square / saw / noise) over + * a 16-row looping pattern. `audio_synth_render` is the body of the + * SDL audio callback and runs on SDL's audio thread; main-loop + * writes to the state it reads hold SDL_LockAudioDevice. + * + * 2. A rolling analysis ring of the most recent mono samples the synth + * produced. `audio_compute_spectrum` runs a 1024-point real FFT + * over that ring and log-bins the magnitude into 128 bytes ready to + * upload as the `iAudio` GL_LUMINANCE 1D texture. + * + * The plan named KISSFFT; we use a compact self-authored radix-2 FFT + * instead — a fixed 1024-point transform is a few dozen lines, keeps the + * playground "all code we wrote", and avoids a third_party vendor + + * license tail + build wiring. + */ +#pragma once + +#include + +/* Number of log-spaced magnitude bins exposed to the shader. Matches + * the width of the iAudio texture and the bar count in audio_bars.frag. */ +#define AUDIO_SPECTRUM_BINS 128 + +/* Prepare the synth for a device running at `sample_rate` Hz with + * `channels` interleaved S16 channels (1 = mono, 2 = stereo). Resets the + * pattern to row 0 and clears the analysis ring. Safe to call before the + * device is unpaused. */ +void audio_synth_init(int sample_rate, int channels); + +/* SDL audio-callback body: fill `len` bytes of `stream` with interleaved + * S16 samples from the synth, and copy the mono mix into the analysis + * ring. When muted, writes silence to `stream` and zeros into the ring + * (so the visualizer flattens) but still advances the pattern so the + * tune resumes in place on unmute. */ +void audio_synth_render(uint8_t *stream, int len); + +/* Compute the 128-bin log-spaced magnitude spectrum of the most recent + * audio into `bins` (each 0..255, ready for a GL_LUMINANCE upload). */ +void audio_compute_spectrum(uint8_t bins[AUDIO_SPECTRUM_BINS]); + +/* Mute toggles output + analysis; the pattern keeps advancing. */ +void audio_set_muted(int muted); +int audio_muted(void); + +/* Switch the playback source to a sound-shader-rendered S16 + * stereo buffer (looped). `pcm` points at `frames` interleaved stereo + * frames and must stay valid until the next call. Passing pcm=NULL or + * frames<=0 reverts to the built-in chip synth. The analysis ring (and + * thus the iAudio visualizer) tracks whichever source is active. */ +void audio_set_sound_pcm(const int16_t *pcm, int frames); diff --git a/programs/sdl2/editor.c b/programs/sdl2/editor.c new file mode 100644 index 0000000000..5f028cb3b8 --- /dev/null +++ b/programs/sdl2/editor.c @@ -0,0 +1,972 @@ +/* + * programs/sdl2/editor.c — gap-buffer text editor implementation. + * + * Buffer layout: + * + * +------------------+~~~~~~~~~~~~+------------------+ + * | pre (gs) | gap | post (cap - ge) | + * +------------------+~~~~~~~~~~~~+------------------+ + * 0 gs ge cap + * + * logical length = gs + (cap - ge) + * cursor = gs (the gap is always at the cursor) + * + * Every move_left/right shifts one byte across the gap so the cursor + * stays at the gap-start; insert/delete then act in O(1). This is the + * classic gap-buffer behavior — fine for a one-file editor with at + * most a few thousand bytes of shader source. + * + * Line table: we don't pre-index lines. Walking the buffer to find + * the line at a given offset is O(n) and runs once per render frame; + * for a 4 KB shader that's ~200 µs worst case, well below the budget. + * If we ever need bigger files we can lazily cache line starts. + */ + +#include "editor.h" +#include "renderer.h" + +#include +#include +#include +#include + +#define INITIAL_CAP 4096 +#define MIN_GAP_BYTES 128 +#define TAB_WIDTH 4 +#define MAX_LINE_BYTES 1024 + +static char *g_buf = NULL; +static size_t g_cap = 0; +static size_t g_gs = 0; +static size_t g_ge = 0; +static int g_top_line = 0; +/* Last cursor offset that editor_render scrolled into view. The render's + * scroll-into-view only re-centers when the cursor has actually moved + * (typing / navigation), so a wheel scroll (editor_scroll) that pushes the + * cursor off-screen isn't snapped straight back on the next frame. + * (size_t)-1 forces a re-center on the first render after init. */ +static size_t g_scroll_anchor = (size_t) -1; +/* Desired column when moving vertically — preserves "horizontal + * memory" across short lines like every other editor. Reset on + * insert/delete and on horizontal navigation. */ +static int g_desired_col = -1; + +/* Selection anchor (logical offset), or NO_SEL when there is no + * selection. The selected span is [min(anchor,cursor), max(...)). */ +#define NO_SEL ((size_t) -1) +static size_t g_sel_anchor = NO_SEL; + +/* Error-line marker: 0-indexed editor line the last compile error maps + * to, or -1 for none. */ +static int g_error_line = -1; + +/* ----- undo / redo ring ------------------------------------------ */ + +#define UNDO_MAX 32 +typedef struct { char *text; size_t cursor; } Snapshot; +typedef enum { EK_NONE = 0, EK_INSERT, EK_DELETE } EditKind; + +static Snapshot g_undo[UNDO_MAX]; +static int g_undo_n = 0; +static Snapshot g_redo[UNDO_MAX]; +static int g_redo_n = 0; +/* Coalescing: a run of same-kind edits shares one undo step. A cursor + * move / selection change sets g_undo_break so the next edit opens a new + * group even if it's the same kind. */ +static EditKind g_undo_last_kind = EK_NONE; +static int g_undo_break = 1; + +static inline size_t logical_length(void) { + return g_gs + (g_cap - g_ge); +} + +/* Read the byte at logical index `idx`. Assumes idx < length(). */ +static inline unsigned char char_at(size_t idx) { + if (idx < g_gs) return (unsigned char) g_buf[idx]; + return (unsigned char) g_buf[idx - g_gs + g_ge]; +} + +/* Grow the buffer if the gap is empty (gs == ge). Doubles capacity + * and shifts the post-gap region to the new end. Idempotent if the + * gap already has slack. */ +static void ensure_gap(size_t need) { + if (g_ge - g_gs >= need) return; + size_t length = logical_length(); + size_t new_cap = g_cap ? g_cap * 2 : INITIAL_CAP; + while (new_cap - length < need + MIN_GAP_BYTES) new_cap *= 2; + char *new_buf = (char *) malloc(new_cap); + if (!new_buf) { + fprintf(stderr, "FAIL: editor: malloc(%zu) failed\n", new_cap); + abort(); + } + size_t post_len = g_cap - g_ge; + if (g_buf) { + memcpy(new_buf, g_buf, g_gs); + memcpy(new_buf + new_cap - post_len, g_buf + g_ge, post_len); + free(g_buf); + } + g_buf = new_buf; + g_ge = new_cap - post_len; + g_cap = new_cap; +} + +/* (Re)build the gap buffer from `text` with the cursor (gap) at offset + * `cursor`. Frees any existing buffer. Shared by editor_init and the + * undo/redo + preset-load restore paths; does NOT log or touch the undo + * stacks. */ +static void build_buffer(const char *text, size_t cursor) { + free(g_buf); + g_cap = INITIAL_CAP; + g_buf = (char *) malloc(g_cap); + if (!g_buf) { + fprintf(stderr, "FAIL: editor: initial malloc failed\n"); + abort(); + } + g_gs = 0; + g_ge = g_cap; + g_top_line = 0; + g_scroll_anchor = (size_t) -1; + g_desired_col = -1; + g_sel_anchor = NO_SEL; + if (text && *text) { + size_t n = strlen(text); + ensure_gap(n); + memcpy(g_buf + g_gs, text, n); + g_gs += n; + } + /* Drag the gap back to `cursor` (clamped). build leaves the gap at + * end-of-text; walk it left to the requested offset. */ + size_t len = logical_length(); + if (cursor > len) cursor = len; + while (g_gs > cursor) { + g_gs--; + g_ge--; + g_buf[g_ge] = g_buf[g_gs]; + } +} + +void editor_init(const char *initial_text) { + /* Fresh document: drop any prior undo/redo history. */ + for (int i = 0; i < g_undo_n; i++) free(g_undo[i].text); + for (int i = 0; i < g_redo_n; i++) free(g_redo[i].text); + g_undo_n = g_redo_n = 0; + g_undo_last_kind = EK_NONE; + g_undo_break = 1; + g_error_line = -1; + + build_buffer(initial_text, 0); + fprintf(stdout, "sdl2: editor loaded %zu chars\n", logical_length()); +} + +void editor_shutdown(void) { + free(g_buf); + g_buf = NULL; + g_cap = g_gs = g_ge = 0; + for (int i = 0; i < g_undo_n; i++) free(g_undo[i].text); + for (int i = 0; i < g_redo_n; i++) free(g_redo[i].text); + g_undo_n = g_redo_n = 0; + g_sel_anchor = NO_SEL; +} + +/* ----- undo / redo ----------------------------------------------- */ + +static void snapshot_stack_push(Snapshot *stack, int *n, char *text, + size_t cursor) { + if (*n == UNDO_MAX) { + free(stack[0].text); + memmove(&stack[0], &stack[1], (UNDO_MAX - 1) * sizeof(Snapshot)); + (*n)--; + } + stack[*n].text = text; + stack[*n].cursor = cursor; + (*n)++; +} + +static void redo_clear(void) { + for (int i = 0; i < g_redo_n; i++) free(g_redo[i].text); + g_redo_n = 0; +} + +/* Capture the pre-edit state onto the undo stack, honoring coalescing. + * Called at the very start of every mutating op (before it changes the + * buffer or deletes a selection), so an undo restores exactly the state + * before the group began. */ +static void undo_record(EditKind kind) { + if (g_undo_break || kind != g_undo_last_kind) { + char *snap = editor_dup_text(); + if (snap) snapshot_stack_push(g_undo, &g_undo_n, snap, g_gs); + redo_clear(); + g_undo_break = 0; + } + g_undo_last_kind = kind; +} + +/* End the current coalescing group — the next edit starts a new undo + * step. Called on cursor moves and selection changes. */ +static void undo_break(void) { g_undo_break = 1; } + +void editor_undo(void) { + if (g_undo_n == 0) return; + char *cur = editor_dup_text(); + if (cur) snapshot_stack_push(g_redo, &g_redo_n, cur, g_gs); + Snapshot s = g_undo[--g_undo_n]; + build_buffer(s.text, s.cursor); + free(s.text); + g_undo_last_kind = EK_NONE; + g_undo_break = 1; + g_sel_anchor = NO_SEL; +} + +void editor_redo(void) { + if (g_redo_n == 0) return; + char *cur = editor_dup_text(); + if (cur) snapshot_stack_push(g_undo, &g_undo_n, cur, g_gs); + Snapshot s = g_redo[--g_redo_n]; + build_buffer(s.text, s.cursor); + free(s.text); + g_undo_last_kind = EK_NONE; + g_undo_break = 1; + g_sel_anchor = NO_SEL; +} + +void editor_replace_all(const char *text) { + undo_break(); /* force a fresh snapshot of the pre-state */ + undo_record(EK_INSERT); + undo_break(); /* and the next edit is its own group too */ + build_buffer(text ? text : "", 0); + g_desired_col = -1; +} + +void editor_set_error_line(int line0) { g_error_line = line0; } + +/* ----- insertions ------------------------------------------------ */ + +void editor_insert_char(char c) { + undo_record(EK_INSERT); + if (editor_has_selection()) editor_delete_selection(); + ensure_gap(1); + g_buf[g_gs++] = c; + g_desired_col = -1; +} + +void editor_insert_text(const char *s, size_t n) { + if (!n) return; + undo_record(EK_INSERT); + if (editor_has_selection()) editor_delete_selection(); + ensure_gap(n); + memcpy(g_buf + g_gs, s, n); + g_gs += n; + g_desired_col = -1; +} + +void editor_insert_newline(void) { + editor_insert_char('\n'); +} + +void editor_insert_tab(void) { + static const char spaces[TAB_WIDTH] = {' ', ' ', ' ', ' '}; + editor_insert_text(spaces, TAB_WIDTH); +} + +/* ----- deletions ------------------------------------------------- */ + +void editor_delete_back(void) { + if (editor_has_selection()) { + undo_record(EK_DELETE); + editor_delete_selection(); + return; + } + if (g_gs == 0) return; + undo_record(EK_DELETE); + g_gs--; + g_desired_col = -1; +} + +void editor_delete_forward(void) { + if (editor_has_selection()) { + undo_record(EK_DELETE); + editor_delete_selection(); + return; + } + if (g_ge == g_cap) return; + undo_record(EK_DELETE); + g_ge++; + g_desired_col = -1; +} + +/* ----- movement -------------------------------------------------- */ + +void editor_move_left(void) { + undo_break(); + if (g_gs == 0) return; + g_gs--; + g_ge--; + g_buf[g_ge] = g_buf[g_gs]; + g_desired_col = -1; +} + +void editor_move_right(void) { + undo_break(); + if (g_ge == g_cap) return; + g_buf[g_gs] = g_buf[g_ge]; + g_gs++; + g_ge++; + g_desired_col = -1; +} + +/* Move the gap (cursor) to a specific logical offset. O(distance). */ +static void move_to_offset(size_t target) { + undo_break(); + size_t len = logical_length(); + if (target > len) target = len; + while (g_gs > target) editor_move_left(); + while (g_gs < target) editor_move_right(); +} + +/* Returns the logical offset of the start of the line containing + * `pos`. The start of the first line is 0; subsequent line starts + * follow a '\n'. */ +static size_t line_start_of(size_t pos) { + if (pos == 0) return 0; + size_t i = pos; + while (i > 0) { + if (char_at(i - 1) == '\n') return i; + i--; + } + return 0; +} + +/* Returns the logical offset of the '\n' that terminates the line + * containing `pos`, or logical_length() if the line is unterminated. */ +static size_t line_end_of(size_t pos) { + size_t len = logical_length(); + size_t i = pos; + while (i < len && char_at(i) != '\n') i++; + return i; +} + +static int current_col(void) { + return (int)(g_gs - line_start_of(g_gs)); +} + +void editor_move_home(void) { + size_t ls = line_start_of(g_gs); + move_to_offset(ls); + g_desired_col = -1; +} + +void editor_move_end(void) { + size_t le = line_end_of(g_gs); + move_to_offset(le); + g_desired_col = -1; +} + +static void move_vertical(int direction) { + /* direction = -1 (up) or +1 (down). Computes the desired column + * from the current position the first time we move vertically, + * then preserves it across subsequent vertical moves so a long + * line doesn't lose horizontal position when transiting through + * short lines. */ + int col = g_desired_col >= 0 ? g_desired_col : current_col(); + size_t cur_ls = line_start_of(g_gs); + if (direction < 0) { + if (cur_ls == 0) return; + size_t prev_ls = line_start_of(cur_ls - 1); + size_t prev_le = cur_ls - 1; + int prev_len = (int)(prev_le - prev_ls); + int target_col = col < prev_len ? col : prev_len; + move_to_offset(prev_ls + (size_t) target_col); + } else { + size_t len = logical_length(); + size_t cur_le = line_end_of(g_gs); + if (cur_le >= len) return; + size_t next_ls = cur_le + 1; + size_t next_le = line_end_of(next_ls); + int next_len = (int)(next_le - next_ls); + int target_col = col < next_len ? col : next_len; + move_to_offset(next_ls + (size_t) target_col); + } + /* Set the remembered column AFTER the move: move_to_offset runs the + * horizontal move primitives, which each reset g_desired_col. */ + g_desired_col = col; +} + +void editor_move_up(void) { move_vertical(-1); } +void editor_move_down(void) { move_vertical(+1); } + +void editor_move_page_up(int visible_lines) { + int n = visible_lines > 1 ? visible_lines - 1 : 1; + for (int i = 0; i < n; i++) move_vertical(-1); +} + +void editor_move_page_down(int visible_lines) { + int n = visible_lines > 1 ? visible_lines - 1 : 1; + for (int i = 0; i < n; i++) move_vertical(+1); +} + +/* Scroll the viewport by `delta_lines` (negative = toward the top of the + * file) WITHOUT moving the cursor — the mouse-wheel path. Clamped so the + * top line can't go past the last line. Because editor_render only + * re-centers on the cursor when it moves, this scroll position persists + * until the user navigates or edits. */ +void editor_scroll(int delta_lines) { + int total = editor_line_count(); + g_top_line += delta_lines; + if (g_top_line > total - 1) g_top_line = total - 1; + if (g_top_line < 0) g_top_line = 0; +} + +/* ----- selection ------------------------------------------------- */ + +int editor_has_selection(void) { + return g_sel_anchor != NO_SEL && g_sel_anchor != g_gs; +} + +void editor_selection_begin(void) { + if (g_sel_anchor == NO_SEL) g_sel_anchor = g_gs; +} + +void editor_selection_clear(void) { + g_sel_anchor = NO_SEL; +} + +void editor_select_all(void) { + undo_break(); + g_sel_anchor = 0; + move_to_offset(logical_length()); + g_desired_col = -1; +} + +/* Ordered selection bounds written to *a (lo) and *b (hi). Returns 0, + * leaving both untouched, when there is no selection. */ +static int selection_bounds(size_t *a, size_t *b) { + if (!editor_has_selection()) return 0; + size_t lo = g_sel_anchor < g_gs ? g_sel_anchor : g_gs; + size_t hi = g_sel_anchor < g_gs ? g_gs : g_sel_anchor; + *a = lo; + *b = hi; + return 1; +} + +char *editor_selection_dup(void) { + size_t a, b; + if (!selection_bounds(&a, &b)) return NULL; + size_t n = b - a; + char *out = (char *) malloc(n + 1); + if (!out) return NULL; + for (size_t i = 0; i < n; i++) out[i] = (char) char_at(a + i); + out[n] = '\0'; + return out; +} + +/* Raw removal of the selected span — does NOT record undo (the public + * insert/delete ops call undo_record before invoking this). Cursor lands + * at the span start; selection is cleared. */ +void editor_delete_selection(void) { + size_t a, b; + if (!selection_bounds(&a, &b)) return; + move_to_offset(a); + size_t count = b - a; + while (count-- > 0 && g_ge < g_cap) g_ge++; + g_sel_anchor = NO_SEL; + g_desired_col = -1; +} + +/* ----- inspection ------------------------------------------------ */ + +size_t editor_cursor_offset(void) { return g_gs; } +int editor_cursor_col(void) { return current_col(); } + +int editor_cursor_line(void) { + int line = 0; + for (size_t i = 0; i < g_gs; i++) { + if (char_at(i) == '\n') line++; + } + return line; +} + +int editor_line_count(void) { + int lines = 1; + size_t len = logical_length(); + for (size_t i = 0; i < len; i++) { + if (char_at(i) == '\n') lines++; + } + return lines; +} + +char *editor_dup_text(void) { + size_t len = logical_length(); + char *out = (char *) malloc(len + 1); + if (!out) return NULL; + memcpy(out, g_buf, g_gs); + memcpy(out + g_gs, g_buf + g_ge, g_cap - g_ge); + out[len] = '\0'; + return out; +} + +/* ----- render ---------------------------------------------------- */ + +static const float COLOR_BG_R = 0.10f; +static const float COLOR_BG_G = 0.10f; +static const float COLOR_BG_B = 0.13f; +static const float GUTTER_BG_R = 0.13f; +static const float GUTTER_BG_G = 0.13f; +static const float GUTTER_BG_B = 0.17f; +static const float GUTTER_FG_R = 0.45f; +static const float GUTTER_FG_G = 0.45f; +static const float GUTTER_FG_B = 0.50f; +/* Editor text color now comes from the syntax highlighter (Dracula + * palette, HL_* below) — see draw_highlighted_line. */ +static const float CURSOR_R = 1.00f; +static const float CURSOR_G = 0.88f; +static const float CURSOR_B = 0.55f; +/* Selection highlight (translucent Dracula "selection" #44475a) and the + * compile-error line tint (translucent red) + its gutter marker color. */ +static const float SEL_R = 0.267f; +static const float SEL_G = 0.278f; +static const float SEL_B = 0.353f; +static const float SEL_A = 0.90f; +static const float ERRLINE_R = 0.50f; +static const float ERRLINE_G = 0.12f; +static const float ERRLINE_B = 0.14f; +static const float ERRLINE_A = 0.35f; +static const float ERRMARK_R = 1.00f; +static const float ERRMARK_G = 0.40f; +static const float ERRMARK_B = 0.40f; +static const int GUTTER_PAD = 6; +static const int GUTTER_DIGITS = 4; /* line numbers up to 9999 */ +/* Vertical breathing room above the first line so it isn't flush + * against the pane's top border. Applied consistently by the layout, + * the renderer, and the click hit-test. */ +static const int EDITOR_TOP_PAD = 8; + +/* Shared layout computation — both `editor_render` and + * `editor_pointer_set_cursor` need the same gutter / text origin so a + * click lands where the glyph was drawn. */ +typedef struct { + int line_h; + int advance; + int gutter_w; + int text_x; + int text_w; + int visible_lines; +} EditorLayout; + +static int compute_layout(int x, int w, int h, EditorLayout *out) { + int line_h = renderer_text_line_height(); + int advance = renderer_text_advance(); + if (line_h <= 0 || advance <= 0) return 0; + int gutter_w = advance * GUTTER_DIGITS + GUTTER_PAD * 2; + if (gutter_w > w / 2) gutter_w = w / 2; + int text_x = x + gutter_w + GUTTER_PAD; + int text_w = w - (text_x - x); + int visible_lines = (h - EDITOR_TOP_PAD) / line_h; + if (visible_lines < 1) visible_lines = 1; + out->line_h = line_h; + out->advance = advance; + out->gutter_w = gutter_w; + out->text_x = text_x; + out->text_w = text_w; + out->visible_lines = visible_lines; + return 1; +} + +/* Walk the buffer and return the byte length of `line` (0-indexed), + * along with its starting offset. Returns -1 if `line` is past the + * end. */ +static int line_extent(int line, size_t *out_start, size_t *out_len) { + size_t len = logical_length(); + size_t pos = 0; + int idx = 0; + while (idx < line) { + if (pos >= len) return -1; + if (char_at(pos) == '\n') idx++; + pos++; + } + size_t start = pos; + while (pos < len && char_at(pos) != '\n') pos++; + *out_start = start; + *out_len = pos - start; + return 0; +} + +/* Map a window-pixel point to a logical offset using the same layout the + * frame was drawn with. Returns 0 if the point is outside the pane or the + * layout is degenerate (caller should ignore), else 1 with *off set. */ +static int pointer_to_offset(int px, int py, + int x, int y, int w, int h, size_t *off) { + EditorLayout L; + if (!compute_layout(x, w, h, &L)) return 0; + if (px < x || px >= x + w || py < y || py >= y + h) return 0; + + int row = (py - y - EDITOR_TOP_PAD) / L.line_h; + if (row < 0) row = 0; + if (row >= L.visible_lines) row = L.visible_lines - 1; + int target_line = g_top_line + row; + int total_lines = editor_line_count(); + if (target_line >= total_lines) target_line = total_lines - 1; + if (target_line < 0) target_line = 0; + + /* Column: clicks left of the text origin (gutter) land at column 0. */ + int col_px = px - L.text_x; + int target_col = col_px < 0 ? 0 : col_px / L.advance; + + size_t line_start = 0, line_len = 0; + if (line_extent(target_line, &line_start, &line_len) != 0) return 0; + if ((size_t) target_col > line_len) target_col = (int) line_len; + *off = line_start + (size_t) target_col; + return 1; +} + +void editor_pointer_set_cursor(int px, int py, + int x, int y, int w, int h) { + size_t off; + if (!pointer_to_offset(px, py, x, y, w, h, &off)) return; + editor_selection_clear(); + move_to_offset(off); + g_desired_col = -1; +} + +void editor_pointer_extend_select(int px, int py, + int x, int y, int w, int h) { + size_t off; + if (!pointer_to_offset(px, py, x, y, w, h, &off)) return; + editor_selection_begin(); /* anchor at the press point if unset */ + move_to_offset(off); + g_desired_col = -1; +} + +/* ----- syntax highlighting (GLSL ES 1.00, Dracula palette) -------- * + * + * The font is monospace, so a token that starts at column `c` is drawn + * at `text_x + c * advance`. We tokenize each visible line and emit one + * colored `renderer_draw_text` span per token. The only state that + * crosses a line boundary is "are we inside a block comment" — GLSL has + * no string literals — so the renderer threads a single `in_block` flag + * from one visible line to the next, seeded from the buffer prefix above + * the viewport by block_comment_state_at(). + * + * Six semantic colors (Dracula), plus the Dracula foreground for + * everything else (whitespace, identifiers not in a word list, and the + * structural delimiters ()[]{};,.). "Operator" covers the arithmetic / + * comparison / logical / assignment symbols; delimiters are left default + * to keep the punctuation noise down. */ +static const float HL_DEFAULT[3] = {0.973f, 0.973f, 0.949f}; /* #f8f8f2 */ +static const float HL_COMMENT[3] = {0.384f, 0.447f, 0.643f}; /* #6272a4 */ +static const float HL_KEYWORD[3] = {1.000f, 0.475f, 0.776f}; /* #ff79c6 */ +static const float HL_TYPE[3] = {0.545f, 0.914f, 0.992f}; /* #8be9fd */ +static const float HL_BUILTIN[3] = {0.314f, 0.980f, 0.482f}; /* #50fa7b */ +static const float HL_NUMBER[3] = {0.741f, 0.576f, 0.976f}; /* #bd93f9 */ +static const float HL_OPERATOR[3] = {0.945f, 0.980f, 0.549f}; /* #f1fa8c */ + +/* GLSL ES 1.00 control-flow keywords and qualifiers. */ +static const char *const HL_KEYWORDS[] = { + "if", "else", "for", "while", "do", "break", "continue", "return", + "discard", "in", "out", "inout", "uniform", "attribute", "varying", + "const", "precision", "highp", "mediump", "lowp", "struct", "true", + "false", "invariant", NULL, +}; + +/* Built-in types. */ +static const char *const HL_TYPES[] = { + "void", "bool", "int", "float", "vec2", "vec3", "vec4", "mat2", + "mat3", "mat4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", + "sampler2D", "samplerCube", NULL, +}; + +/* Built-in functions, gl_* variables, and the Shadertoy-shape uniforms / + * entry points our template provides (iTime, iResolution, mainImage, …). */ +static const char *const HL_BUILTINS[] = { + "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", + "sinh", "cosh", "tanh", "tanh4", "pow", "exp", "log", "exp2", "log2", + "sqrt", "inversesqrt", "abs", "sign", "floor", "ceil", "fract", "mod", + "min", "max", "clamp", "mix", "step", "smoothstep", "length", + "distance", "dot", "cross", "normalize", "reflect", "refract", + "faceforward", "matrixCompMult", "lessThan", "lessThanEqual", + "greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", + "not", "texture2D", "texture2DProj", "textureCube", "dFdx", "dFdy", + "fwidth", + "gl_FragColor", "gl_FragCoord", "gl_FragData", "gl_Position", + "gl_PointSize", "gl_PointCoord", "gl_FrontFacing", + "iResolution", "iTime", "iTimeDelta", "iFrame", "iMouse", "iDate", + "iSampleRate", "iChannel0", "iChannel1", "iChannel2", "iChannel3", + "iAudio", "iBufferOffset", "fragCoord", "fragColor", "mainImage", + "mainSound", NULL, +}; + +static inline int hl_is_digit(unsigned char c) { return c >= '0' && c <= '9'; } +static inline int hl_is_ident_start(unsigned char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; +} +static inline int hl_is_ident_char(unsigned char c) { + return hl_is_ident_start(c) || hl_is_digit(c); +} +static inline int hl_is_operator(unsigned char c) { + return c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || + c == '=' || c == '<' || c == '>' || c == '!' || c == '&' || + c == '|' || c == '^' || c == '~' || c == '?' || c == ':'; +} + +static int hl_in_list(const char *s, size_t len, const char *const *list) { + for (size_t i = 0; list[i]; i++) { + if (strlen(list[i]) == len && memcmp(s, list[i], len) == 0) return 1; + } + return 0; +} + +static const float *hl_classify_word(const char *s, size_t len) { + if (hl_in_list(s, len, HL_KEYWORDS)) return HL_KEYWORD; + if (hl_in_list(s, len, HL_TYPES)) return HL_TYPE; + if (hl_in_list(s, len, HL_BUILTINS)) return HL_BUILTIN; + return HL_DEFAULT; +} + +/* Compute whether logical offset `target` sits inside a block comment, + * by tokenizing comments from the buffer start. O(target) — runs once per + * frame to seed the first visible line's state. Line comments (//) and + * block comments (slash-star) are tracked; a // inside a block, or a + * slash-star inside a //, does not nest (matching the GLSL lexer). */ +static int block_comment_state_at(size_t target) { + size_t len = logical_length(); + int in_block = 0, in_line = 0; + for (size_t i = 0; i < target; i++) { + unsigned char c = char_at(i); + unsigned char d = (i + 1 < len) ? char_at(i + 1) : 0; + if (in_line) { if (c == '\n') in_line = 0; continue; } + if (in_block) { + if (c == '*' && d == '/') { in_block = 0; i++; } + continue; + } + if (c == '/' && d == '/') { in_line = 1; i++; } + else if (c == '/' && d == '*') { in_block = 1; i++; } + } + return in_block; +} + +/* Tokenize `s[0..n)` and draw each token as a colored span. Drawing is + * clamped to the first `draw_limit` columns (the visible width); tokens + * are still scanned past that point so the returned in-block-comment + * state stays correct when a slash-star-slash terminator lies off-screen. + * Returns the updated in_block flag for the next line. */ +static int draw_highlighted_line(int text_x, int line_y, int advance, + const char *s, size_t n, size_t draw_limit, + int in_block) { + size_t i = 0; + while (i < n) { + size_t start = i; + const float *color; + if (in_block) { + while (i < n) { + if (s[i] == '*' && i + 1 < n && s[i + 1] == '/') { + i += 2; in_block = 0; break; + } + i++; + } + color = HL_COMMENT; + } else if (s[i] == '/' && i + 1 < n && s[i + 1] == '/') { + i = n; /* line comment to EOL */ + color = HL_COMMENT; + } else if (s[i] == '/' && i + 1 < n && s[i + 1] == '*') { + i += 2; in_block = 1; + while (i < n) { + if (s[i] == '*' && i + 1 < n && s[i + 1] == '/') { + i += 2; in_block = 0; break; + } + i++; + } + color = HL_COMMENT; + } else if (s[i] == '#') { /* preprocessor directive */ + i++; + while (i < n && hl_is_ident_char((unsigned char) s[i])) i++; + color = HL_KEYWORD; + } else if (hl_is_ident_start((unsigned char) s[i])) { + while (i < n && hl_is_ident_char((unsigned char) s[i])) i++; + color = hl_classify_word(s + start, i - start); + } else if (hl_is_digit((unsigned char) s[i]) || + (s[i] == '.' && i + 1 < n && + hl_is_digit((unsigned char) s[i + 1]))) { + i++; + while (i < n && + (hl_is_ident_char((unsigned char) s[i]) || s[i] == '.' || + ((s[i] == '+' || s[i] == '-') && + (s[i - 1] == 'e' || s[i - 1] == 'E')))) { + i++; + } + color = HL_NUMBER; + } else if (hl_is_operator((unsigned char) s[i])) { + while (i < n && hl_is_operator((unsigned char) s[i])) { + /* Don't let an operator run swallow a following comment. */ + if (s[i] == '/' && i + 1 < n && + (s[i + 1] == '/' || s[i + 1] == '*')) break; + i++; + } + color = HL_OPERATOR; + } else { + /* Whitespace + structural delimiters: one default-colored run, + * stopping at the next token start. */ + i++; + while (i < n) { + unsigned char c = (unsigned char) s[i]; + if (hl_is_ident_start(c) || hl_is_digit(c) || + hl_is_operator(c) || c == '#' || + (c == '.' && i + 1 < n && + hl_is_digit((unsigned char) s[i + 1]))) { + break; + } + i++; + } + color = HL_DEFAULT; + } + + if (start < draw_limit) { + size_t end = i < draw_limit ? i : draw_limit; + renderer_draw_text(text_x + (int) start * advance, line_y, + s + start, end - start, + color[0], color[1], color[2]); + } + } + return in_block; +} + +void editor_render(int x, int y, int w, int h, + unsigned int now_ms, int has_focus) { + if (w <= 0 || h <= 0) return; + + EditorLayout L; + if (!compute_layout(x, w, h, &L)) return; + int line_h = L.line_h; + int advance = L.advance; + int gutter_w = L.gutter_w; + int text_x = L.text_x; + int text_w = L.text_w; + int visible_lines = L.visible_lines; + + /* Scroll-into-view: if the cursor's line is outside the current + * window of `visible_lines` starting at g_top_line, scroll. Only when + * the cursor moved since the last render — otherwise a wheel scroll + * (editor_scroll) that pushed the cursor off-screen would be snapped + * straight back here every frame. */ + int cur_line = editor_cursor_line(); + size_t cur_off = editor_cursor_offset(); + if (cur_off != g_scroll_anchor) { + if (cur_line < g_top_line) g_top_line = cur_line; + if (cur_line >= g_top_line + visible_lines) { + g_top_line = cur_line - visible_lines + 1; + } + g_scroll_anchor = cur_off; + } + if (g_top_line < 0) g_top_line = 0; + + /* Background fills (one solid quad each). */ + renderer_fill_rect(x, y, w, h, + COLOR_BG_R, COLOR_BG_G, COLOR_BG_B, 1.0f); + renderer_fill_rect(x, y, gutter_w, h, + GUTTER_BG_R, GUTTER_BG_G, GUTTER_BG_B, 1.0f); + + /* Walk to the start of g_top_line. */ + size_t len = logical_length(); + size_t pos = 0; + int line_idx = 0; + while (pos < len && line_idx < g_top_line) { + if (char_at(pos) == '\n') line_idx++; + pos++; + } + + /* Seed the syntax highlighter's block-comment state from everything + * above the first visible line, then thread it down line by line. */ + int in_block = block_comment_state_at(pos); + + /* Visible cursor blink: ON for 600 ms, OFF for 400 ms. Off when + * unfocused. */ + int cursor_visible = has_focus && (((now_ms / 100) % 10) < 6); + + /* Selection span (logical offsets) for the highlight pass. */ + size_t sel_a = 0, sel_b = 0; + int has_sel = selection_bounds(&sel_a, &sel_b); + + int total_lines = editor_line_count(); + char line_buf[MAX_LINE_BYTES]; + char gutter_buf[16]; + int line_y = y + EDITOR_TOP_PAD; + for (int row = 0; + row < visible_lines && g_top_line + row < total_lines; + row++) { + int this_line = g_top_line + row; + size_t line_start_off = pos; /* logical offset of this line's col 0 */ + + /* Extract this line into line_buf. */ + size_t k = 0; + while (pos < len) { + unsigned char c = char_at(pos); + pos++; + if (c == '\n') break; + if (k < sizeof line_buf - 1) line_buf[k++] = (char) c; + } + line_buf[k] = '\0'; + + int max_cols = text_w / advance; + if (max_cols < 0) max_cols = 0; + + /* Error-line tint: a full-width red wash behind the failing line. */ + int is_error_line = (g_error_line >= 0 && this_line == g_error_line); + if (is_error_line) { + renderer_fill_rect(text_x, line_y, text_w, line_h, + ERRLINE_R, ERRLINE_G, ERRLINE_B, ERRLINE_A); + } + + /* Selection wash: intersect [sel_a, sel_b) with this line. A line + * fully inside the selection extends one extra cell to hint that + * the trailing newline is included. */ + if (has_sel && sel_a <= line_start_off + k && sel_b > line_start_off) { + size_t a = sel_a > line_start_off ? sel_a - line_start_off : 0; + size_t b = sel_b - line_start_off; + int newline_sel = b > k; /* selection runs past EOL */ + if (b > k) b = k; + if ((size_t) max_cols < a) a = (size_t) max_cols; + if ((size_t) max_cols < b) b = (size_t) max_cols; + int wcols = (int) (b - a) + (newline_sel ? 1 : 0); + if (wcols > 0) { + renderer_fill_rect(text_x + (int) a * advance, line_y, + wcols * advance, line_h, + SEL_R, SEL_G, SEL_B, SEL_A); + } + } + + /* Gutter line number — right-aligned in the gutter. 1-indexed + * for human-readable display. The failing line is prefixed with + * '!' and drawn in red. */ + int displayed = this_line + 1; + if (is_error_line) { + snprintf(gutter_buf, sizeof gutter_buf, "!%3d", displayed); + renderer_draw_textz(x + GUTTER_PAD, line_y, gutter_buf, + ERRMARK_R, ERRMARK_G, ERRMARK_B); + } else { + snprintf(gutter_buf, sizeof gutter_buf, "%4d", displayed); + renderer_draw_textz(x + GUTTER_PAD, line_y, gutter_buf, + GUTTER_FG_R, GUTTER_FG_G, GUTTER_FG_B); + } + + /* Source text, syntax-highlighted. Drawing is clamped to the + * visible width; the full line is still scanned so the carried + * block-comment state stays correct. */ + size_t draw_n = k <= (size_t) max_cols ? k : (size_t) max_cols; + in_block = draw_highlighted_line(text_x, line_y, advance, + line_buf, k, draw_n, in_block); + + /* Cursor — draw on top if it's on this row. */ + if (cursor_visible) { + if (cur_line == this_line) { + int col = editor_cursor_col(); + int cx = text_x + col * advance; + int cy = line_y; + int cw = has_focus ? 2 : 1; + int ch = line_h - 2; + renderer_fill_rect(cx, cy, cw, ch, + CURSOR_R, CURSOR_G, CURSOR_B, + has_focus ? 0.95f : 0.55f); + } + } + + line_y += line_h; + } +} diff --git a/programs/sdl2/editor.h b/programs/sdl2/editor.h new file mode 100644 index 0000000000..cd87467386 --- /dev/null +++ b/programs/sdl2/editor.h @@ -0,0 +1,113 @@ +/* + * programs/sdl2/editor.h — text editor surface for the left pane of the + * SDL2 GLSL playground. A monospace gap-buffer editor sized for one + * shader source file at a time: printable ASCII insert, backspace, + * delete, arrows, home/end/pgup/pgdn, soft-tab, enter, selection, + * clipboard-friendly dup, and undo/redo. + * + * Cursor convention: `cursor` is a logical character offset in the + * range [0, length()]; insert at cursor places the new character just + * before the cursor, then advances it by one (so the cursor stays + * immediately after the last typed character). + * + * The editor is pure state + rendering — it does NOT debounce, it + * does NOT recompile, it does NOT write to disk. Those are main.c's + * concerns; main.c calls into the editor for each keystroke and reads + * back the text via editor_dup_text() when it's time to recompile or + * save. Keeping the layers split makes the editor unit-testable on its + * own without mocking SDL/GL (see programs/sdl2/test/editor_test.c). + */ +#pragma once + +#include + +void editor_init(const char *initial_text); +void editor_shutdown(void); + +/* Insertions ------------------------------------------------------ */ +void editor_insert_char(char c); +void editor_insert_text(const char *s, size_t n); +void editor_insert_newline(void); +void editor_insert_tab(void); /* soft tab: 4 spaces */ + +/* Deletions ------------------------------------------------------- */ +void editor_delete_back(void); /* backspace */ +void editor_delete_forward(void); /* delete key */ + +/* Selection + clipboard ------------------------------------------- * + * + * Selection is an anchor offset plus the cursor: the selected span is + * [min(anchor,cursor), max(anchor,cursor)). main.c drives it from the + * keyboard/mouse: call editor_selection_begin() before a shift-extended + * move (anchors at the current cursor if not already anchored), and + * editor_selection_clear() before a plain move/click. Insert/delete + * automatically replace the active selection, so callers don't special- + * case it. */ +void editor_selection_begin(void); /* anchor at cursor if unanchored */ +void editor_selection_clear(void); +void editor_select_all(void); +int editor_has_selection(void); +/* malloc'd null-terminated copy of the selected text, or NULL if the + * selection is empty. */ +char *editor_selection_dup(void); +/* Remove the selected span (cursor lands at its start). No-op if empty. */ +void editor_delete_selection(void); + +/* Undo / redo ----------------------------------------------------- * + * Snapshot ring (~32 states) of (text, cursor). Consecutive same-kind + * edits coalesce into one step; a cursor move or selection change ends + * the current group. */ +void editor_undo(void); +void editor_redo(void); + +/* Replace the whole buffer (preset load). Recorded as one undo step; + * cursor resets to the top and any selection is cleared. */ +void editor_replace_all(const char *text); + +/* Error-line marker ----------------------------------------------- * + * `line0` is the 0-indexed editor line the last compile error maps to, + * or -1 to clear. editor_render tints that line and prefixes its gutter + * number with '!'. */ +void editor_set_error_line(int line0); + +/* Movement -------------------------------------------------------- */ +void editor_move_left(void); +void editor_move_right(void); +void editor_move_up(void); +void editor_move_down(void); +void editor_move_home(void); +void editor_move_end(void); +void editor_move_page_up(int visible_lines); +void editor_move_page_down(int visible_lines); +/* Scroll the viewport by delta_lines without moving the cursor (mouse + * wheel). Negative scrolls toward the top of the file. */ +void editor_scroll(int delta_lines); + +/* Inspection ------------------------------------------------------ */ +size_t editor_cursor_offset(void); +int editor_cursor_line(void); +int editor_cursor_col(void); +int editor_line_count(void); + +/* Returns a malloc'd null-terminated copy of the entire text. */ +char *editor_dup_text(void); + +/* Render the editor surface into the pixel rectangle (x, y, w, h) + * of the window. `now_ms` drives the cursor blink. The renderer module + * handles the actual glyph drawing; the editor owns layout, gutter, + * and scrolling. `has_focus` toggles a solid vs hollow cursor. */ +void editor_render(int x, int y, int w, int h, + unsigned int now_ms, int has_focus); + +/* Move the cursor to the buffer offset under the window-pixel point + * (px, py), given the same editor render rectangle (x, y, w, h) the + * frame was drawn with. Clamps the column to the line length and the + * line to the last visible line. */ +void editor_pointer_set_cursor(int px, int py, + int x, int y, int w, int h); + +/* Like editor_pointer_set_cursor but keeps the selection anchor fixed + * and moves only the cursor — the mouse-drag selection path. Anchors at + * the current cursor first if there is no active selection. */ +void editor_pointer_extend_select(int px, int py, + int x, int y, int w, int h); diff --git a/programs/sdl2/main.c b/programs/sdl2/main.c new file mode 100644 index 0000000000..bc1ed888d4 --- /dev/null +++ b/programs/sdl2/main.c @@ -0,0 +1,975 @@ +/* + * programs/sdl2/main.c — entry point for the SDL2 GLSL playground + * (`sdl2.wasm`, installed at /usr/local/bin/sdl2). Keystrokes typed + * into the left pane edit a gap buffer (`editor.c`), 250 ms of + * keystroke-idle triggers a recompile of the user shader, and Ctrl+S + * writes the editor's contents to `/home/shaders/image/current.frag` + * and force-reloads. + * + * main.c stays at orchestration: SDL/GL/audio init, the main event + * loop, key routing, the auto-recompile timer, F5/Ctrl+S, ESC quit. + * All GL drawing (user shader, error strip, editor text, font atlas) + * lives in `renderer.c`; all gap-buffer + cursor + line navigation + * lives in `editor.c`. + * + * Startup shader-source chain: + * 1. /home/shaders/image/current.frag (user-editable, primary) + * 2. /usr/share/shaders/image/plasma.frag (preset shipped in VFS) + * 3. built-in PLASMA_SRC string (handed off to renderer.c) + * + * F5 still re-reads ONLY (1). Auto-recompile after typing pulls from + * the editor's in-memory buffer, NOT the file — so the user sees their + * edits reflected in the right pane even before they save. Ctrl+S + * persists the editor's buffer to (1) and triggers an F5-equivalent + * reload so the live image matches the on-disk state. + * + * Audio runs on SDL's own audio thread; main-loop writes to the state + * the callback reads hold SDL_LockAudioDevice. + */ + +#include +#include +#include /* readdir for the Ctrl+L preset list */ +#include +#include +#include +#include +#include /* mkdir for Ctrl+S parent-dir creation */ + +#include "audio.h" +#include "editor.h" +#include "renderer.h" +#include "sound_shader.h" + +/* Used only when SDL_GetCurrentDisplayMode fails (headless harness). + * In the browser path we honor the kernel-reported display size as-is + * so the GL framebuffer matches the kandelo canvas dimensions and + * nothing gets letterboxed inside the pane. */ +static const int WINDOW_FALLBACK_W = 1280; +static const int WINDOW_FALLBACK_H = 720; + +/* Editor : render split, left-to-right. The render pane houses the + * live image shader; the editor takes the rest. The split is 2/3 + * editor + 1/3 render — the editor is the surface the user is + * actually working in, and the render pane is a preview. */ +static const int EDITOR_NUMERATOR = 2; +static const int EDITOR_DENOMINATOR = 3; + +static const char *USER_CURRENT_PATH = "/home/shaders/image/current.frag"; +/* Boot default: the image preset leg points at "Trailing the + * Twinkling Tunnelwisp" (CC0, ported from shadertoy.com/view/WfcGWj). + * plasma.frag is still staged as a loadable preset; it just isn't what + * boots. */ +static const char *PRESET_IMAGE_PATH = "/usr/share/shaders/image/tunnelwisp.frag"; + +/* The sound-shader source chain mirrors the image chain. Its preset leg + * points at the Tunnelwisp track; sine.frag stays a loadable preset. */ +static const char *USER_SOUND_PATH = "/home/shaders/sound/current.frag"; +static const char *PRESET_SOUND_PATH = "/usr/share/shaders/sound/tunnelwisp.frag"; + +static const unsigned int AUTO_RECOMPILE_DEBOUNCE_MS = 250; + +/* Lines the editor viewport moves per mouse-wheel notch. */ +static const int MOUSE_WHEEL_SCROLL_LINES = 3; + +/* Boot splash duration over the render pane (ms). The backdrop fades out + * over the tail; the title persists in the render-pane corner afterward. */ +static const unsigned int SPLASH_MS = 2600; + +/* Editor target: F1 edits the image shader, F2 the sound shader. Only + * one buffer lives in the editor at a time; the other is parked in + * g_image_text / g_sound_text (main owns those strings). */ +typedef enum { MODE_IMAGE = 0, MODE_SOUND = 1 } EditMode; +static EditMode g_mode = MODE_IMAGE; +static char *g_image_text = NULL; +static char *g_sound_text = NULL; +static int g_audio_rate = 48000; +static SDL_AudioDeviceID g_audio_dev = 0; + +/* Built-in plasma — kept in sync with + * programs/sdl2/presets/image/plasma.frag. Last-resort fallback for + * test harnesses that don't stage a VFS shader file. */ +static const char *BUILTIN_PLASMA_SRC = + "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n" + " vec2 uv = fragCoord / iResolution.xy;\n" + " float r = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831);\n" + " float g = 0.5 + 0.5 * sin(iTime * 1.3 + uv.y * 6.2831);\n" + " float b = 0.5 + 0.5 * sin(iTime * 0.7 + (uv.x + uv.y) * 6.2831);\n" + " fragColor = vec4(r, g, b, 1.0);\n" + "}\n"; + +/* Built-in sound shader — kept in sync with + * programs/sdl2/presets/sound/sine.frag. Last-resort fallback when no + * VFS sound preset is staged (e.g. the Node test harness). */ +static const char *BUILTIN_SOUND_SRC = + "vec2 mainSound(in float time) {\n" + " float v = 0.3 * sin(6.2831853 * 220.0 * time);\n" + " return vec2(v, v);\n" + "}\n"; + +/* Renders the chip synth or the sound-shader buffer, and feeds the FFT + * analysis ring that drives the iAudio uniform. SDL's dsp backend runs + * this on its own audio thread, so every main-loop write to the state it + * reads is bracketed by SDL_LockAudioDevice. */ +static void audio_cb(void *user, Uint8 *stream, int len) { + (void) user; + audio_synth_render((Uint8 *) stream, len); +} + +/* Slurp a text file into a malloc'd null-terminated buffer. Returns + * NULL if open or read fails (caller treats that as "not present"). + * Capped at 1 MiB so a runaway pipe in /home/shaders can't OOM us. */ +static char *read_text_file(const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } + long n = ftell(f); + if (n < 0 || n > (1 << 20)) { fclose(f); return NULL; } + if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; } + char *buf = malloc((size_t) n + 1); + if (!buf) { fclose(f); return NULL; } + size_t got = fread(buf, 1, (size_t) n, f); + fclose(f); + if (got != (size_t) n) { free(buf); return NULL; } + buf[n] = '\0'; + return buf; +} + +/* Walk the source-resolution chain: user file → preset → built-in. + * The label we print into stdout lets the test harnesses confirm which + * leg of the chain was taken without reaching into the kernel. */ +static char *load_initial_shader_source(void) { + char *src = read_text_file(USER_CURRENT_PATH); + if (src) { + printf("sdl2: shader-source=%s\n", USER_CURRENT_PATH); + return src; + } + src = read_text_file(PRESET_IMAGE_PATH); + if (src) { + printf("sdl2: shader-source=%s\n", PRESET_IMAGE_PATH); + return src; + } + printf("sdl2: shader-source=builtin-plasma\n"); + return strdup(BUILTIN_PLASMA_SRC); +} + +/* Sound-shader equivalent of load_initial_shader_source: user file → + * Tunnelwisp preset → built-in sine. */ +static char *load_initial_sound_source(void) { + char *src = read_text_file(USER_SOUND_PATH); + if (src) { + printf("sdl2: sound-source=%s\n", USER_SOUND_PATH); + return src; + } + src = read_text_file(PRESET_SOUND_PATH); + if (src) { + printf("sdl2: sound-source=%s\n", PRESET_SOUND_PATH); + return src; + } + printf("sdl2: sound-source=builtin-sine\n"); + return strdup(BUILTIN_SOUND_SRC); +} + +/* Write the editor's buffer to `path`, mkdir-ing /home/shaders and + * `subdir` first in case the host never created them. Returns 1 on + * success. Shared by the image (Ctrl+S in F1) and sound (F2) save paths. */ +static int save_editor_to(const char *path, const char *subdir) { + /* Best-effort directory creation. fopen below surfaces the real + * problem if the path can't be reached; we just ensure the + * parents exist on a fresh boot. EEXIST and similar are ignored. */ + (void) mkdir("/home/shaders", 0755); + (void) mkdir(subdir, 0755); + + char *text = editor_dup_text(); + if (!text) { + fprintf(stderr, "WARN: save: editor_dup_text() returned NULL\n"); + return 0; + } + FILE *f = fopen(path, "wb"); + if (!f) { + fprintf(stderr, "WARN: save: fopen(%s) failed\n", path); + free(text); + return 0; + } + size_t n = strlen(text); + size_t wrote = fwrite(text, 1, n, f); + fclose(f); + free(text); + if (wrote != n) { + fprintf(stderr, + "WARN: save: fwrite wrote %zu of %zu bytes\n", wrote, n); + return 0; + } + fprintf(stderr, "WARN: save: wrote %zu bytes to %s\n", n, path); + return 1; +} + +/* F5 / Ctrl+S reload path: re-read the user-editable shader file + * (NOT the preset — that's first-boot-only). On read or compile + * failure, keep the last-good program and raise the red strip. */ +static void reload_user_shader_from_file(const char *trigger) { + char *src = read_text_file(USER_CURRENT_PATH); + if (!src) { + fprintf(stderr, + "WARN: %s: %s not readable\n", trigger, USER_CURRENT_PATH); + renderer_set_error_visible(1); + return; + } + int real_failure = renderer_recompile_user_shader(src); + free(src); + if (real_failure) { + renderer_set_error_visible(1); + int line = renderer_last_error_line(); + editor_set_error_line(line > 0 ? line - 1 : -1); + return; + } + renderer_set_error_visible(0); + editor_set_error_line(-1); + fprintf(stderr, "WARN: %s: reloaded %s\n", trigger, USER_CURRENT_PATH); +} + +/* Auto-recompile from the editor's in-memory buffer. Same outcome as + * an F5 reload but without the file round-trip — so the user sees + * their edits reflected the moment they stop typing for 250 ms. */ +static void recompile_from_editor(void) { + char *src = editor_dup_text(); + if (!src) return; + int real_failure = renderer_recompile_user_shader(src); + free(src); + renderer_set_error_visible(real_failure ? 1 : 0); + int line = real_failure ? renderer_last_error_line() : -1; + editor_set_error_line(line > 0 ? line - 1 : -1); + fprintf(stdout, "sdl2: editor recompile (real_failure=%d)\n", + real_failure); +} + +/* Recompile the sound shader from the editor's in-memory buffer, then + * re-render the full multi-tile track and hand the decoded PCM to the + * audio source. On a real compile failure keep the last-good program + + * audio and raise the error strip. */ +static void recompile_sound_from_editor(const char *trigger) { + char *src = editor_dup_text(); + if (!src) return; + int real_failure = sound_shader_recompile(src); + free(src); + renderer_set_error_visible(real_failure ? 1 : 0); + int line = real_failure ? sound_shader_last_error_line() : -1; + editor_set_error_line(line > 0 ? line - 1 : -1); + if (!real_failure) { + int frames = sound_shader_render(g_audio_rate); + const int16_t *pcm = sound_shader_pcm(&frames); + SDL_LockAudioDevice(g_audio_dev); + audio_set_sound_pcm(pcm, frames); + SDL_UnlockAudioDevice(g_audio_dev); + } + fprintf(stdout, "sdl2: sound recompile via %s (real_failure=%d)\n", + trigger, real_failure); +} + +/* Park the editor's current text into the slot for the active mode, then + * load the other slot. The editor is re-initialized, so the cursor resets + * to the top (the two slots persist text, not cursor position). main owns + * the two parked strings; editor_init copies from them. */ +static void switch_mode(EditMode target) { + if (target == g_mode) return; + char *cur = editor_dup_text(); + if (g_mode == MODE_IMAGE) { free(g_image_text); g_image_text = cur; } + else { free(g_sound_text); g_sound_text = cur; } + editor_shutdown(); + g_mode = target; + const char *load = (g_mode == MODE_IMAGE) ? g_image_text : g_sound_text; + editor_init(load ? load : ""); + fprintf(stdout, "sdl2: edit-mode=%s\n", + g_mode == MODE_IMAGE ? "image" : "sound"); + + /* Entering sound mode compiles + renders the sound buffer and makes + * it the audio source — so the first F2 starts the sound shader even + * before the user edits. Switching back to image leaves that audio + * playing (the picture's iAudio keeps reacting to it). The synth is + * the boot default until this first activation. */ + if (g_mode == MODE_SOUND) { + recompile_sound_from_editor("F2"); + } +} + +/* ----- clipboard (Ctrl+C / Ctrl+X / Ctrl+V) ---------------------- * + * + * Prefer the SDL clipboard so copy/paste interop with the surrounding + * environment, but keep an in-app fallback copy: under KMSDRM the SDL + * clipboard bridge may be a no-op, and we still want copy/paste to work + * within the editor. */ +static char *g_clip = NULL; + +static void clip_copy(void) { + char *s = editor_selection_dup(); + if (!s) return; + SDL_SetClipboardText(s); + free(g_clip); + g_clip = s; /* retain as the fallback source for paste */ +} + +static void clip_cut(Uint32 *edit_ts) { + if (!editor_has_selection()) return; + clip_copy(); + editor_delete_back(); /* removes the selection + records undo */ + if (edit_ts) *edit_ts = SDL_GetTicks(); +} + +static void clip_paste(Uint32 *edit_ts) { + char *sdl_text = SDL_HasClipboardText() ? SDL_GetClipboardText() : NULL; + const char *use = (sdl_text && *sdl_text) ? sdl_text : g_clip; + if (use && *use) { + editor_insert_text(use, strlen(use)); + if (edit_ts) *edit_ts = SDL_GetTicks(); + } + if (sdl_text) SDL_free(sdl_text); +} + +/* ----- preset browser (Ctrl+L cycle / Ctrl+Shift+L overlay) ------ */ + +#define MAX_PRESETS 32 +#define PRESET_NAME_MAX 64 + +static char g_preset_names[MAX_PRESETS][PRESET_NAME_MAX]; +static int g_preset_count = 0; +static int g_preset_mode = -1; /* EditMode the cached list reflects */ +static int g_preset_current = -1; /* last-loaded index, -1 = none yet */ +static int g_preset_overlay = 0; /* list overlay open */ +static int g_preset_sel = 0; /* highlighted row in the overlay */ + +static const char *preset_dir(EditMode m) { + return (m == MODE_IMAGE) ? "/usr/share/shaders/image" + : "/usr/share/shaders/sound"; +} + +/* Rebuild the cached preset list for `m` from its VFS directory: *.frag + * basenames, sorted, capped at MAX_PRESETS. Cached per-mode so repeated + * Ctrl+L doesn't re-scan. */ +static void preset_refresh(EditMode m) { + if (g_preset_mode == (int) m && g_preset_count > 0) return; + g_preset_count = 0; + g_preset_mode = (int) m; + DIR *d = opendir(preset_dir(m)); + if (!d) return; + struct dirent *e; + while ((e = readdir(d)) != NULL && g_preset_count < MAX_PRESETS) { + const char *n = e->d_name; + size_t len = strlen(n); + if (len < 6 || len >= PRESET_NAME_MAX) continue; + if (strcmp(n + len - 5, ".frag") != 0) continue; + strcpy(g_preset_names[g_preset_count++], n); + } + closedir(d); + /* Insertion sort by name — the list is tiny. */ + for (int i = 1; i < g_preset_count; i++) { + char tmp[PRESET_NAME_MAX]; + strcpy(tmp, g_preset_names[i]); + int j = i - 1; + while (j >= 0 && strcmp(g_preset_names[j], tmp) > 0) { + strcpy(g_preset_names[j + 1], g_preset_names[j]); + j--; + } + strcpy(g_preset_names[j + 1], tmp); + } +} + +/* Load preset `idx` into the editor as one undo step and recompile the + * active pipeline. */ +static void preset_load(int idx, Uint32 *edit_ts) { + if (idx < 0 || idx >= g_preset_count) return; + char path[256]; + snprintf(path, sizeof path, "%s/%s", + preset_dir(g_mode), g_preset_names[idx]); + char *text = read_text_file(path); + if (!text) { + fprintf(stderr, "WARN: preset: %s not readable\n", path); + return; + } + editor_replace_all(text); + free(text); + g_preset_current = idx; + if (g_mode == MODE_IMAGE) recompile_from_editor(); + else recompile_sound_from_editor("preset"); + if (edit_ts) *edit_ts = 0; /* compiled now — cancel any pending debounce */ + fprintf(stdout, "sdl2: preset load=%s\n", g_preset_names[idx]); +} + +static void preset_cycle(Uint32 *edit_ts) { + preset_refresh(g_mode); + if (g_preset_count == 0) { fprintf(stdout, "sdl2: preset list empty\n"); return; } + preset_load((g_preset_current + 1) % g_preset_count, edit_ts); +} + +static void preset_overlay_open(void) { + preset_refresh(g_mode); + if (g_preset_count == 0) { fprintf(stdout, "sdl2: preset list empty\n"); return; } + g_preset_overlay = 1; + g_preset_sel = g_preset_current >= 0 ? g_preset_current : 0; +} + +/* Draw the centered preset list over the editor pane (pane_w × pane_h). + * No-op when the overlay is closed. */ +static void preset_overlay_render(int pane_w, int pane_h) { + if (!g_preset_overlay) return; + int line_h = renderer_text_line_height(); + int adv = renderer_text_advance(); + if (line_h <= 0 || adv <= 0) return; + + int panel_w = 360; + if (panel_w > pane_w - 40) panel_w = pane_w - 40; + int panel_h = line_h * (g_preset_count + 1) + 16; + int px = (pane_w - panel_w) / 2; + int py = (pane_h - panel_h) / 2; + if (px < 0) px = 0; + if (py < 0) py = 0; + + renderer_fill_rect(0, 0, pane_w, pane_h, 0.0f, 0.0f, 0.0f, 0.45f); + renderer_fill_rect(px, py, panel_w, panel_h, 0.12f, 0.12f, 0.16f, 0.98f); + + int ty = py + 8; + renderer_draw_textz(px + 10, ty, + g_mode == MODE_IMAGE ? "Load image preset" + : "Load sound preset", + 0.85f, 0.85f, 0.55f); + ty += line_h; + for (int i = 0; i < g_preset_count; i++) { + if (i == g_preset_sel) { + renderer_fill_rect(px + 4, ty, panel_w - 8, line_h, + 0.27f, 0.28f, 0.35f, 0.95f); + } + renderer_draw_textz(px + 10, ty, g_preset_names[i], + 0.92f, 0.92f, 0.94f); + ty += line_h; + } +} + +/* Translate an SDL keysym + modifier set into a printable ASCII byte, + * or 0 if the keystroke should not type a character. Specific to the + * US-QWERTY layout that SDL_evdev defaults to in our SDL2 stack — we + * don't have xkbcommon, so SDL_TEXTINPUT can't be relied on. */ +static char sym_to_ascii(SDL_Keycode sym, Uint16 mod) { + int shift = (mod & KMOD_SHIFT) != 0; + /* Lowercase a..z (matches the SDLK_* numeric layout). */ + if (sym >= SDLK_a && sym <= SDLK_z) { + char base = (char) ('a' + (sym - SDLK_a)); + return shift ? (char) (base - ('a' - 'A')) : base; + } + if (sym >= SDLK_0 && sym <= SDLK_9) { + static const char shifted[10] = { + ')', '!', '@', '#', '$', '%', '^', '&', '*', '(' + }; + return shift ? shifted[sym - SDLK_0] : (char) ('0' + (sym - SDLK_0)); + } + /* Numeric keypad. SDL emits separate SDLK_KP_* symbols regardless + * of NumLock state on most stacks — the user reported KP_0..9 not + * inserting on their keyboard. Shift doesn't apply to the numpad + * digits. KP_PERIOD/PLUS/MINUS/etc. cover the surrounding keys. */ + if (sym >= SDLK_KP_1 && sym <= SDLK_KP_9) { + return (char) ('1' + (sym - SDLK_KP_1)); + } + /* Punctuation table — keysym → (unshifted, shifted). */ + switch (sym) { + case SDLK_SPACE: return ' '; + case SDLK_MINUS: return shift ? '_' : '-'; + case SDLK_EQUALS: return shift ? '+' : '='; + case SDLK_LEFTBRACKET: return shift ? '{' : '['; + case SDLK_RIGHTBRACKET: return shift ? '}' : ']'; + case SDLK_BACKSLASH: return shift ? '|' : '\\'; + case SDLK_SEMICOLON: return shift ? ':' : ';'; + case SDLK_QUOTE: return shift ? '"' : '\''; + case SDLK_BACKQUOTE: return shift ? '~' : '`'; + case SDLK_COMMA: return shift ? '<' : ','; + case SDLK_PERIOD: return shift ? '>' : '.'; + case SDLK_SLASH: return shift ? '?' : '/'; + case SDLK_KP_0: return '0'; + case SDLK_KP_PERIOD: return '.'; + case SDLK_KP_DIVIDE: return '/'; + case SDLK_KP_MULTIPLY: return '*'; + case SDLK_KP_MINUS: return '-'; + case SDLK_KP_PLUS: return '+'; + case SDLK_KP_EQUALS: return '='; + default: return 0; + } +} + +int main(void) { + /* The stdout breadcrumbs are the test contract (sdl2.test.ts waits + * on them mid-run) and the harness feeds stdout through a pipe, so + * musl full-buffers it. Line-buffer explicitly so every breadcrumb + * leaves at its newline. */ + setvbuf(stdout, NULL, _IOLBF, 0); + + setenv("SDL_VIDEODRIVER", "kmsdrm", 1); + setenv("SDL_AUDIODRIVER", "dsp", 1); + /* Without libudev, src/core/linux/SDL_evdev.c::SDL_EVDEV_Init + * leaves the device list empty (its no-udev branch is a literal + * `TODO: scan like a caveman`). SDL_EVDEV_DEVICES is the + * upstream-blessed escape hatch — class 2 = keyboard, class + * 1 = mouse — matching the kernel's two virtual input devices + * (host/test/input-evdev.test.ts). */ + setenv("SDL_EVDEV_DEVICES", + "2:/dev/input/event0,1:/dev/input/event1", 1); + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS) < 0) { + fprintf(stderr, "FAIL: SDL_Init: %s\n", SDL_GetError()); + return 1; + } + printf("sdl2: SDL_Init OK (video=%s, audio=%s)\n", + SDL_GetCurrentVideoDriver() ? SDL_GetCurrentVideoDriver() : "(none)", + SDL_GetCurrentAudioDriver() ? SDL_GetCurrentAudioDriver() : "(none)"); + + int window_w, window_h; + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(0, &mode) == 0 + && mode.w > 0 && mode.h > 0) { + /* Take the kernel-reported display size verbatim so the + * framebuffer fills the entire kandelo canvas. The earlier + * 1280×720 clamp left the bottom of the pane letterboxed + * whenever the canvas was taller than 720 px. */ + window_w = mode.w; + window_h = mode.h; + printf("sdl2: display=%dx%d\n", window_w, window_h); + } else { + window_w = WINDOW_FALLBACK_W; + window_h = WINDOW_FALLBACK_H; + printf("sdl2: display=unknown using default=%dx%d\n", + window_w, window_h); + } + + SDL_Window *win = SDL_CreateWindow( + "sdl2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + window_w, window_h, SDL_WINDOW_OPENGL); + if (!win) { + fprintf(stderr, "FAIL: SDL_CreateWindow: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + SDL_GLContext glctx = SDL_GL_CreateContext(win); + if (!glctx) { + fprintf(stderr, "FAIL: SDL_GL_CreateContext: %s\n", SDL_GetError()); + SDL_DestroyWindow(win); + SDL_Quit(); + return 1; + } + SDL_GL_MakeCurrent(win, glctx); + + int gl_w, gl_h; + SDL_GL_GetDrawableSize(win, &gl_w, &gl_h); + renderer_init(gl_w, gl_h); + + /* Resolve the startup shader source and compile it once. The + * renderer's `recompile_user_shader` returns the same headless-vs- + * real-failure discrimination main used to have inline. */ + char *initial_src = load_initial_shader_source(); + int initial_real_failure = + renderer_recompile_user_shader(initial_src); + renderer_set_error_visible(initial_real_failure ? 1 : 0); + + /* The editor takes ownership of a copy of the initial source so + * the user can immediately type over it. We also park a copy as the + * image slot so switching away and back (F1/F2) restores it. We free + * the loader's malloc'd buffer ourselves. */ + g_image_text = strdup(initial_src); + editor_init(initial_src); + free(initial_src); + + SDL_AudioSpec want, have; + SDL_zero(want); + want.freq = 48000; + want.format = AUDIO_S16LSB; + want.channels = 2; + want.samples = 1024; + want.callback = audio_cb; + SDL_AudioDeviceID audio = + SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + g_audio_dev = audio; + if (audio == 0) { + fprintf(stderr, "FAIL: SDL_OpenAudioDevice: %s\n", SDL_GetError()); + renderer_shutdown(); + editor_shutdown(); + SDL_GL_DeleteContext(glctx); + SDL_DestroyWindow(win); + SDL_Quit(); + return 1; + } + /* Initialize the synth against what the device actually granted (the + * driver may have changed freq/channels from `want`) so the sample + * rate the pattern timing uses matches the playback rate. */ + g_audio_rate = have.freq; + audio_synth_init(have.freq, have.channels); + + /* Stand up the sound-shader pipeline and compile the resolved + * sound source. The Tunnelwisp track is the boot default: render it + * and hand the decoded PCM to the audio source immediately, instead + * of waiting for the first F2. On the + * headless Node harness glReadPixels is a no-op, so sound_shader_render + * reports 0 audible frames and audio_set_sound_pcm falls back to the + * chip synth — real GL (the browser) plays the rendered track on boot. */ + sound_shader_init(); + g_sound_text = load_initial_sound_source(); + if (sound_shader_recompile(g_sound_text) == 0) { + int boot_frames = sound_shader_render(g_audio_rate); + const int16_t *boot_pcm = sound_shader_pcm(&boot_frames); + audio_set_sound_pcm(boot_pcm, boot_frames); + fprintf(stdout, "sdl2: boot sound render frames=%d\n", boot_frames); + } else { + /* Real compile failure of the boot sound shader: the synth stays + * the audio source. Log it so a silent fall-through to the chip + * synth is diagnosable rather than mistaken for "no sound shader". */ + fprintf(stdout, "sdl2: boot sound recompile FAILED — synth fallback: %s\n", + sound_shader_last_error()); + } + + SDL_PauseAudioDevice(audio, 0); + + glEnable(GL_SCISSOR_TEST); + + Uint32 start = SDL_GetTicks(); + Uint32 prev_ticks = start; + Uint32 last_edit_ts = 0; /* 0 = no pending edit */ + int running = 1; + int frames = 0; + int spectrum_logged = 0; + float mouse_x_norm = 0.0f; + float mouse_y_norm = 0.0f; + int last_mouse_x = 0; /* window-pixel x, for wheel pane hit-test */ + int dragging = 0; /* left button held in the editor pane → drag-select */ + + while (running) { + SDL_Event ev; + SDL_PumpEvents(); + while (SDL_PollEvent(&ev)) { + if (ev.type == SDL_QUIT) { + running = 0; + continue; + } + if (ev.type == SDL_MOUSEMOTION) { + int gw, gh; + SDL_GL_GetDrawableSize(win, &gw, &gh); + int editor_w = gw * EDITOR_NUMERATOR / EDITOR_DENOMINATOR; + int render_w = gw - editor_w; + last_mouse_x = ev.motion.x; + if (render_w > 0 && gh > 0) { + float mx = (float)(ev.motion.x - editor_w) / (float) render_w; + float my = (float)(gh - ev.motion.y) / (float) gh; + if (mx < 0.0f) mx = 0.0f; else if (mx > 1.0f) mx = 1.0f; + if (my < 0.0f) my = 0.0f; else if (my > 1.0f) my = 1.0f; + mouse_x_norm = mx; + mouse_y_norm = my; + } + /* Drag-select: extend the selection to the pointer while + * the left button is held over the editor pane. */ + if (dragging && (ev.motion.state & SDL_BUTTON_LMASK)) { + editor_pointer_extend_select(ev.motion.x, ev.motion.y, + 0, 0, editor_w, gh); + } + continue; + } + if (ev.type == SDL_MOUSEWHEEL) { + /* Scroll the editor only when the pointer is over the + * editor pane (left). SDL gives wheel.y > 0 for scroll-up + * (away from the user) → move the viewport toward the top + * of the file (negative line delta). FLIPPED inverts it. */ + int gw, gh; + SDL_GL_GetDrawableSize(win, &gw, &gh); + int editor_w = gw * EDITOR_NUMERATOR / EDITOR_DENOMINATOR; + if (last_mouse_x < editor_w) { + int dir = (ev.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + ? -1 : 1; + int notches = ev.wheel.y * dir; + editor_scroll(-notches * MOUSE_WHEEL_SCROLL_LINES); + fprintf(stdout, "sdl2: editor scroll wheel=%d\n", notches); + } + continue; + } + if (ev.type == SDL_MOUSEBUTTONDOWN + && ev.button.button == SDL_BUTTON_LEFT) { + int gw, gh; + SDL_GL_GetDrawableSize(win, &gw, &gh); + int editor_w = gw * EDITOR_NUMERATOR / EDITOR_DENOMINATOR; + if (ev.button.x < editor_w) { + editor_pointer_set_cursor(ev.button.x, ev.button.y, + 0, 0, editor_w, gh); + dragging = 1; /* begin a potential drag-selection */ + } + continue; + } + if (ev.type == SDL_MOUSEBUTTONUP + && ev.button.button == SDL_BUTTON_LEFT) { + dragging = 0; + continue; + } + if (ev.type != SDL_KEYDOWN) continue; + + SDL_Keycode sym = ev.key.keysym.sym; + Uint16 mod = ev.key.keysym.mod; + int ctrl = (mod & KMOD_CTRL) != 0; + int shift = (mod & KMOD_SHIFT) != 0; + + /* Preset overlay is modal: while open it swallows keys (arrows + * choose, Enter loads, Esc closes) and never reaches the editor + * or the ESC-quit path. */ + if (g_preset_overlay) { + if (sym == SDLK_UP) { + g_preset_sel = (g_preset_sel - 1 + g_preset_count) + % g_preset_count; + } else if (sym == SDLK_DOWN) { + g_preset_sel = (g_preset_sel + 1) % g_preset_count; + } else if (sym == SDLK_RETURN || sym == SDLK_KP_ENTER) { + preset_load(g_preset_sel, &last_edit_ts); + g_preset_overlay = 0; + } else if (sym == SDLK_ESCAPE) { + g_preset_overlay = 0; + } + continue; + } + + /* Window/playground control keys first — these never feed + * into the editor. */ + if (sym == SDLK_ESCAPE) { + running = 0; + continue; + } + if (sym == SDLK_F5) { + reload_user_shader_from_file("F5"); + continue; + } + /* F1 / F2 switch the editor between the image and sound + * shaders; Ctrl+1 / Ctrl+2 are equivalents for keyboards where + * the function row is hijacked by the OS (e.g. a MacBook's + * F1/F2 are brightness keys without Fn). Entering sound mode + * activates the sound shader as the audio source (switch_mode). */ + if (sym == SDLK_F1 || (ctrl && sym == SDLK_1)) { + switch_mode(MODE_IMAGE); + last_edit_ts = 0; + continue; + } + if (sym == SDLK_F2 || (ctrl && sym == SDLK_2)) { + switch_mode(MODE_SOUND); + last_edit_ts = 0; + continue; + } + if (ctrl && sym == SDLK_s) { + if (g_mode == MODE_IMAGE) { + if (save_editor_to(USER_CURRENT_PATH, + "/home/shaders/image")) { + reload_user_shader_from_file("Ctrl+S"); + } + } else { + if (save_editor_to(USER_SOUND_PATH, + "/home/shaders/sound")) { + recompile_sound_from_editor("Ctrl+S"); + } + } + continue; + } + if (ctrl && sym == SDLK_m) { + int muted = !audio_muted(); + SDL_LockAudioDevice(g_audio_dev); + audio_set_muted(muted); + SDL_UnlockAudioDevice(g_audio_dev); + fprintf(stdout, "sdl2: audio %s\n", + muted ? "muted" : "unmuted"); + continue; + } + /* Preset browser: Ctrl+L cycles to the next preset for the + * active mode; Ctrl+Shift+L opens the chooser overlay. */ + if (ctrl && sym == SDLK_l) { + if (shift) preset_overlay_open(); + else preset_cycle(&last_edit_ts); + continue; + } + /* Clipboard + undo/redo + select-all. */ + if (ctrl && sym == SDLK_c) { clip_copy(); continue; } + if (ctrl && sym == SDLK_x) { clip_cut(&last_edit_ts); continue; } + if (ctrl && sym == SDLK_v) { clip_paste(&last_edit_ts); continue; } + if (ctrl && sym == SDLK_a) { editor_select_all(); continue; } + if (ctrl && sym == SDLK_z) { + editor_undo(); + last_edit_ts = SDL_GetTicks(); /* re-sync the preview */ + continue; + } + if (ctrl && sym == SDLK_y) { + editor_redo(); + last_edit_ts = SDL_GetTicks(); + continue; + } + + /* Shift + a navigation key extends the selection; the same key + * without Shift drops it. Applied before the move runs. */ + switch (sym) { + case SDLK_LEFT: case SDLK_RIGHT: case SDLK_UP: case SDLK_DOWN: + case SDLK_HOME: case SDLK_END: + case SDLK_PAGEUP: case SDLK_PAGEDOWN: + if (shift) editor_selection_begin(); + else editor_selection_clear(); + break; + default: break; + } + + /* Editor navigation. */ + switch (sym) { + case SDLK_LEFT: editor_move_left(); continue; + case SDLK_RIGHT: editor_move_right(); continue; + case SDLK_UP: editor_move_up(); continue; + case SDLK_DOWN: editor_move_down(); continue; + case SDLK_HOME: editor_move_home(); continue; + case SDLK_END: editor_move_end(); continue; + case SDLK_PAGEUP: editor_move_page_up(20); continue; + case SDLK_PAGEDOWN: editor_move_page_down(20); continue; + case SDLK_BACKSPACE: editor_delete_back(); + last_edit_ts = SDL_GetTicks(); + continue; + case SDLK_DELETE: editor_delete_forward(); + last_edit_ts = SDL_GetTicks(); + continue; + case SDLK_RETURN: + case SDLK_KP_ENTER: + editor_insert_newline(); + last_edit_ts = SDL_GetTicks(); + continue; + case SDLK_TAB: + editor_insert_tab(); + last_edit_ts = SDL_GetTicks(); + continue; + default: break; + } + + /* Ignore other Ctrl/Alt chords for now. */ + if (mod & (KMOD_CTRL | KMOD_ALT | KMOD_GUI)) continue; + + char ch = sym_to_ascii(sym, mod); + if (ch != 0) { + editor_insert_char(ch); + last_edit_ts = SDL_GetTicks(); + } + } + + SDL_GL_GetDrawableSize(win, &gl_w, &gl_h); + renderer_set_screen_size(gl_w, gl_h); + int editor_w = gl_w * EDITOR_NUMERATOR / EDITOR_DENOMINATOR; + int render_w = gl_w - editor_w; + + Uint32 now = SDL_GetTicks(); + float t = (float)(now - start) / 1000.0f; + float dt = (float)(now - prev_ticks) / 1000.0f; + prev_ticks = now; + + /* Debounced auto-recompile — fires once 250 ms after the last + * keystroke. last_edit_ts == 0 means "nothing pending". */ + if (last_edit_ts != 0 + && (now - last_edit_ts) >= AUTO_RECOMPILE_DEBOUNCE_MS) { + if (g_mode == MODE_IMAGE) recompile_from_editor(); + else recompile_sound_from_editor("auto"); + last_edit_ts = 0; + } + + /* Whole window: background clear (avoids flicker outside the + * scissor regions while we swap viewports). */ + glViewport(0, 0, gl_w, gl_h); + glScissor (0, 0, gl_w, gl_h); + glClearColor(0.10f, 0.10f, 0.13f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + /* Update the iAudio FFT texture from the latest synth output. + * Computed every frame regardless of whether the audio callback + * pulled samples this tick — on a silent ring the bins are simply + * zero, so the upload path (and the iAudio binding) still runs + * under the headless Node harness. */ + unsigned char spectrum[AUDIO_SPECTRUM_BINS]; + audio_compute_spectrum(spectrum); + renderer_set_audio_spectrum(spectrum, AUDIO_SPECTRUM_BINS); + if (!spectrum_logged) { + printf("sdl2: audio spectrum uploaded bins=%d\n", + AUDIO_SPECTRUM_BINS); + spectrum_logged = 1; + } + + /* Right pane: user shader. */ + renderer_draw_user_shader(editor_w, 0, render_w, gl_h, + t, dt, mouse_x_norm, mouse_y_norm, + frames); + renderer_draw_error_strip(editor_w, 0, render_w, gl_h); + + /* Render-pane chrome: a persistent title in the corner, plus a + * boot splash that fades out. Scissor to the render pane so the + * text isn't clipped by the editor pass that follows. */ + { + glViewport(0, 0, gl_w, gl_h); + glScissor (editor_w, 0, render_w, gl_h); + int lh = renderer_text_line_height(); + int adv = renderer_text_advance(); + const char *title = "SDL2 GLSL Playground"; + renderer_draw_textz(editor_w + 10, gl_h - lh - 8, title, + 0.72f, 0.76f, 0.86f); + + unsigned int age = now - start; + if (age < SPLASH_MS && adv > 0) { + /* Backdrop holds, then fades over the final ~40%. */ + float frac = (float) age / (float) SPLASH_MS; + float a = frac < 0.6f ? 0.6f : 0.6f * (1.0f - (frac - 0.6f) / 0.4f); + if (a < 0.0f) a = 0.0f; + renderer_fill_rect(editor_w, 0, render_w, gl_h, + 0.05f, 0.05f, 0.08f, a); + const char *hint = "F1 image | F2 sound | Ctrl+L presets | ESC quit"; + int tx = editor_w + (render_w - (int) strlen(title) * adv) / 2; + int hx = editor_w + (render_w - (int) strlen(hint) * adv) / 2; + if (tx < editor_w) tx = editor_w; + if (hx < editor_w) hx = editor_w; + int cy = gl_h / 2; + renderer_draw_textz(tx, cy - lh, title, 0.93f, 0.93f, 0.78f); + renderer_draw_textz(hx, cy + 6, hint, 0.78f, 0.81f, 0.85f); + } + } + + /* Left pane: editor. Scissor + viewport reset so the + * coordinate space matches the renderer's window-pixel model. */ + glViewport(0, 0, gl_w, gl_h); + glScissor (0, 0, editor_w, gl_h); + editor_render(0, 0, editor_w, gl_h, now, /*has_focus=*/1); + + /* Mode badge in the top-right of the editor pane: the shader being + * edited + the key to switch to the other (Ctrl+1/Ctrl+2, which + * also work as F1/F2). Drawn over the editor after its own pass so + * it sits on top of the gutter/text. */ + { + const char *badge = (g_mode == MODE_IMAGE) + ? "image [Ctrl+2: sound]" + : "sound [Ctrl+1: image]"; + int adv = renderer_text_advance(); + int badge_px = (int) strlen(badge) * adv; + int bx = editor_w - badge_px - 10; + if (bx < 0) bx = 0; + renderer_draw_textz(bx, 6, badge, 0.75f, 0.80f, 0.55f); + } + + /* Preset chooser overlay (Ctrl+Shift+L) — drawn last, over the + * editor pane, still under the editor-pane scissor. */ + preset_overlay_render(editor_w, gl_h); + + SDL_GL_SwapWindow(win); + frames++; + } + + /* Capture elapsed BEFORE SDL_Quit — SDL_QuitSubSystem(TIMER) tears + * down the start_ts cache, so a post-Quit SDL_GetTicks() would + * re-init from a fresh base and the subtraction would wrap. */ + Uint32 elapsed = SDL_GetTicks() - start; + + SDL_PauseAudioDevice(audio, 1); + SDL_CloseAudioDevice(audio); + sound_shader_shutdown(); + renderer_shutdown(); + editor_shutdown(); + free(g_image_text); + free(g_sound_text); + free(g_clip); + SDL_GL_DeleteContext(glctx); + SDL_DestroyWindow(win); + SDL_Quit(); + + printf("sdl2: OK frames=%d elapsed=%u ms exit=esc\n", + frames, (unsigned) elapsed); + return 0; +} diff --git a/programs/sdl2/presets/image/audio_bars.frag b/programs/sdl2/presets/image/audio_bars.frag new file mode 100644 index 0000000000..214218c2ab --- /dev/null +++ b/programs/sdl2/presets/image/audio_bars.frag @@ -0,0 +1,39 @@ +// Preset: a 128-bar FFT visualizer driven by the chip synth. +// The host wraps this body with the GLSL ES 1.0 Shadertoy template from +// programs/sdl2/renderer.c, which declares the uniforms used below +// (iResolution, iTime, iAudio). iAudio is a 128x1 GL_LUMINANCE texture +// holding the live magnitude spectrum (see programs/sdl2/audio.c): +// texture2D(iAudio, vec2(x, 0.0)).r is the level at normalized +// frequency x in [0, 1]. +// +// Load this in the playground (or let it boot as the right-pane preset) +// and the bars must visibly react to the synth — bass on the left, +// highs on the right. A flat bar row means the audio ring is silent +// (e.g. muted via Ctrl+M, or a headless GL context with no audio pull). + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + + // Sample the spectrum at this column. A small smoothing offset reads + // two neighboring frequencies so the LINEAR-filtered texture gives + // continuous bar heights rather than 128 hard steps. + float level = texture2D(iAudio, vec2(uv.x, 0.0)).r; + + // Bar: lit below the level line, dark above. A bright cap near the + // top of each bar reads as a peak meter. + float bar = step(uv.y, level); + float cap = smoothstep(level - 0.02, level, uv.y) + * step(uv.y, level + 0.02); + + // Hue sweeps blue->magenta across the spectrum; brightness rises with + // the bar level so loud bands glow. + vec3 lowCol = vec3(0.10, 0.45, 1.00); + vec3 highCol = vec3(1.00, 0.25, 0.65); + vec3 barCol = mix(lowCol, highCol, uv.x) * (0.35 + 0.65 * level); + + vec3 bg = vec3(0.03, 0.03, 0.06); + vec3 col = mix(bg, barCol, bar); + col += cap * vec3(1.0); // white peak cap + + fragColor = vec4(col, 1.0); +} diff --git a/programs/sdl2/presets/image/plasma.frag b/programs/sdl2/presets/image/plasma.frag new file mode 100644 index 0000000000..fe3faaab3f --- /dev/null +++ b/programs/sdl2/presets/image/plasma.frag @@ -0,0 +1,18 @@ +// Default Shadertoy-style image shader: a plasma over the right pane. +// The host wraps this body with the GLSL ES 1.0 Shadertoy template +// from programs/sdl2/main.c (FRAG_PREFIX / FRAG_SUFFIX), so this file +// only contains the `mainImage(out vec4 fragColor, in vec2 fragCoord)` +// entry point that the wrapper calls. +// +// Kept in sync with the built-in PLASMA_SRC fallback in +// programs/sdl2/main.c — if you edit one, edit both. The duplication +// is so the binary still renders when no VFS shader is staged (e.g. +// host/test/sdl2.test.ts under NodeKernelHost). + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 uv = fragCoord / iResolution.xy; + float r = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831); + float g = 0.5 + 0.5 * sin(iTime * 1.3 + uv.y * 6.2831); + float b = 0.5 + 0.5 * sin(iTime * 0.7 + (uv.x + uv.y) * 6.2831); + fragColor = vec4(r, g, b, 1.0); +} diff --git a/programs/sdl2/presets/image/tunnelwisp.frag b/programs/sdl2/presets/image/tunnelwisp.frag new file mode 100644 index 0000000000..a1a421b847 --- /dev/null +++ b/programs/sdl2/presets/image/tunnelwisp.frag @@ -0,0 +1,75 @@ +// CC0: Trailing the Twinkling Tunnelwisp +// A bit of Saturday coding (also Norway's Constitution Day). +// Some artifacts remain, but it's good enough for my standards. +// +// Music by Pestis created for Cassini's Cosmic Conclusion +// https://demozoo.org/productions/367582/ +// +// Original (CC0) Shadertoy: https://www.shadertoy.com/view/WfcGWj +// +// Ported verbatim to GLSL ES 1.00 (our playground is WebGL1, the original +// is WebGL2/ES 3.00). The ONLY changes are dialect-level and do not alter +// the image: locals are zero-initialized, the golfed comma-operator +// for-loop is expanded into an equivalent explicit loop (identical math, +// identical order), and tanh() — absent in ES 1.00 — is provided by the +// tanh4() helper below. Everything else is the original code. + +// Distance field for gyroid, adapted from Paul Karlik's "Gyroid Travel" +// in KodeLife. Tweaked slightly for this effect. +float g(vec4 p, float s) { + // p.x=-abs(p.x); // Makes it nicer (IMO) but costs bytes! + return abs(dot(sin(p *= s), cos(p.zxwy)) - 1.) / s; +} + +// tanh is a GLSL ES 3.00 builtin; emulate it for ES 1.00. Input is clamped +// so exp() can't overflow to Inf (which would yield NaN); tanh saturates +// well before |x|=15 so the clamp is invisible in the output. +vec4 tanh4(vec4 x) { + x = clamp(x, -15., 15.); + vec4 e = exp(2. * x); + return (e - 1.) / (e + 1.); +} + +void mainImage(out vec4 O, vec2 C) { + float i = 0., d = 0., z = 0., s = 0., T = iTime; + vec4 o = vec4(0.), q = vec4(0.), p = vec4(0.), U = vec4(2, 1, 0, 3); + vec2 r = iResolution.xy; + + // Step through the scene, up to 78 steps. (Original used a single golfed + // for-loop with all of the following packed into its comma-separated + // increment clause; expanded here for ES 1.00 — same computation.) + for (int n = 0; n < 78; n++) { + // Accumulate glow — brighter and sharper if not mirrored (above axis). + o += (s > 0. ? 1. : .1) * p.w * p / max(s > 0. ? d : d * d * d, 5E-4); + + // Advance along the ray by current distance estimate (+ epsilon). + // The epsilon makes the cave walls somewhat translucent. + z += d + 5E-4; + // Compute ray direction, scaled by distance. + q = vec4(normalize(vec3(C - .5 * r, r.y)) * z, .2); + // Traverse through the cave. + q.z += T / 3E1; + // Save sign before mirroring. + s = q.y + .1; + // Creates the water reflection effect. + q.y = abs(s); + p = q; + p.y -= .11; + // Twist cave walls based on depth — the rotation matrix + // mat2(cos(a), sin(a), -sin(a), cos(a)) is approximated with + // mat2(cos(a + vec4(0,11,33,0))). + p.xy *= mat2(cos(11. * U.zywz - 2. * p.z)); + p.y -= .2; + // Combine gyroid fields at two scales for more detail. + d = abs(g(p, 8.) - g(p, 24.)) / 4.; + // Base glow color varies with distance from center. + p = 1. + cos(.7 * U + 5. * q.z); + } + + // Add pulsing glow for the "tunnelwisp". + o += (1.4 + sin(T) * sin(1.7 * T) * sin(2.3 * T)) + * 1E3 * U / length(q.xy); + + // Apply tanh for soft tone mapping. + O = tanh4(o / 1E5); +} diff --git a/programs/sdl2/presets/sound/chord.frag b/programs/sdl2/presets/sound/chord.frag new file mode 100644 index 0000000000..6b62fd569b --- /dev/null +++ b/programs/sdl2/presets/sound/chord.frag @@ -0,0 +1,28 @@ +// Sound preset: a major-triad arpeggio. +// Steps through the notes of a C-major chord (C–E–G–C) four times a +// second, each note a plucked sine with a short decay envelope. Shows +// distinct FFT peaks marching up the spectrum in the iAudio visualizer. +// See programs/sdl2/sound_shader.c for the mainSound contract. +// +// Note: GLSL ES 1.00 fragment shaders don't allow dynamically-indexed +// local arrays, so the ratio is picked with an if-chain rather than +// array lookup. + +float ratio_for(float step) { + // Equal-temperament ratios for C, E, G, and the octave C. + if (step < 0.5) return 1.0; // C + if (step < 1.5) return 1.2599; // major third (E) + if (step < 2.5) return 1.4983; // perfect fifth (G) + return 2.0; // octave C +} + +vec2 mainSound(in float time) { + float base = 261.63; // C4 + float step = floor(fract(time * 0.25) * 4.0); // 0..3 over 4 s + float freq = base * ratio_for(step); + + float note = fract(time * 4.0); // 0..1 within each 1/4 s step + float env = exp(-5.0 * note); + float v = 0.4 * env * sin(6.2831853 * freq * time); + return vec2(v, v); +} diff --git a/programs/sdl2/presets/sound/fm_bell.frag b/programs/sdl2/presets/sound/fm_bell.frag new file mode 100644 index 0000000000..a855078cbd --- /dev/null +++ b/programs/sdl2/presets/sound/fm_bell.frag @@ -0,0 +1,18 @@ +// Sound preset: an FM bell that retriggers once per second. +// Two-operator frequency modulation — a sine carrier whose phase is +// modulated by a higher-frequency sine. The modulation index and the +// amplitude both decay across each one-second note (via fract(time)), +// giving the bright-attack / mellow-tail timbre of a struck bell. +// See programs/sdl2/sound_shader.c for the mainSound contract. + +vec2 mainSound(in float time) { + float note = fract(time); // 0..1 within each second + float carrier = 440.0; + float modulator = 880.0; + float index = 5.0 * exp(-3.0 * note); // FM brightness decays + float env = exp(-3.5 * note); // amplitude decays + float phase = 6.2831853 * carrier * time + + index * sin(6.2831853 * modulator * time); + float v = 0.4 * env * sin(phase); + return vec2(v, v); +} diff --git a/programs/sdl2/presets/sound/noise_sweep.frag b/programs/sdl2/presets/sound/noise_sweep.frag new file mode 100644 index 0000000000..37afdbd492 --- /dev/null +++ b/programs/sdl2/presets/sound/noise_sweep.frag @@ -0,0 +1,16 @@ +// Sound preset: white noise under a slow amplitude sweep. +// A cheap hash turns the per-sample time index into pseudo-random noise; +// a slow LFO sweeps the level so the FFT visualizer shows a broadband +// wash rising and falling. Good for confirming iAudio reacts across the +// whole spectrum (vs the single bins a sine lights up). +// See programs/sdl2/sound_shader.c for the mainSound contract. + +float hash(float n) { return fract(sin(n) * 43758.5453123); } + +vec2 mainSound(in float time) { + // ~quantize to a noise rate so adjacent samples decorrelate. + float n = hash(floor(time * 12000.0)) * 2.0 - 1.0; + float sweep = 0.5 + 0.5 * sin(6.2831853 * 0.2 * time); + float v = 0.3 * n * sweep; + return vec2(v, v); +} diff --git a/programs/sdl2/presets/sound/sine.frag b/programs/sdl2/presets/sound/sine.frag new file mode 100644 index 0000000000..dc1082bac8 --- /dev/null +++ b/programs/sdl2/presets/sound/sine.frag @@ -0,0 +1,14 @@ +// Sound preset: a pure 220 Hz sine tone. +// A "sound shader" supplies `vec2 mainSound(in float time)` returning the +// (left, right) amplitude in [-1, 1] at `time` seconds. The host wraps +// this body with the GLSL ES 1.0 sound template from +// programs/sdl2/sound_shader.c, renders it to an FBO, reads it back, and +// plays the decoded PCM through ALSA. The image shader's iAudio uniform +// then visualizes this output's FFT — edit the sound, watch the picture. +// +// Kept in sync with BUILTIN_SOUND_SRC in programs/sdl2/main.c. + +vec2 mainSound(in float time) { + float v = 0.3 * sin(6.2831853 * 220.0 * time); + return vec2(v, v); +} diff --git a/programs/sdl2/presets/sound/tunnelwisp.frag b/programs/sdl2/presets/sound/tunnelwisp.frag new file mode 100644 index 0000000000..eec6073373 --- /dev/null +++ b/programs/sdl2/presets/sound/tunnelwisp.frag @@ -0,0 +1,57 @@ +// CC0: Trailing the Twinkling Tunnelwisp +// A bit of Saturday coding (also Norway's Constitution Day). +// Some artifacts remain, but it's good enough for my standards. +// +// Music by Pestis created for Cassini's Cosmic Conclusion +// https://demozoo.org/productions/367582/ +// +// Original (CC0) Shadertoy: https://www.shadertoy.com/view/WfcGWj +// +// Ported verbatim to GLSL ES 1.00. Dialect-level changes only — the +// synthesis math is unchanged: +// - Shadertoy's signature is mainSound(int samp, float time); the int +// sample index is unused here, and our host template calls +// mainSound(time), so the parameter is dropped. +// - Locals are zero-initialized (ES 1.00 has no implicit zero-init). +// - The golfed `for(c=3.;c<4.1;n+=c*=1.02)` inner loop is expanded into +// an explicit constant-bound loop producing the identical sequence. +// +// NOTE: this composition evolves over minutes, but our sound-shader buffer +// is fixed (~2.73 s) and loops, so only the opening plays, repeating. The +// image is continuously iTime-animated and is unaffected. + +vec2 mainSound(in float t) { + // Accumulator for the final sound output. + vec2 r = vec2(0.); + + // a controls the overall progression; b is its fractional part. + for (float i = 1.; i < 4.; i++) + for (float j = 1.; j < 5.; j++) { + float a = t * j / 32. + i / 3., b = fract(a); + + // n is the base frequency vector (slight stereo offset); m accumulates + // harmonics. + vec2 m = vec2(0.), n = vec2(t, t + 3.) + t / j; + + // Sum sine harmonics with a gradually increasing frequency multiplier + // and 1/c amplitude falloff. (Original: for(c=3.;c<4.1;n+=c*=1.02).) + float c = 3.; + for (int k = 0; k < 16; k++) { + if (c >= 4.1) break; + m += sin(n * c) / c; + c *= 1.02; + n += c; + } + + // Only contribute sound during the first part of the composition. + if (a < 9.) + r += sin( + m + + 4. * sin(t / j / 47.) + * sin(exp2(mod(a - b, 3.) / 6. + 8.5) * t * j * i + i + j) + ) + * exp2(-b * 12. - 1. / b + 6. - (i + j) / 3.); + } + + return r; +} diff --git a/programs/sdl2/renderer.c b/programs/sdl2/renderer.c new file mode 100644 index 0000000000..fd4bf54772 --- /dev/null +++ b/programs/sdl2/renderer.c @@ -0,0 +1,636 @@ +/* + * programs/sdl2/renderer.c — GLES2 drawing facade for the SDL2 GLSL + * playground. See renderer.h for the public surface. This translation + * unit is the *only* place where stb_truetype's implementation is + * realized — the rest of the playground gets it as a pure header. + * + * Three programs live here: + * prog_user - the live image-shader (PLASMA_SRC or user-supplied). + * prog_strip - the translucent red error strip. + * prog_text - a textured-quad program reading the Inconsolata atlas. + * + * The atlas is baked once at init via stb_truetype's `BakeFontBitmap` + * over the printable ASCII range (32..126). One static GL_LUMINANCE + * 2D texture; one CPU-side `stbtt_bakedchar` table for per-glyph UV + + * size + xadvance lookup. We bake at the height the editor renders at + * (TEXT_PIXEL_HEIGHT). + * + * Drawing text issues one `glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)` per + * glyph — sub-optimal but trivial to verify. Per-frame the editor will + * draw on the order of ~1500 glyphs; that's well within the headroom + * even with the GLES2 channel-syscall draw overhead. + */ + +#include "renderer.h" + +#include +#include +#include +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#include "third_party/stb_truetype.h" +#include "third_party/inconsolata_ttf.h" + +/* ----- font / atlas configuration -------------------------------- */ + +/* The atlas uploads as a single GL_LUMINANCE texture in one + * OP_TEX_IMAGE_2D TLV record. libglesv2_stub.c packs the payload + * length into a u16, so the largest single-call upload is ≈65499 + * bytes. ATLAS_W*ATLAS_H = 61440 stays under the cap and holds 95 + * glyphs at 28 px height. + * + * We use stbtt_Pack* (not the older BakeFontBitmap) so glyphs carry + * grayscale-coverage antialiasing and sample back with bilinear + * filtering at display time. At 28 px the glyphs hold enough detail + * that 1× oversampling already looks smooth — and 2× horizontal + * oversampling at this height would push the atlas past the + * single-upload payload cap (256×360 ≈ 92 KB > 65499). The render + * target is the full 1920×1080 framebuffer, which the kandelo Modeset + * pane down-samples to its CSS width; a larger bake (28 px vs the + * prior 20 px) therefore reads as a crisp modern editor font instead + * of the thin, aliased "MS-DOS" look the 20 px atlas produced once + * down-sampled. */ +#define TEXT_PIXEL_HEIGHT 28 /* glyph render height in pixels */ +#define TEXT_LINE_STRIDE 34 /* baseline-to-baseline px */ +#define TEXT_OVERSAMPLE_X 1 +#define TEXT_OVERSAMPLE_Y 1 +#define ATLAS_W 256 +#define ATLAS_H 240 +#define ATLAS_FIRST 32 +#define ATLAS_COUNT 95 /* 32..126 inclusive */ + +static stbtt_packedchar g_atlas_chars[ATLAS_COUNT]; +static GLuint g_atlas_tex = 0; +static int g_glyph_advance = 0; +static int g_glyph_ascent = 0; + +/* ----- screen state ---------------------------------------------- */ +static int g_screen_w = 1280; +static int g_screen_h = 720; + +/* ----- error state (shared with main + editor via accessors) ----- */ +static char g_last_error[4096]; +static int g_error_visible = 0; + +void renderer_set_error_visible(int v) { g_error_visible = v ? 1 : 0; } + +/* ----- shared compile helper ------------------------------------- */ + +/* `real_failure_out` is OR'd in: we leave it untouched on success and + * set it to 1 only when the compile actually produced an info-log + * entry. Empty-log failures are the headless-GL stub described in + * handoff-2 §C — they get a WARN but are treated as a no-op so the + * Node test path can still run the binary end-to-end. */ +static GLuint compile_shader(GLenum type, const char *src, const char *tag, + int *real_failure_out) { + GLuint s = glCreateShader(type); + glShaderSource(s, 1, &src, NULL); + glCompileShader(s); + GLint status = 0; + glGetShaderiv(s, GL_COMPILE_STATUS, &status); + if (!status) { + GLint loglen = 0; + glGetShaderiv(s, GL_INFO_LOG_LENGTH, &loglen); + if (loglen > 0 && loglen < (GLint) sizeof g_last_error) { + glGetShaderInfoLog(s, loglen, NULL, g_last_error); + if (real_failure_out) *real_failure_out = 1; + } else { + snprintf(g_last_error, sizeof g_last_error, + "(empty info log; headless GL?)"); + } + fprintf(stderr, "WARN: %s compile: %s\n", tag, g_last_error); + } + return s; +} + +static GLuint link_program(GLuint vs, GLuint fs, const char *tag, + int *real_failure_out) { + GLuint prog = glCreateProgram(); + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + glDeleteShader(vs); + glDeleteShader(fs); + GLint status = 0; + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (!status) { + GLint loglen = 0; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &loglen); + if (loglen > 0 && loglen < (GLint) sizeof g_last_error) { + glGetProgramInfoLog(prog, loglen, NULL, g_last_error); + if (real_failure_out) *real_failure_out = 1; + } else { + snprintf(g_last_error, sizeof g_last_error, + "(empty link log; headless GL?)"); + } + fprintf(stderr, "WARN: %s link: %s\n", tag, g_last_error); + } + return prog; +} + +/* ===== user image-shader program ================================ */ + +static const char *USER_VS_SRC = + "attribute vec2 a_pos;\n" + "void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }\n"; + +/* GLSL ES 1.0 wrapper around the user's `mainImage`. `iAudio` is a + * 128×1 GL_LUMINANCE texture holding the live FFT magnitude spectrum + * (see audio.c). Shaders that don't sample it link fine — the uniform + * just drops out and renderer_draw_user_shader's location check skips + * the bind. */ +static const char *USER_FRAG_PREFIX = + "#version 100\n" + "precision highp float;\n" + "uniform vec2 iResolution;\n" + "uniform vec2 iViewportOrigin;\n" + "uniform float iTime;\n" + "uniform float iTimeDelta;\n" + "uniform vec2 iMouse;\n" + "uniform int iFrame;\n" + "uniform sampler2D iAudio;\n"; + +static const char *USER_FRAG_SUFFIX = + "\nvoid main() {\n" + " vec4 c;\n" + " mainImage(c, gl_FragCoord.xy - iViewportOrigin);\n" + " gl_FragColor = c;\n" + "}\n"; + +/* Parse the GLSL info log for the 1-indexed source line of the first + * error — ANGLE/Mesa format "ERROR: 0:: ..." — and map it back to + * the user's editor coordinates by subtracting the template prefix's line + * count (the wrapper lines the user never sees). Returns the 1-indexed + * user line, or -1 if the log has no parseable line or the error sits in + * the template prefix itself. */ +static int parse_error_line(const char *log, const char *prefix) { + const char *p = strstr(log, "0:"); + if (!p) return -1; + p += 2; + if (*p < '0' || *p > '9') return -1; + int combined = 0; + while (*p >= '0' && *p <= '9') combined = combined * 10 + (*p++ - '0'); + int prefix_lines = 0; + for (const char *q = prefix; *q; q++) if (*q == '\n') prefix_lines++; + int user_line = combined - prefix_lines; + return user_line >= 1 ? user_line : -1; +} + +int renderer_last_error_line(void) { + return parse_error_line(g_last_error, USER_FRAG_PREFIX); +} + +static GLuint g_user_prog = 0; +static GLuint g_user_vbo = 0; +static GLint g_user_a_pos = -1; +static GLint g_user_u_iRes = -1; +static GLint g_user_u_iVpOrigin = -1; +static GLint g_user_u_iTime = -1; +static GLint g_user_u_iTimeDt = -1; +static GLint g_user_u_iMouse = -1; +static GLint g_user_u_iFrame = -1; +static GLint g_user_u_iAudio = -1; + +/* The FFT spectrum texture sampled by `iAudio`. Created lazily on the + * first renderer_set_audio_spectrum so headless test runs (no audio + * uploads) never allocate it. */ +static GLuint g_audio_tex = 0; + +static void user_rebind_uniforms(void) { + g_user_a_pos = glGetAttribLocation (g_user_prog, "a_pos"); + g_user_u_iRes = glGetUniformLocation(g_user_prog, "iResolution"); + g_user_u_iVpOrigin = glGetUniformLocation(g_user_prog, "iViewportOrigin"); + g_user_u_iTime = glGetUniformLocation(g_user_prog, "iTime"); + g_user_u_iTimeDt = glGetUniformLocation(g_user_prog, "iTimeDelta"); + g_user_u_iMouse = glGetUniformLocation(g_user_prog, "iMouse"); + g_user_u_iFrame = glGetUniformLocation(g_user_prog, "iFrame"); + g_user_u_iAudio = glGetUniformLocation(g_user_prog, "iAudio"); +} + +int renderer_recompile_user_shader(const char *user_src) { + size_t pre = strlen(USER_FRAG_PREFIX); + size_t usr = strlen(user_src); + size_t suf = strlen(USER_FRAG_SUFFIX); + char *frag = malloc(pre + usr + suf + 1); + if (!frag) return 0; + memcpy(frag, USER_FRAG_PREFIX, pre); + memcpy(frag + pre, user_src, usr); + memcpy(frag + pre + usr, USER_FRAG_SUFFIX, suf + 1); + + int real_failure = 0; + GLuint vs = compile_shader(GL_VERTEX_SHADER, USER_VS_SRC, + "user-vertex", &real_failure); + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, frag, + "user-fragment", &real_failure); + free(frag); + GLuint new_prog = link_program(vs, fs, "user-program", &real_failure); + + if (real_failure) { + glDeleteProgram(new_prog); + return 1; + } + if (g_user_prog) glDeleteProgram(g_user_prog); + g_user_prog = new_prog; + user_rebind_uniforms(); + return 0; +} + +void renderer_set_audio_spectrum(const unsigned char *bins, int n) { + if (!bins || n <= 0) return; + if (!g_audio_tex) { + glGenTextures(1, &g_audio_tex); + glBindTexture(GL_TEXTURE_2D, g_audio_tex); + /* LINEAR so the 128 bins read as smooth bars; clamp so the edge + * texels don't wrap. */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + glBindTexture(GL_TEXTURE_2D, g_audio_tex); + } + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + /* One-row (n×1) GL_LUMINANCE texture — 128 bytes, far under the + * OP_TEX_IMAGE_2D single-call payload cap. */ + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, + n, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bins); +} + +void renderer_draw_user_shader(int vp_x, int vp_y, int vp_w, int vp_h, + float t, float dt, + float mouse_x, float mouse_y, int frame) { + if (!g_user_prog) return; + glViewport(vp_x, vp_y, vp_w, vp_h); + glScissor (vp_x, vp_y, vp_w, vp_h); + glUseProgram(g_user_prog); + glBindBuffer(GL_ARRAY_BUFFER, g_user_vbo); + if (g_user_a_pos >= 0) { + glVertexAttribPointer((GLuint) g_user_a_pos, 2, GL_FLOAT, + GL_FALSE, 0, NULL); + glEnableVertexAttribArray((GLuint) g_user_a_pos); + } + if (g_user_u_iRes >= 0) glUniform2f(g_user_u_iRes, + (float) vp_w, (float) vp_h); + if (g_user_u_iVpOrigin >= 0) glUniform2f(g_user_u_iVpOrigin, + (float) vp_x, (float) vp_y); + if (g_user_u_iTime >= 0) glUniform1f(g_user_u_iTime, t); + if (g_user_u_iTimeDt >= 0) glUniform1f(g_user_u_iTimeDt, dt); + if (g_user_u_iMouse >= 0) glUniform2f(g_user_u_iMouse, + mouse_x, mouse_y); + if (g_user_u_iFrame >= 0) glUniform1i(g_user_u_iFrame, frame); + /* Bind the FFT texture to unit 0 for the iAudio sampler. The text + * program also uses unit 0, but each draw rebinds its own texture, so + * sharing the unit is safe. */ + if (g_user_u_iAudio >= 0 && g_audio_tex) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_audio_tex); + glUniform1i(g_user_u_iAudio, 0); + } + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +/* ===== error-strip program ====================================== */ + +static const char *STRIP_VS_SRC = + "attribute vec2 a_pos;\n" + "void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }\n"; + +static const char *STRIP_FS_SRC = + "#version 100\n" + "precision mediump float;\n" + "void main() { gl_FragColor = vec4(1.0, 0.1, 0.1, 0.55); }\n"; + +static GLuint g_strip_prog = 0; +static GLuint g_strip_vbo = 0; +static GLint g_strip_a_pos = -1; + +void renderer_draw_error_strip(int vp_x, int vp_y, int vp_w, int vp_h) { + if (!g_error_visible || !g_strip_prog) return; + glViewport(vp_x, vp_y, vp_w, vp_h); + glScissor (vp_x, vp_y, vp_w, vp_h); + glUseProgram(g_strip_prog); + glBindBuffer(GL_ARRAY_BUFFER, g_strip_vbo); + if (g_strip_a_pos >= 0) { + glVertexAttribPointer((GLuint) g_strip_a_pos, 2, GL_FLOAT, + GL_FALSE, 0, NULL); + glEnableVertexAttribArray((GLuint) g_strip_a_pos); + } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDisable(GL_BLEND); +} + +/* ===== text / fill-rect program ================================= */ + +/* Both `renderer_draw_text` and `renderer_fill_rect` share one + * textured-quad program. `fill_rect` binds a 1×1 white texture so the + * fragment color is purely the per-draw uniform color. `draw_text` + * binds the atlas. Vertex data: 2D pos + 2D uv, written per-quad + * into a STREAM_DRAW VBO; we issue 4 verts via TRIANGLE_STRIP. */ + +static const char *TEXT_VS_SRC = + "attribute vec2 a_pos;\n" + "attribute vec2 a_uv;\n" + "varying vec2 v_uv;\n" + "void main() {\n" + " v_uv = a_uv;\n" + " gl_Position = vec4(a_pos, 0.0, 1.0);\n" + "}\n"; + +/* The atlas is GL_LUMINANCE, so .r carries the only meaningful + * channel. Multiply by the per-draw u_color (with .a alpha) and + * blend additively so a fully-transparent texel contributes nothing. + * The 1×1 white texture used by fill_rect makes that .r be 1.0 across + * the quad, so the color uniform drives the result. */ +static const char *TEXT_FS_SRC = + "#version 100\n" + "precision mediump float;\n" + "varying vec2 v_uv;\n" + "uniform sampler2D u_tex;\n" + "uniform vec4 u_color;\n" + "void main() {\n" + " float a = texture2D(u_tex, v_uv).r;\n" + " gl_FragColor = vec4(u_color.rgb, u_color.a * a);\n" + "}\n"; + +static GLuint g_text_prog = 0; +static GLuint g_text_vbo = 0; +static GLuint g_white_tex = 0; +static GLint g_text_a_pos = -1; +static GLint g_text_a_uv = -1; +static GLint g_text_u_tex = -1; +static GLint g_text_u_color = -1; + +/* Convert a window-pixel rectangle to NDC. Origin (0,0) is top-left + * in pixel space; (-1,-1) is bottom-left in NDC. The vbo layout is + * 4 verts × (x, y, u, v), arranged as a triangle strip. */ +static void quad_pixels_to_ndc(int x, int y, int w, int h, + float u0, float v0, float u1, float v1, + float verts[16]) { + float sx = g_screen_w > 0 ? (float) g_screen_w : 1.0f; + float sy = g_screen_h > 0 ? (float) g_screen_h : 1.0f; + float x0n = (float) x / sx * 2.0f - 1.0f; + float x1n = (float)(x + w) / sx * 2.0f - 1.0f; + float y0n = -((float) y / sy * 2.0f - 1.0f); + float y1n = -((float)(y + h) / sy * 2.0f - 1.0f); + /* TRIANGLE_STRIP order: (x0,y0)(x1,y0)(x0,y1)(x1,y1). + * y1n < y0n in NDC because we flipped. */ + verts[ 0] = x0n; verts[ 1] = y0n; verts[ 2] = u0; verts[ 3] = v0; + verts[ 4] = x1n; verts[ 5] = y0n; verts[ 6] = u1; verts[ 7] = v0; + verts[ 8] = x0n; verts[ 9] = y1n; verts[10] = u0; verts[11] = v1; + verts[12] = x1n; verts[13] = y1n; verts[14] = u1; verts[15] = v1; +} + +static void text_bind_pointers(void) { + if (g_text_a_pos >= 0) { + glVertexAttribPointer((GLuint) g_text_a_pos, 2, GL_FLOAT, GL_FALSE, + sizeof(float) * 4, (void *) 0); + glEnableVertexAttribArray((GLuint) g_text_a_pos); + } + if (g_text_a_uv >= 0) { + glVertexAttribPointer((GLuint) g_text_a_uv, 2, GL_FLOAT, GL_FALSE, + sizeof(float) * 4, + (void *)(uintptr_t) (sizeof(float) * 2)); + glEnableVertexAttribArray((GLuint) g_text_a_uv); + } +} + +void renderer_fill_rect(int x, int y, int w, int h, + float r, float g, float b, float a) { + if (!g_text_prog || w <= 0 || h <= 0) return; + glUseProgram(g_text_prog); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_white_tex); + if (g_text_u_tex >= 0) glUniform1i(g_text_u_tex, 0); + if (g_text_u_color >= 0) glUniform4f(g_text_u_color, r, g, b, a); + float verts[16]; + quad_pixels_to_ndc(x, y, w, h, 0.0f, 0.0f, 1.0f, 1.0f, verts); + glBindBuffer(GL_ARRAY_BUFFER, g_text_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STREAM_DRAW); + text_bind_pointers(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDisable(GL_BLEND); +} + +int renderer_draw_text(int x, int y, const char *s, size_t n, + float r, float g, float b) { + if (!g_text_prog || !g_atlas_tex || g_glyph_advance == 0) return 0; + glUseProgram(g_text_prog); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_atlas_tex); + if (g_text_u_tex >= 0) glUniform1i(g_text_u_tex, 0); + if (g_text_u_color >= 0) glUniform4f(g_text_u_color, r, g, b, 1.0f); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + float pen_x = (float) x; + float pen_y = (float) y + (float) g_glyph_ascent; + glBindBuffer(GL_ARRAY_BUFFER, g_text_vbo); + for (size_t i = 0; i < n; i++) { + unsigned char c = (unsigned char) s[i]; + if (c < ATLAS_FIRST || c >= ATLAS_FIRST + ATLAS_COUNT) { + pen_x += (float) g_glyph_advance; + continue; + } + stbtt_aligned_quad q; + float px = pen_x; + float py = pen_y; + stbtt_GetPackedQuad(g_atlas_chars, ATLAS_W, ATLAS_H, + c - ATLAS_FIRST, &px, &py, &q, + /*align_to_integer=*/1); + /* Monospace: force the advance to the cell width regardless of + * what the font says. Inconsolata's per-glyph xadvance is + * already constant within rounding, but pinning it here also + * keeps the cursor column math (in editor.c) exact. */ + pen_x += (float) g_glyph_advance; + if (q.x1 <= q.x0 || q.y1 <= q.y0) continue; + int gx = (int) q.x0; + int gy = (int) q.y0; + int gw = (int)(q.x1 - q.x0); + int gh = (int)(q.y1 - q.y0); + float verts[16]; + quad_pixels_to_ndc(gx, gy, gw, gh, q.s0, q.t0, q.s1, q.t1, verts); + glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STREAM_DRAW); + text_bind_pointers(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + glDisable(GL_BLEND); + return (int)(pen_x - (float) x); +} + +int renderer_draw_textz(int x, int y, const char *s, + float r, float g, float b) { + return renderer_draw_text(x, y, s, strlen(s), r, g, b); +} + +int renderer_text_advance(void) { return g_glyph_advance ? g_glyph_advance : 9; } +int renderer_text_line_height(void){ return TEXT_LINE_STRIDE; } + +/* ===== init / shutdown ========================================== */ + +static void build_user_program_initial(void) { + /* Bake a known-good shader at startup. The caller (main) will + * overwrite this immediately with the VFS-resolved source. */ + static const char *PLASMA_SRC = + "void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n" + " vec2 uv = fragCoord / iResolution.xy;\n" + " float r = 0.5 + 0.5 * sin(iTime + uv.x * 6.2831);\n" + " float g = 0.5 + 0.5 * sin(iTime * 1.3 + uv.y * 6.2831);\n" + " float b = 0.5 + 0.5 * sin(iTime * 0.7 + (uv.x + uv.y) * 6.2831);\n" + " fragColor = vec4(r, g, b, 1.0);\n" + "}\n"; + renderer_recompile_user_shader(PLASMA_SRC); + + static const float user_quad[8] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + }; + glGenBuffers(1, &g_user_vbo); + glBindBuffer(GL_ARRAY_BUFFER, g_user_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof user_quad, user_quad, + GL_STATIC_DRAW); +} + +static void build_strip_program(void) { + int rf = 0; + GLuint vs = compile_shader(GL_VERTEX_SHADER, STRIP_VS_SRC, + "strip-vertex", &rf); + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, STRIP_FS_SRC, + "strip-fragment", &rf); + g_strip_prog = link_program(vs, fs, "strip-program", &rf); + g_strip_a_pos = glGetAttribLocation(g_strip_prog, "a_pos"); + static const float strip_quad[8] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, -0.92f, + 1.0f, -0.92f, + }; + glGenBuffers(1, &g_strip_vbo); + glBindBuffer(GL_ARRAY_BUFFER, g_strip_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof strip_quad, strip_quad, + GL_STATIC_DRAW); +} + +static void build_text_program(void) { + int rf = 0; + GLuint vs = compile_shader(GL_VERTEX_SHADER, TEXT_VS_SRC, + "text-vertex", &rf); + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, TEXT_FS_SRC, + "text-fragment", &rf); + g_text_prog = link_program(vs, fs, "text-program", &rf); + g_text_a_pos = glGetAttribLocation (g_text_prog, "a_pos"); + g_text_a_uv = glGetAttribLocation (g_text_prog, "a_uv"); + g_text_u_tex = glGetUniformLocation(g_text_prog, "u_tex"); + g_text_u_color = glGetUniformLocation(g_text_prog, "u_color"); + glGenBuffers(1, &g_text_vbo); + + /* 1×1 opaque white texture used by fill_rect. */ + glGenTextures(1, &g_white_tex); + glBindTexture(GL_TEXTURE_2D, g_white_tex); + unsigned char white = 0xff; + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, + 1, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, &white); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +static void build_font_atlas(void) { + /* Bake the printable ASCII subset of Inconsolata into an + * ATLAS_W×ATLAS_H GL_LUMINANCE texture. stbtt_PackFontRange returns + * non-zero on success; the 28 px range fits the 95 glyphs into + * 256×240 (under the single-upload payload cap, see above). */ + unsigned char *pixels = (unsigned char *) calloc(ATLAS_W * ATLAS_H, 1); + if (!pixels) { + fprintf(stderr, "WARN: text atlas: out of memory\n"); + return; + } + stbtt_pack_context pc; + int packed = 0; + if (stbtt_PackBegin(&pc, pixels, ATLAS_W, ATLAS_H, + /*stride=*/0, /*padding=*/1, NULL)) { + stbtt_PackSetOversampling(&pc, + TEXT_OVERSAMPLE_X, TEXT_OVERSAMPLE_Y); + packed = stbtt_PackFontRange(&pc, inconsolata_ttf, 0, + (float) TEXT_PIXEL_HEIGHT, + ATLAS_FIRST, ATLAS_COUNT, + g_atlas_chars); + stbtt_PackEnd(&pc); + } + if (!packed) { + fprintf(stderr, + "WARN: text atlas: pack failed (atlas may be partial)\n"); + } + glGenTextures(1, &g_atlas_tex); + glBindTexture(GL_TEXTURE_2D, g_atlas_tex); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, + ATLAS_W, ATLAS_H, 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + free(pixels); + + /* Use the lowercase 'm' (index 'm' - ATLAS_FIRST) advance as the + * monospace cell width — Inconsolata is constant-width and 'm' is + * present. Round up: stbtt returns fractional pixels. The ascent + * is derived from the bake's t0/t1 of 'M'. */ + if ('m' - ATLAS_FIRST < ATLAS_COUNT) { + float xad = g_atlas_chars['m' - ATLAS_FIRST].xadvance; + g_glyph_advance = (int)(xad + 0.5f); + if (g_glyph_advance < 1) g_glyph_advance = 1; + } + if ('M' - ATLAS_FIRST < ATLAS_COUNT) { + /* For stbtt baked quads, y0 is the negative of the ascent. */ + float yoff = -g_atlas_chars['M' - ATLAS_FIRST].yoff; + g_glyph_ascent = (int)(yoff + 0.5f); + if (g_glyph_ascent < 1) g_glyph_ascent = TEXT_PIXEL_HEIGHT - 4; + } + /* Diagnostic line — the test harness uses it to confirm atlas + * baking completed. */ + fprintf(stdout, "sdl2: text-atlas baked=%d advance=%dpx ascent=%dpx\n", + packed, g_glyph_advance, g_glyph_ascent); +} + +void renderer_init(int screen_w, int screen_h) { + g_screen_w = screen_w; + g_screen_h = screen_h; + /* Confirms the GL drawable the renderer actually paints into. If + * this prints smaller than the SDL window (sdl2: display=WxH), the + * framebuffer is being scaled up by the pane and text will look + * coarse regardless of the atlas bake size. */ + fprintf(stdout, "sdl2: renderer drawable=%dx%d\n", screen_w, screen_h); + build_user_program_initial(); + build_strip_program(); + build_text_program(); + build_font_atlas(); +} + +void renderer_set_screen_size(int w, int h) { + g_screen_w = w; + g_screen_h = h; +} + +void renderer_shutdown(void) { + if (g_user_prog) { glDeleteProgram(g_user_prog); g_user_prog = 0; } + if (g_strip_prog) { glDeleteProgram(g_strip_prog); g_strip_prog = 0; } + if (g_text_prog) { glDeleteProgram(g_text_prog); g_text_prog = 0; } + if (g_user_vbo) { glDeleteBuffers(1, &g_user_vbo); g_user_vbo = 0; } + if (g_strip_vbo) { glDeleteBuffers(1, &g_strip_vbo); g_strip_vbo = 0; } + if (g_text_vbo) { glDeleteBuffers(1, &g_text_vbo); g_text_vbo = 0; } + if (g_white_tex) { glDeleteTextures(1, &g_white_tex); g_white_tex = 0; } + if (g_atlas_tex) { glDeleteTextures(1, &g_atlas_tex); g_atlas_tex = 0; } + if (g_audio_tex) { glDeleteTextures(1, &g_audio_tex); g_audio_tex = 0; } +} diff --git a/programs/sdl2/renderer.h b/programs/sdl2/renderer.h new file mode 100644 index 0000000000..528d861d2f --- /dev/null +++ b/programs/sdl2/renderer.h @@ -0,0 +1,97 @@ +/* + * programs/sdl2/renderer.h — GLES2 drawing facade for the SDL2 GLSL + * playground. Owns three GL programs and the font atlas: + * + * 1. The user image-shader program (PLASMA_SRC fallback + the + * `mainImage`/Shadertoy-shape wrapper). Hot-swapped + * on every successful recompile; on a failed recompile the last + * good handle is retained. + * 2. A translucent red strip drawn at the bottom of the right pane + * when `renderer_set_error_visible(1)` is in effect. + * 3. A textured-quad program for the editor: glyphs are baked once + * at startup from the vendored Inconsolata-Regular.ttf into a + * GL_LUMINANCE atlas via stb_truetype. + * + * The header is intentionally free of SDL2 and GLES2 includes so the + * editor module (which only needs the text-rendering surface) doesn't + * drag the GL toolchain in. + */ +#pragma once + +#include + +void renderer_init(int screen_w, int screen_h); +void renderer_shutdown(void); + +/* The whole-window drawable size — text rendering converts pixel + * coordinates to NDC against this size. Call when SDL reports a new + * drawable size (resize, DPI change). */ +void renderer_set_screen_size(int w, int h); + +/* User-shader pipeline ------------------------------------------------ + * + * `renderer_recompile_user_shader` wraps `user_src` in the + * GLSL ES 1.0 Shadertoy template and links a new program. On success + * it swaps the active handle and re-binds the iResolution/iTime/etc + * uniform locations; on a real failure (compile/link status==0 with a + * non-empty info log) it deletes the broken program, leaves the last + * good handle in place, stashes the failing source line for + * renderer_last_error_line(), and returns 1. Empty-info-log failures — + * the headless-GL stub that NodeKernelHost emits because host_gl_query + * returns -1 — return 0 (warned but not "real"). */ +int renderer_recompile_user_shader(const char *user_src); + +/* Draws the user image-shader over the [vp_x, vp_x+vp_w] × [vp_y, + * vp_y+vp_h] sub-rectangle of the framebuffer. iResolution is the + * sub-rectangle size; iViewportOrigin is (vp_x, vp_y) so the Shadertoy + * `fragCoord / iResolution` idiom yields the standard 0..1 UVs even + * though gl_FragCoord is window-absolute. */ +void renderer_draw_user_shader(int vp_x, int vp_y, int vp_w, int vp_h, + float t, float dt, + float mouse_x, float mouse_y, int frame); + +/* Draws the translucent red strip across the bottom 8% of the + * (vp_x, vp_y, vp_w, vp_h) rectangle iff renderer_set_error_visible(1) + * is in effect. The caller is expected to have the right-pane scissor + * still active so the strip is clipped within it. */ +void renderer_draw_error_strip(int vp_x, int vp_y, int vp_w, int vp_h); + +void renderer_set_error_visible(int visible); + +/* 1-indexed user-source line of the last image-shader compile error + * (template prefix already subtracted), or -1 if none/unmappable. The + * editor uses it to mark the failing line. Only meaningful right after a + * recompile that returned a real failure. */ +int renderer_last_error_line(void); + +/* Audio spectrum (iAudio) --------------------------------------------- + * + * Uploads `n` magnitude bytes as a single-row GL_LUMINANCE texture bound + * to the user shader's `iAudio` sampler. The shader samples it as a 1D + * lookup: `texture2D(iAudio, vec2(x, 0.0)).r` is the level at normalized + * frequency x∈[0,1]. Call once per frame before renderer_draw_user_shader. + * `n` is expected to be AUDIO_SPECTRUM_BINS (128). */ +void renderer_set_audio_spectrum(const unsigned char *bins, int n); + +/* Text rendering ------------------------------------------------------ + * + * Inconsolata is rendered at a fixed pixel height; the atlas is built + * once at init time. Coordinates are pixels with the origin at the + * top-left of the window, matching the editor's mental model. */ + +int renderer_text_advance(void); /* monospace glyph cell width */ +int renderer_text_line_height(void); /* total line stride (px) */ + +/* Draw `n` ASCII bytes starting at the top-left pixel (x, y), in + * solid (r, g, b). Returns the advance in pixels. Non-printable bytes + * are silently skipped. Newlines are NOT handled — the editor renders + * one call per visible line. */ +int renderer_draw_text(int x, int y, const char *s, size_t n, + float r, float g, float b); +int renderer_draw_textz(int x, int y, const char *s, + float r, float g, float b); + +/* Solid-color filled rectangle in window-pixel coordinates. Used for + * the cursor, the gutter background, and any flat editor chrome. */ +void renderer_fill_rect(int x, int y, int w, int h, + float r, float g, float b, float a); diff --git a/programs/sdl2/sound_shader.c b/programs/sdl2/sound_shader.c new file mode 100644 index 0000000000..e0026faf20 --- /dev/null +++ b/programs/sdl2/sound_shader.c @@ -0,0 +1,337 @@ +/* + * programs/sdl2/sound_shader.c — see sound_shader.h for the model. + * + * The render path is deliberately separate from renderer.c's image + * pipeline: its own program, FBO, attachment texture, and quad VBO. The + * only shared GL state we must be careful about is GL_SCISSOR_TEST, which + * main.c leaves enabled for the split-pane layout — we disable it around + * the off-screen render so the full FBO is written, then restore it. + */ + +#include "sound_shader.h" + +#include +#include +#include +#include + +/* ----- shader template ------------------------------------------- */ + +static const char *SOUND_VS_SRC = + "attribute vec2 a_pos;\n" + "void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }\n"; + +/* The fragment turns its pixel into one stereo frame. iResolution.x is + * the FBO width, so `idx` walks 0..frames-1 row-major, matching the + * row-major glReadPixels decode on the CPU side. mainSound's vec2 is + * encoded as two 16-bit values split low/high byte across RGBA. */ +static const char *SOUND_FRAG_PREFIX = + "#version 100\n" + "precision highp float;\n" + "uniform float iSampleRate;\n" + "uniform float iBufferOffset;\n" + "uniform vec2 iResolution;\n"; + +static const char *SOUND_FRAG_SUFFIX = + "\nvoid main() {\n" + " float idx = floor(gl_FragCoord.x)\n" + " + floor(gl_FragCoord.y) * iResolution.x;\n" + " float t = iBufferOffset + idx / iSampleRate;\n" + " vec2 s = clamp(mainSound(t), -1.0, 1.0);\n" + " vec2 v16 = floor((s * 0.5 + 0.5) * 65535.0 + 0.5);\n" + " vec2 hi = floor(v16 / 256.0);\n" + " vec2 lo = v16 - hi * 256.0;\n" + " gl_FragColor = vec4(lo.x, hi.x, lo.y, hi.y) / 255.0;\n" + "}\n"; + +/* Default shader compiled at init — a quiet 220 Hz sine so the module is + * always in a renderable state even before a preset loads. */ +static const char *SOUND_DEFAULT_SRC = + "vec2 mainSound(in float time) {\n" + " float v = 0.3 * sin(6.2831853 * 220.0 * time);\n" + " return vec2(v, v);\n" + "}\n"; + +/* ----- GL objects + state ---------------------------------------- */ + +static GLuint g_prog = 0; +static GLuint g_vbo = 0; +static GLuint g_fbo = 0; +static GLuint g_fbo_tex = 0; +static GLint g_a_pos = -1; +static GLint g_u_rate = -1; +static GLint g_u_offset = -1; +static GLint g_u_res = -1; + +static char g_last_error[4096]; + +/* Decoded playback buffer (interleaved S16 stereo) + the frame count from + * the last render. Malloc'd once at init (≈54 MiB for the full track — + * too large for static BSS); NULL if the allocation failed, in which case + * render reports 0 frames and audio.c falls back to the chip synth. */ +static int16_t *g_pcm = NULL; +static int g_pcm_frames = 0; + +const char *sound_shader_last_error(void) { return g_last_error; } + +/* Map the GLSL info log's first "ERROR: 0::" back to the user's + * editor coordinates by subtracting the SOUND_FRAG_PREFIX line count. + * Returns the 1-indexed user line, or -1 if none/unmappable. */ +int sound_shader_last_error_line(void) { + const char *p = strstr(g_last_error, "0:"); + if (!p) return -1; + p += 2; + if (*p < '0' || *p > '9') return -1; + int combined = 0; + while (*p >= '0' && *p <= '9') combined = combined * 10 + (*p++ - '0'); + int prefix_lines = 0; + for (const char *q = SOUND_FRAG_PREFIX; *q; q++) + if (*q == '\n') prefix_lines++; + int user_line = combined - prefix_lines; + return user_line >= 1 ? user_line : -1; +} + +/* ----- compile helpers (same discrimination as renderer.c) ------- */ + +static GLuint sc_compile(GLenum type, const char *src, const char *tag, + int *real_failure) { + GLuint s = glCreateShader(type); + glShaderSource(s, 1, &src, NULL); + glCompileShader(s); + GLint status = 0; + glGetShaderiv(s, GL_COMPILE_STATUS, &status); + if (!status) { + GLint loglen = 0; + glGetShaderiv(s, GL_INFO_LOG_LENGTH, &loglen); + if (loglen > 0 && loglen < (GLint) sizeof g_last_error) { + glGetShaderInfoLog(s, loglen, NULL, g_last_error); + if (real_failure) *real_failure = 1; + } else { + snprintf(g_last_error, sizeof g_last_error, + "(empty info log; headless GL?)"); + } + fprintf(stderr, "WARN: sound-%s compile: %s\n", tag, g_last_error); + } + return s; +} + +static GLuint sc_link(GLuint vs, GLuint fs, int *real_failure) { + GLuint prog = glCreateProgram(); + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + glDeleteShader(vs); + glDeleteShader(fs); + GLint status = 0; + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (!status) { + GLint loglen = 0; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &loglen); + if (loglen > 0 && loglen < (GLint) sizeof g_last_error) { + glGetProgramInfoLog(prog, loglen, NULL, g_last_error); + if (real_failure) *real_failure = 1; + } else { + snprintf(g_last_error, sizeof g_last_error, + "(empty link log; headless GL?)"); + } + fprintf(stderr, "WARN: sound-program link: %s\n", g_last_error); + } + return prog; +} + +static void rebind_uniforms(void) { + g_a_pos = glGetAttribLocation (g_prog, "a_pos"); + g_u_rate = glGetUniformLocation(g_prog, "iSampleRate"); + g_u_offset = glGetUniformLocation(g_prog, "iBufferOffset"); + g_u_res = glGetUniformLocation(g_prog, "iResolution"); +} + +int sound_shader_recompile(const char *user_src) { + size_t pre = strlen(SOUND_FRAG_PREFIX); + size_t usr = strlen(user_src); + size_t suf = strlen(SOUND_FRAG_SUFFIX); + char *frag = malloc(pre + usr + suf + 1); + if (!frag) return 0; + memcpy(frag, SOUND_FRAG_PREFIX, pre); + memcpy(frag + pre, user_src, usr); + memcpy(frag + pre + usr, SOUND_FRAG_SUFFIX, suf + 1); + + int real_failure = 0; + GLuint vs = sc_compile(GL_VERTEX_SHADER, SOUND_VS_SRC, "vertex", + &real_failure); + GLuint fs = sc_compile(GL_FRAGMENT_SHADER, frag, "fragment", + &real_failure); + free(frag); + GLuint new_prog = sc_link(vs, fs, &real_failure); + + if (real_failure) { + glDeleteProgram(new_prog); + return 1; + } + if (g_prog) glDeleteProgram(g_prog); + g_prog = new_prog; + rebind_uniforms(); + return 0; +} + +/* ----- init / shutdown ------------------------------------------- */ + +void sound_shader_init(void) { + static const float quad[8] = { + -1.0f, -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, 1.0f, + }; + glGenBuffers(1, &g_vbo); + glBindBuffer(GL_ARRAY_BUFFER, g_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof quad, quad, GL_STATIC_DRAW); + + /* RGBA8 color target. NULL pixels → allocate without upload (dodges + * the OP_TEX_IMAGE_2D single-call payload cap; a 1024×1024 RGBA upload + * would be 4 MiB). */ + glGenTextures(1, &g_fbo_tex); + glBindTexture(GL_TEXTURE_2D, g_fbo_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + SOUND_SHADER_TEX_W, SOUND_SHADER_TEX_H, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glGenFramebuffers(1, &g_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, g_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, g_fbo_tex, 0); + GLenum st = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (st != GL_FRAMEBUFFER_COMPLETE) { + /* Non-fatal: the headless stub returns COMPLETE by default, and a + * real incomplete FBO just yields silence (0 frames). */ + fprintf(stderr, "WARN: sound FBO status=0x%x\n", (unsigned) st); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + /* Playback buffer for the full multi-tile track. malloc (not static) + * because it's ~54 MiB; on failure g_pcm stays NULL and render bails to + * the synth fallback rather than crashing. */ + g_pcm = malloc((size_t) SOUND_SHADER_FRAMES * 2 * sizeof(int16_t)); + if (!g_pcm) { + fprintf(stderr, "WARN: sound pcm malloc(%zu bytes) failed\n", + (size_t) SOUND_SHADER_FRAMES * 2 * sizeof(int16_t)); + } + + sound_shader_recompile(SOUND_DEFAULT_SRC); + fprintf(stdout, "sdl2: sound-shader init fbo=%dx%d tiles=%d frames=%d\n", + SOUND_SHADER_TEX_W, SOUND_SHADER_TEX_H, SOUND_SHADER_TILES, + SOUND_SHADER_FRAMES); +} + +void sound_shader_shutdown(void) { + if (g_prog) { glDeleteProgram(g_prog); g_prog = 0; } + if (g_vbo) { glDeleteBuffers(1, &g_vbo); g_vbo = 0; } + if (g_fbo_tex) { glDeleteTextures(1, &g_fbo_tex); g_fbo_tex = 0; } + /* The GLES stub exposes no glDeleteFramebuffers op; the FBO name is + * released when the GL context tears down at process exit. */ + g_fbo = 0; + free(g_pcm); + g_pcm = NULL; + g_pcm_frames = 0; +} + +/* ----- render + readback ----------------------------------------- */ + +int sound_shader_render(int sample_rate) { + if (!g_prog || !g_pcm) { g_pcm_frames = 0; return 0; } + if (sample_rate <= 0) sample_rate = 48000; + + const int W = SOUND_SHADER_TEX_W; + const int H = SOUND_SHADER_TEX_H; + const int tile_frames = SOUND_SHADER_TILE_FRAMES; + + /* main.c enables GL_SCISSOR_TEST once and leaves it on for the split- + * pane layout; the stub has no glIsEnabled, so we unconditionally + * disable it for the off-screen renders and re-enable it after. */ + glDisable(GL_SCISSOR_TEST); + + /* One reusable staging band-buffer for the whole FBO. calloc (not + * malloc): under the headless GL stub glReadPixels is a no-op that + * leaves the buffer untouched, so zero-init guarantees an all-zero + * "silent" decode there rather than feeding uninitialized garbage to + * the audio source. Real GL fully overwrites every band of every tile. */ + uint8_t *staging = calloc((size_t) W * H * 4, 1); + if (!staging) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glEnable(GL_SCISSOR_TEST); + g_pcm_frames = 0; + return 0; + } + /* 60 KB target → 15 rows/band at W=1024; stays under the kernel cap + * (MAX_QUERY_OUT_LEN = 64 KB) while minimizing readback round-trips. */ + int rows_per_band = (60 * 1024) / (W * 4); + if (rows_per_band < 1) rows_per_band = 1; + + /* Render the track as SOUND_SHADER_TILES consecutive time-windows. Each + * tile is a full FBO dispatch (kept light to dodge the GPU watchdog); + * iBufferOffset advances by one tile's duration so the windows are + * contiguous in `time`, and the decoded frames land back-to-back in + * g_pcm. */ + int any_nonzero = 0; + for (int tile = 0; tile < SOUND_SHADER_TILES; tile++) { + float offset = (float) ((double) tile * tile_frames / sample_rate); + + glBindFramebuffer(GL_FRAMEBUFFER, g_fbo); + glViewport(0, 0, W, H); + glUseProgram(g_prog); + glBindBuffer(GL_ARRAY_BUFFER, g_vbo); + if (g_a_pos >= 0) { + glVertexAttribPointer((GLuint) g_a_pos, 2, GL_FLOAT, GL_FALSE, + 0, NULL); + glEnableVertexAttribArray((GLuint) g_a_pos); + } + if (g_u_rate >= 0) glUniform1f(g_u_rate, (float) sample_rate); + if (g_u_offset >= 0) glUniform1f(g_u_offset, offset); + if (g_u_res >= 0) glUniform2f(g_u_res, (float) W, (float) H); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + /* Read back in <=64 KB row-bands. */ + for (int y0 = 0; y0 < H; y0 += rows_per_band) { + int rows = rows_per_band; + if (y0 + rows > H) rows = H - y0; + glReadPixels(0, y0, W, rows, GL_RGBA, GL_UNSIGNED_BYTE, + staging + (size_t) y0 * W * 4); + } + + /* Decode RGBA8 → S16 stereo into this tile's slice of g_pcm; track + * whether anything is non-zero so a headless (all-zero) run falls + * back to the synth. */ + size_t base = (size_t) tile * tile_frames; + for (int i = 0; i < tile_frames; i++) { + unsigned lo_l = staging[i * 4 + 0]; + unsigned hi_l = staging[i * 4 + 1]; + unsigned lo_r = staging[i * 4 + 2]; + unsigned hi_r = staging[i * 4 + 3]; + if (lo_l | hi_l | lo_r | hi_r) any_nonzero = 1; + unsigned l16 = (hi_l << 8) | lo_l; + unsigned r16 = (hi_r << 8) | lo_r; + /* [0,65535] → [-1,1] → S16. */ + float lf = (float) l16 / 65535.0f * 2.0f - 1.0f; + float rf = (float) r16 / 65535.0f * 2.0f - 1.0f; + g_pcm[(base + i) * 2 + 0] = (int16_t) (lf * 32767.0f); + g_pcm[(base + i) * 2 + 1] = (int16_t) (rf * 32767.0f); + } + } + free(staging); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glEnable(GL_SCISSOR_TEST); + + const int frames = tile_frames * SOUND_SHADER_TILES; + g_pcm_frames = any_nonzero ? frames : 0; + fprintf(stdout, "sdl2: sound-shader render frames=%d tiles=%d audible=%d\n", + frames, SOUND_SHADER_TILES, any_nonzero); + return g_pcm_frames; +} + +const int16_t *sound_shader_pcm(int *out_frames) { + if (out_frames) *out_frames = g_pcm_frames; + return g_pcm_frames > 0 ? g_pcm : NULL; +} diff --git a/programs/sdl2/sound_shader.h b/programs/sdl2/sound_shader.h new file mode 100644 index 0000000000..35d2b1bee4 --- /dev/null +++ b/programs/sdl2/sound_shader.h @@ -0,0 +1,76 @@ +/* + * programs/sdl2/sound_shader.h — Shadertoy-style "sound shader" support + * for the SDL2 GLSL playground: a shader renders the audio it plays. + * + * A sound shader supplies `vec2 mainSound(in float time)` returning the + * (left, right) amplitude in [-1, 1] at `time` seconds. We wrap it in a + * GLSL ES 1.0 template that turns each framebuffer pixel into one stereo + * frame: the fragment computes its sample index from gl_FragCoord, + * evaluates mainSound, and encodes the result as RGBA8 (L → r,g and + * R → b,a, 16-bit split). Rendering the whole FBO once therefore + * produces SOUND_SHADER_FRAMES stereo frames in a single dispatch; we + * then glReadPixels the FBO back (in <=64 KB row-bands, the kernel's + * MAX_QUERY_OUT_LEN cap) and decode RGBA8 → S16. The decoded buffer is + * handed to audio.c, which loops it as the playback source. + * + * GL ownership note: like renderer.c this module talks GLES2 directly. + * It keeps its own program / FBO / VBO so the image-shader pipeline in + * renderer.c is untouched. Under the headless Node GL stub glReadPixels + * is a no-op (host_gl_query returns -1), so the decoded buffer stays + * silent and sound_shader_render reports 0 frames — audio.c then falls + * back to the chip synth. Real audio only materializes on the browser + * host with a live WebGL context. + */ +#pragma once + +#include + +/* FBO geometry. The FBO holds ONE tile = SOUND_SHADER_TILE_FRAMES stereo + * frames (1024×1024 = 1048576 ≈ 21.85 s at 48 kHz). A single heavy + * mainSound (e.g. the Tunnelwisp boot default) rendered across the whole + * track in one dispatch would trip the GPU watchdog, so sound_shader_render + * renders SOUND_SHADER_TILES tiles in sequence, advancing the iBufferOffset + * uniform by one tile's worth of seconds each time, and concatenates the + * read-back PCM. 13 tiles ≈ 284 s — long enough to play the (~4.5 min, + * one-shot) track out to where every voice has faded, so the loop back to + * t=0 is clean rather than an abrupt mid-phrase cut. + * + * The decoded S16 stereo buffer is SOUND_SHADER_FRAMES*2*2 bytes ≈ 54 MiB, + * malloc'd once at init (not static BSS). Each tile's readback is still + * chunked into <=64 KB row-bands for the kernel's MAX_QUERY_OUT_LEN cap. */ +#define SOUND_SHADER_TEX_W 1024 +#define SOUND_SHADER_TEX_H 1024 +#define SOUND_SHADER_TILES 13 +#define SOUND_SHADER_TILE_FRAMES (SOUND_SHADER_TEX_W * SOUND_SHADER_TEX_H) +#define SOUND_SHADER_FRAMES (SOUND_SHADER_TILE_FRAMES * SOUND_SHADER_TILES) + +/* Create the GL program scaffold (FBO, attachment texture, quad VBO) and + * compile a known-good default sine shader. Call once after the GL + * context is current. */ +void sound_shader_init(void); +void sound_shader_shutdown(void); + +/* Compile `user_src` (a `mainSound` body) into the sound program. Mirrors + * renderer_recompile_user_shader: returns 1 on a real compile/link + * failure (last-good program retained, error stashed), 0 on success or a + * headless empty-log failure. */ +int sound_shader_recompile(const char *user_src); + +/* Render the current sound program to the FBO and read it back, decoding + * RGBA8 → S16 stereo into the internal buffer. Returns the number of + * stereo frames decoded, or 0 if there is no program or the readback + * produced all-zero pixels (headless / silent). `sample_rate` sets the + * iSampleRate uniform so the shader's `time` axis matches playback. */ +int sound_shader_render(int sample_rate); + +/* Borrow the most recently decoded S16 stereo buffer. `*out_frames` is + * set to the frame count from the last sound_shader_render. The pointer + * is stable for the life of the module; its contents change on each + * render. Returns NULL with *out_frames=0 before the first render. */ +const int16_t *sound_shader_pcm(int *out_frames); + +const char *sound_shader_last_error(void); + +/* 1-indexed user-source line of the last sound-shader compile error + * (template prefix already subtracted), or -1 if none/unmappable. */ +int sound_shader_last_error_line(void); diff --git a/programs/sdl2/test/editor_test.c b/programs/sdl2/test/editor_test.c new file mode 100644 index 0000000000..d892541695 --- /dev/null +++ b/programs/sdl2/test/editor_test.c @@ -0,0 +1,229 @@ +/* + * programs/sdl2/test/editor_test.c — native unit test for the pure + * gap-buffer logic in editor.c (insert/delete, movement, selection, + * clipboard-dup, auto-replace, and undo/redo coalescing). + * + * editor.c depends only on renderer.h for its draw calls, so we compile + * it natively here against the stub renderer below — no SDL/GL/wasm. The + * host gate runs this via host/test/sdl2-editor-unit.test.ts. Each top + * level case is one behavior; CHECK reports the first divergence per case + * and the process exits non-zero if any case fails. + */ + +#include "../editor.h" +#include "../renderer.h" + +#include +#include +#include + +/* ----- renderer stubs -------------------------------------------- * + * editor_render draws through these; the unit test only cares that the + * geometry math doesn't fault, so they record nothing. The fixed advance + * / line height give compute_layout a stable grid for the render smoke. */ +int renderer_text_advance(void) { return 8; } +int renderer_text_line_height(void) { return 16; } +int renderer_draw_text(int x, int y, const char *s, size_t n, + float r, float g, float b) { + (void) x; (void) y; (void) s; (void) r; (void) g; (void) b; + return (int) n * 8; +} +int renderer_draw_textz(int x, int y, const char *s, + float r, float g, float b) { + return renderer_draw_text(x, y, s, strlen(s), r, g, b); +} +void renderer_fill_rect(int x, int y, int w, int h, + float r, float g, float b, float a) { + (void) x; (void) y; (void) w; (void) h; + (void) r; (void) g; (void) b; (void) a; +} + +/* ----- assertions ------------------------------------------------ */ + +static int failures = 0; + +#define CHECK(cond, msg) do { \ + if (!(cond)) { printf("FAIL: %s\n", (msg)); failures++; } \ +} while (0) + +static void check_text(const char *want, const char *label) { + char *got = editor_dup_text(); + if (!got || strcmp(got, want) != 0) { + printf("FAIL: %s: got \"%s\" want \"%s\"\n", + label, got ? got : "(null)", want); + failures++; + } + free(got); +} + +static void check_sel(const char *want, const char *label) { + char *got = editor_selection_dup(); + const char *g = got ? got : ""; + if (strcmp(g, want) != 0) { + printf("FAIL: %s: selection \"%s\" want \"%s\"\n", label, g, want); + failures++; + } + free(got); +} + +/* ----- cases ----------------------------------------------------- */ + +static void test_insert_delete(void) { + editor_init(""); + editor_insert_char('a'); + editor_insert_char('b'); + editor_insert_char('c'); + check_text("abc", "insert chars"); + CHECK(editor_cursor_offset() == 3, "insert leaves cursor after text"); + + editor_delete_back(); + check_text("ab", "delete_back"); + + editor_move_home(); + editor_delete_forward(); + check_text("b", "delete_forward at home"); + + editor_init("x"); + editor_move_end(); + editor_insert_text("yz", 2); + check_text("xyz", "insert_text"); + + editor_init(""); + editor_insert_tab(); + check_text(" ", "insert_tab is four spaces"); +} + +static void test_movement(void) { + editor_init("ab\ncd"); + editor_move_home(); + CHECK(editor_cursor_offset() == 0, "home → offset 0"); + CHECK(editor_cursor_line() == 0 && editor_cursor_col() == 0, + "start is line 0 col 0"); + + editor_move_down(); /* desired col 0 → line 1 col 0 */ + CHECK(editor_cursor_line() == 1, "move_down advances a line"); + editor_move_end(); + CHECK(editor_cursor_col() == 2, "end of \"cd\" is col 2"); + CHECK(editor_line_count() == 2, "two lines"); + + /* Desired-column memory across a short line. */ + editor_init("longline\nx\nlongline"); + editor_move_end(); /* col 8 on line 0 */ + editor_move_down(); /* line 1 "x" is shorter → clamps to col 1 */ + CHECK(editor_cursor_col() == 1, "clamp to short line"); + editor_move_down(); /* line 2 restores desired col 8 */ + CHECK(editor_cursor_col() == 8, "desired column restored"); +} + +static void test_selection(void) { + editor_init("hello world"); + editor_selection_begin(); /* anchor at offset 0 */ + for (int i = 0; i < 5; i++) editor_move_right(); + CHECK(editor_has_selection(), "selection active after shift-move"); + check_sel("hello", "selection span"); + + editor_selection_clear(); + CHECK(!editor_has_selection(), "selection cleared"); + + editor_select_all(); + CHECK(editor_has_selection(), "select_all selects"); + check_sel("hello world", "select_all span"); + + /* delete_selection removes the span and lands the cursor at its start. */ + editor_init("hello world"); + editor_selection_begin(); + for (int i = 0; i < 6; i++) editor_move_right(); /* "hello " */ + editor_delete_selection(); + check_text("world", "delete_selection"); + CHECK(editor_cursor_offset() == 0, "cursor at deleted-span start"); + CHECK(!editor_has_selection(), "selection cleared after delete"); +} + +static void test_auto_replace(void) { + /* Typing over a selection replaces it (the path every insert caller + * relies on instead of special-casing). */ + editor_init("hello"); + editor_select_all(); + editor_insert_char('X'); + check_text("X", "insert replaces selection"); + + /* Backspace with a selection deletes the selection, not one char. */ + editor_init("hello"); + editor_select_all(); + editor_delete_back(); + check_text("", "backspace deletes selection"); + + /* Paste-shaped insert_text over a selection. */ + editor_init("abc"); + editor_select_all(); + editor_insert_text("XY", 2); + check_text("XY", "insert_text replaces selection"); +} + +static void test_undo_redo(void) { + /* A run of same-kind inserts coalesces into one undo step. */ + editor_init(""); + editor_insert_char('a'); + editor_insert_char('b'); + editor_insert_char('c'); + editor_undo(); + check_text("", "coalesced inserts undo as one step"); + editor_redo(); + check_text("abc", "redo restores the group"); + + /* A cursor move ends the group, so the next insert is its own step. */ + editor_init(""); + editor_insert_char('a'); + editor_move_left(); + editor_insert_char('b'); /* "ba" */ + check_text("ba", "two groups typed"); + editor_undo(); + check_text("a", "first undo reverts second group"); + editor_undo(); + check_text("", "second undo reverts first group"); + + /* A fresh edit clears the redo stack. */ + editor_init(""); + editor_insert_char('a'); + editor_undo(); + editor_insert_char('z'); + editor_redo(); /* nothing to redo */ + check_text("z", "edit after undo clears redo"); +} + +static void test_replace_all(void) { + editor_init("original"); + editor_replace_all("brand new"); + check_text("brand new", "replace_all swaps the buffer"); + CHECK(editor_cursor_offset() == 0, "replace_all resets cursor to top"); + editor_undo(); + check_text("original", "replace_all is a single undo step"); +} + +static void test_render_smoke(void) { + /* Exercise the render + error-line path for crash-safety; the stub + * renderer swallows the draws. */ + editor_init("vec2 uv;\n bad line\n/* block */ ok"); + editor_set_error_line(1); + editor_render(0, 0, 400, 300, /*now_ms=*/0, /*has_focus=*/1); + editor_set_error_line(-1); + editor_render(0, 0, 400, 300, /*now_ms=*/700, /*has_focus=*/0); +} + +int main(void) { + test_insert_delete(); + test_movement(); + test_selection(); + test_auto_replace(); + test_undo_redo(); + test_replace_all(); + test_render_smoke(); + editor_shutdown(); + + if (failures == 0) { + printf("editor_test: ALL PASS\n"); + return 0; + } + printf("editor_test: %d FAILURE(S)\n", failures); + return 1; +} diff --git a/programs/sdl2/third_party/Inconsolata-Regular.ttf b/programs/sdl2/third_party/Inconsolata-Regular.ttf new file mode 100644 index 0000000000..3b74e08a12 Binary files /dev/null and b/programs/sdl2/third_party/Inconsolata-Regular.ttf differ diff --git a/programs/sdl2/third_party/NOTICE.md b/programs/sdl2/third_party/NOTICE.md new file mode 100644 index 0000000000..dc7ad00310 --- /dev/null +++ b/programs/sdl2/third_party/NOTICE.md @@ -0,0 +1,51 @@ +# Third-party vendored assets — SDL2 playground + +Two files are vendored here for the editor implementation introduced in +the GLSL playground's text rendering. Both ship under permissive licenses +compatible with this repo. Neither file is modified from upstream. + +## `stb_truetype.h` + +- Upstream: +- Version: v1.26 (header self-reports the version at line 1). +- License: dual-licensed under MIT and public-domain Unlicense — see the + trailing `ALTERNATIVE A` / `ALTERNATIVE B` license block in the file + itself. We use the public-domain leg. +- Author: Sean Barrett / RAD Game Tools, 2009-2021. + +`stb_truetype` is included as a header-only library. Exactly one +translation unit (`renderer.c`) defines `STB_TRUETYPE_IMPLEMENTATION` +before including the header so the implementation lands once. + +## `Inconsolata-Regular.ttf` + +- Upstream: +- License: SIL Open Font License v1.1 + (). +- Authors: Raph Levien with The Inconsolata Project Authors. + +The TTF is converted to a C byte array at build time by +`scripts/build-programs.sh` (which emits `inconsolata_ttf.h` next to +this file). The generated header is git-ignored — the `.ttf` is the +source of truth. To regenerate the header manually: + +```bash +python3 - <<'PY' +import pathlib, textwrap +src = pathlib.Path("programs/sdl2/third_party/Inconsolata-Regular.ttf").read_bytes() +dst = pathlib.Path("programs/sdl2/third_party/inconsolata_ttf.h") +chunks = textwrap.wrap(",".join(f"0x{b:02x}" for b in src), width=88) +dst.write_text( + "/* Auto-generated from Inconsolata-Regular.ttf — see NOTICE.md */\n" + "#pragma once\n" + f"static const unsigned char inconsolata_ttf[] = {{\n" + + "\n".join(chunks) + "\n};\n" + f"static const unsigned int inconsolata_ttf_len = {len(src)};\n" +) +PY +``` + +The SIL OFL requires that derived font files carry a reserved-name +clause; we do not modify the font binary, so no reserved-name handling +applies. The OFL also requires attribution wherever the font is +distributed, which this NOTICE provides. diff --git a/programs/sdl2/third_party/stb_truetype.h b/programs/sdl2/third_party/stb_truetype.h new file mode 100644 index 0000000000..90a5c2e2b3 --- /dev/null +++ b/programs/sdl2/third_party/stb_truetype.h @@ -0,0 +1,5079 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) Yakov Galka +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + // distance from singular values (in the same units as the pixel grid) + const float eps = 1./1024, eps2 = eps*eps; + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 >= eps2) + precompute[i] = 1.0f / len2; + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear + if (STBTT_fabs(b) >= eps2) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/programs/sdl2_evdev_smoke.c b/programs/sdl2_evdev_smoke.c new file mode 100644 index 0000000000..aa8dee6259 --- /dev/null +++ b/programs/sdl2_evdev_smoke.c @@ -0,0 +1,44 @@ +/* + * sdl2_evdev_smoke — exercise SDL2's evdev input backend against + * the kandelo /dev/input/event[0-31] devices. Prints "OK evdev" on + * success. + * + * Verifies, at minimum: + * * libSDL2.a + libinput.a (the no-op stub) link in one pass. + * Linking libinput.a here is mostly to verify that SDL2's + * udev-fallback detection branch — the one that calls + * `libinput_udev_create_context` and expects NULL — finds the + * stub at link time. With --disable-libudev SDL2 falls through + * to direct /dev/input/event* scan instead. + * * SDL_Init(SDL_INIT_EVENTS) sets up the event queue without + * spawning a thread (open-arch #1 — no pthread_create on wasm32). + * * SDL_PumpEvents() is callable; on an empty event queue it must + * not block, hang, or trap. + * + * Used by host/test/sdl2-evdev-smoke.test.ts. + */ +#include +#include + +int main(void) +{ + if (SDL_Init(SDL_INIT_EVENTS) != 0) { + fprintf(stderr, "FAIL: SDL_Init(EVENTS): %s\n", SDL_GetError()); + return 1; + } + + /* Two pump iterations — one to flush any startup events, one to + * verify the second call doesn't blow up against stale state. */ + SDL_PumpEvents(); + SDL_PumpEvents(); + + SDL_Event ev; + int polled = SDL_PollEvent(&ev); + /* polled may be 0 (no events) or 1 (a startup event such as + * window-shown — both are acceptable as long as PollEvent itself + * returns and doesn't trap). */ + + printf("OK evdev polled=%d\n", polled); + SDL_Quit(); + return 0; +} diff --git a/programs/sdl2_kmsdrm_smoke.c b/programs/sdl2_kmsdrm_smoke.c new file mode 100644 index 0000000000..391b6434de --- /dev/null +++ b/programs/sdl2_kmsdrm_smoke.c @@ -0,0 +1,54 @@ +/* + * sdl2_kmsdrm_smoke — exercise SDL2's KMSDRM video backend against + * the kandelo /dev/dri/card0 device. Prints "OK kmsdrm " on + * success, where is the name SDL2 selected for the video + * subsystem (expected: "KMSDRM"). + * + * Verifies, at minimum: + * * libSDL2.a + libdrm.a + libgbm.a link in one pass. + * * SDL_Init(SDL_INIT_VIDEO) reaches the KMSDRM probe and returns + * successfully against kandelo's libdrm-KMS subset. + * * SDL_GetCurrentVideoDriver() reports "KMSDRM" — confirms the + * backend wasn't silently downgraded to "dummy". + * + * Window creation is deliberately skipped: gbm_surface_create needs + * the GL stubs (libegl-stub + libgles2-stub + libgbm-extended) that + * land in the same PR. This smoke test runs the link + init path; the + * sdl2 playground binary runs the create-window + page-flip path. + * + * Used by host/test/sdl2-kmsdrm-smoke.test.ts. + */ +#include +#include +#include + +int main(void) +{ + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "FAIL: SDL_Init(VIDEO): %s\n", SDL_GetError()); + return 1; + } + + const char *driver = SDL_GetCurrentVideoDriver(); + if (driver == NULL) { + fprintf(stderr, "FAIL: SDL_GetCurrentVideoDriver returned NULL\n"); + SDL_Quit(); + return 1; + } + if (strcmp(driver, "KMSDRM") != 0) { + fprintf(stderr, "FAIL: video driver = %s, expected KMSDRM\n", driver); + SDL_Quit(); + return 1; + } + + int n = SDL_GetNumVideoDisplays(); + if (n < 1) { + fprintf(stderr, "FAIL: SDL_GetNumVideoDisplays = %d (expected >= 1)\n", n); + SDL_Quit(); + return 1; + } + + printf("OK kmsdrm %s displays=%d\n", driver, n); + SDL_Quit(); + return 0; +} diff --git a/scripts/build-dri-stubs.sh b/scripts/build-dri-stubs.sh index 6b49ffe662..9c6d198677 100755 --- a/scripts/build-dri-stubs.sh +++ b/scripts/build-dri-stubs.sh @@ -1,10 +1,20 @@ #!/bin/bash set -euo pipefail -# Compile libc/glue/libdrm_stub.c + libc/glue/libgbm_stub.c into -# sysroot/lib/{libdrm.a,libgbm.a}. Run after scripts/build-musl.sh -# has populated the sysroot with the DRI headers from -# libc/musl-overlay/include/{drm,gbm.h,xf86drm*.h}/. +# Install sysroot/lib/libdrm.a from the libdrm package and compile +# libc/glue/libgbm_stub.c into sysroot/lib/libgbm.a. Run after +# scripts/build-musl.sh has populated the sysroot with gbm.h from +# libc/musl-overlay/include/. +# +# libdrm is upstream 2.4.120, KMS subset only. The recipe lives at +# packages/registry/libdrm/; the dep-resolver compiles xf86drm.c / +# xf86drmMode.c / xf86drmHash.c / xf86drmRandom.c into libdrm.a and +# stages the UAPI + public headers under a cached prefix. We copy both +# into the sysroot so `-ldrm` resolves and the headers are visible at +# $SYSROOT/include/drm/ and $SYSROOT/include/libdrm/. Copy, not +# symlink: scripts/test-graphics-pkgconfig.sh proves a copied sysroot +# still resolves its graphics flags, and a link into the resolver +# cache would leave that copy dangling. REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" SYSROOT="$REPO_ROOT/sysroot" @@ -25,37 +35,48 @@ LLVM_BIN="$(find_llvm_bin)" CC="$LLVM_BIN/clang" AR="$LLVM_BIN/llvm-ar" -if [ ! -f "$SYSROOT/include/xf86drm.h" ] || [ ! -f "$SYSROOT/include/gbm.h" ]; then - echo "Error: DRI headers missing from $SYSROOT/include." >&2 +if [ ! -f "$SYSROOT/include/gbm.h" ]; then + echo "Error: gbm.h missing from $SYSROOT/include." >&2 echo "Run scripts/build-musl.sh first (installs overlay headers)." >&2 exit 1 fi +OUT_DIR="$SYSROOT/lib" +PC_DIR="$OUT_DIR/pkgconfig" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +mkdir -p "$OUT_DIR" "$PC_DIR" + +echo "Resolving libdrm (upstream KMS subset)..." +HOST_TRIPLE="$(rustc -vV | awk '/^host/ {print $2}')" +(cd "$REPO_ROOT" && cargo run -p xtask --target "$HOST_TRIPLE" --quiet -- build-deps resolve libdrm >/dev/null) +LIBDRM_PREFIX="$(cd "$REPO_ROOT" && cargo run -p xtask --target "$HOST_TRIPLE" --quiet -- build-deps path libdrm)" +if [ ! -f "$LIBDRM_PREFIX/lib/libdrm.a" ]; then + echo "Error: libdrm resolve succeeded but $LIBDRM_PREFIX/lib/libdrm.a is missing." >&2 + exit 1 +fi +rm -rf "$OUT_DIR/libdrm.a" "$SYSROOT/include/drm" "$SYSROOT/include/libdrm" +cp "$LIBDRM_PREFIX/lib/libdrm.a" "$OUT_DIR/libdrm.a" +cp -R "$LIBDRM_PREFIX/include/drm" "$SYSROOT/include/drm" +cp -R "$LIBDRM_PREFIX/include/libdrm" "$SYSROOT/include/libdrm" + CFLAGS=( --target=wasm32-unknown-unknown --sysroot="$SYSROOT" -I"$GLUE_DIR" + -I"$SYSROOT/include/libdrm" + -I"$SYSROOT/include/drm" -nostdlib -O2 -matomics -mbulk-memory -fno-trapping-math ) -OUT_DIR="$SYSROOT/lib" -PC_DIR="$OUT_DIR/pkgconfig" -TMP="$(mktemp -d)" -trap 'rm -rf "$TMP"' EXIT -mkdir -p "$OUT_DIR" "$PC_DIR" - -echo "Building libdrm.a (libdrm_stub.c)..." -"$CC" "${CFLAGS[@]}" -c "$GLUE_DIR/libdrm_stub.c" -o "$TMP/libdrm_stub.o" -"$AR" rcs "$OUT_DIR/libdrm.a" "$TMP/libdrm_stub.o" - echo "Building libgbm.a (libgbm_stub.c)..." "$CC" "${CFLAGS[@]}" -c "$GLUE_DIR/libgbm_stub.c" -o "$TMP/libgbm_stub.o" "$AR" rcs "$OUT_DIR/libgbm.a" "$TMP/libgbm_stub.o" -echo "DRI stubs installed:" +echo "DRI libraries installed:" ls -la "$OUT_DIR/libdrm.a" "$OUT_DIR/libgbm.a" bash "$REPO_ROOT/scripts/write-graphics-pkgconfig.sh" dri "$PC_DIR" diff --git a/scripts/build-programs.sh b/scripts/build-programs.sh index e2adf9b2bf..9dbf56ab25 100755 --- a/scripts/build-programs.sh +++ b/scripts/build-programs.sh @@ -143,6 +143,14 @@ CFLAGS=( -fno-trapping-math -mllvm -wasm-enable-sjlj -mllvm -wasm-use-legacy-eh=false + # Upstream libdrm installs public headers under `include/libdrm/` + # (matches the `--cflags` pkg-config flag). Programs `#include + # ` from there. `include/drm/` is the UAPI dir that + # xf86drm.h itself transitively pulls in via `#include `; + # both dirs must be on the search path or the upstream header + # fan-out doesn't resolve. Harmless when the dirs are absent. + -I"$SYSROOT/include/libdrm" + -I"$SYSROOT/include/drm" ) LINK_PRE_LIBS=( @@ -324,6 +332,25 @@ if ls "$REPO_ROOT/programs/"*.cpp >/dev/null 2>&1; then ensure_libcxx_in_sysroot wasm32 "$SYSROOT" fi +# Resolve SDL2 and stage it in the sysroot when there are SDL2 programs to +# build. Re-resolved on every run rather than guarded on libSDL2.a: SDL2 +# depends on libdrm, whose cache directory moves when its build.toml +# revision bumps, and a guarded fast path would leave the staged headers +# and archive pointing at the pre-bump cache. The resolver is cached, so +# repeating it is cheap. +if ls "$REPO_ROOT"/programs/sdl2_*.c >/dev/null 2>&1 \ + || ls "$REPO_ROOT"/programs/sdl2/*.c >/dev/null 2>&1; then + echo "==> Resolving sdl2 for SDL2 programs..." + HOST_TRIPLE="$(rustc -vV | awk '/^host/ {print $2}')" + (cd "$REPO_ROOT" && cargo run -p xtask --target "$HOST_TRIPLE" --quiet -- \ + build-deps resolve sdl2 >/dev/null) + SDL2_PREFIX="$(cd "$REPO_ROOT" && cargo run -p xtask \ + --target "$HOST_TRIPLE" --quiet -- build-deps path sdl2)" + cp "$SDL2_PREFIX/lib/libSDL2.a" "$SYSROOT/lib/libSDL2.a" + rm -rf "$SYSROOT/include/SDL2" + cp -R "$SDL2_PREFIX/include/SDL2" "$SYSROOT/include/SDL2" +fi + echo "Building user programs..." for src in "$REPO_ROOT/programs/"*.c; do [ -f "$src" ] || continue @@ -338,7 +365,7 @@ for src in "$REPO_ROOT/programs/"*.c; do # copies only as runtime test fixtures. build_program "$src" "$TEST_FIXTURE_DIR/wasm32" ;; - modeset.c|dri-modeset.c|dumb_roundtrip.c) + modeset.c|dri-modeset.c|dumb_roundtrip.c|gbm_surface_smoke.c) build_program "$src" "$OUT_DIR_32" \ "$SYSROOT/lib/libgbm.a" "$SYSROOT/lib/libdrm.a" ;; @@ -346,6 +373,13 @@ for src in "$REPO_ROOT/programs/"*.c; do build_program "$src" "$OUT_DIR_32" \ "$SYSROOT/lib/libdrm.a" ;; + sdl2_*.c) + # SDL2's KMSDRM backend calls into gbm and libdrm, so both + # follow libSDL2.a in the link order. + build_program "$src" "$OUT_DIR_32" \ + "$SYSROOT/lib/libSDL2.a" \ + "$SYSROOT/lib/libgbm.a" "$SYSROOT/lib/libdrm.a" + ;; posix-timer-thread.c) # Keep the fixture's pthread capacity small so its timer-helper # churn test proves detached helpers are actually reclaimed. @@ -363,6 +397,69 @@ for src in "$REPO_ROOT/programs/"*.cpp; do build_cpp_program "$src" "$OUT_DIR_32" done +# SDL2 playground app — every .c under programs/sdl2/ links into the +# single sdl2.wasm binary. Multi-source clang invocation: clang accepts +# the sources together with the libc/glue prelude and the full SDL2 + +# dependency archive set, then we run fork-instrument on the result. +# libEGL / libGLESv2 are named explicitly: the SDL_opengles2 header +# bundle transitively pulls , but the per-file grep in +# build_program only catches direct top-level EGL/GLES includes. +if ls "$REPO_ROOT"/programs/sdl2/*.c >/dev/null 2>&1; then + # Regenerate the Inconsolata TTF→C byte-array header if missing or + # older than the .ttf. The .h is git-ignored; the .ttf is the + # source of truth (see programs/sdl2/third_party/NOTICE.md). + sdl2_ttf="$REPO_ROOT/programs/sdl2/third_party/Inconsolata-Regular.ttf" + sdl2_ttf_h="$REPO_ROOT/programs/sdl2/third_party/inconsolata_ttf.h" + if [ -f "$sdl2_ttf" ]; then + if [ ! -f "$sdl2_ttf_h" ] || [ "$sdl2_ttf" -nt "$sdl2_ttf_h" ]; then + echo " Regenerating inconsolata_ttf.h from $(basename "$sdl2_ttf")..." + python3 - "$sdl2_ttf" "$sdl2_ttf_h" <<'PY' +import sys, pathlib +src = pathlib.Path(sys.argv[1]).read_bytes() +dst = pathlib.Path(sys.argv[2]) +# 16 bytes per line keeps each token whole and the file ~6× the .ttf +# size — well under what clang chokes on. +PER_LINE = 16 +lines = [ + ",".join(f"0x{b:02x}" for b in src[i:i + PER_LINE]) + for i in range(0, len(src), PER_LINE) +] +dst.write_text( + "/* Auto-generated from Inconsolata-Regular.ttf by " + "scripts/build-programs.sh. */\n" + "/* See programs/sdl2/third_party/NOTICE.md for license. */\n" + "#pragma once\n" + f"static const unsigned char inconsolata_ttf[] = {{\n" + + ",\n".join(lines) + "\n};\n" + f"static const unsigned int inconsolata_ttf_len = {len(src)};\n" +) +PY + fi + fi + sdl2_sources=("$REPO_ROOT"/programs/sdl2/*.c) + sdl2_wasm="$OUT_DIR_32/sdl2.wasm" + if package_owns_direct_program_path wasm32 sdl2.wasm; then + # Same contract as build_program's ownership check: the sdl2-demo + # recipe publishes this path through build-deps. + if [ -e "$sdl2_wasm" ] && [ ! -L "$sdl2_wasm" ]; then + echo "Error: package-owned resolver mirror is already occupied: $sdl2_wasm" >&2 + exit 1 + fi + echo " Skipping sdl2: package resolver owns wasm32/sdl2.wasm" + else + echo " Compiling sdl2 (multi-source: ${#sdl2_sources[@]} file(s))..." + "$CC" "${CFLAGS[@]}" "${sdl2_sources[@]}" \ + "${LINK_PRE_LIBS[@]}" \ + "$SYSROOT/lib/libSDL2.a" \ + "$SYSROOT/lib/libgbm.a" "$SYSROOT/lib/libdrm.a" \ + "$SYSROOT/lib/libEGL.a" "$SYSROOT/lib/libGLESv2.a" \ + "${LINK_POST_LIBS[@]}" \ + -o "$sdl2_wasm" + "$FORK_INSTRUMENT" "$sdl2_wasm" -o "$sdl2_wasm.instr" + mv "$sdl2_wasm.instr" "$sdl2_wasm" + fi +fi + echo "Building example programs..." for src in "$REPO_ROOT/examples/"*.c; do [ -f "$src" ] || continue diff --git a/scripts/write-graphics-pkgconfig.sh b/scripts/write-graphics-pkgconfig.sh index ccbb8fac02..9d85a78491 100755 --- a/scripts/write-graphics-pkgconfig.sh +++ b/scripts/write-graphics-pkgconfig.sh @@ -19,10 +19,10 @@ libdir=${prefix}/lib includedir=${prefix}/include Name: libdrm -Description: Kandelo wasm DRI userspace shim -Version: 1.0.0 +Description: Direct Rendering Manager library, KMS subset +Version: 2.4.120 Libs: -L${libdir} -ldrm -Cflags: -I${includedir} +Cflags: -I${includedir} -I${includedir}/libdrm -I${includedir}/drm EOF cat >"$pc_dir/gbm.pc" <<'EOF' @@ -34,7 +34,7 @@ Name: gbm Description: Kandelo wasm GBM userspace shim Version: 1.0.0 Libs: -L${libdir} -lgbm -ldrm -Cflags: -I${includedir} +Cflags: -I${includedir} -I${includedir}/libdrm -I${includedir}/drm EOF ;; gles) diff --git a/web-libs/kandelo-session/src/kernel-host.ts b/web-libs/kandelo-session/src/kernel-host.ts index 35d3985624..c94e5a4625 100644 --- a/web-libs/kandelo-session/src/kernel-host.ts +++ b/web-libs/kandelo-session/src/kernel-host.ts @@ -160,6 +160,20 @@ export interface KernelLike { * bit0=left, bit1=right, bit2=middle. */ injectMouseEvent?(dx: number, dy: number, buttons: number): void; + /** + * Inject one evdev record into `/dev/input/event{0,1}`. `device` is + * 0 for the keyboard, 1 for the pointer; the remaining fields mirror + * Linux `struct input_event` (`ev_type`, `code`, `value`). Used to + * feed absolute-position pointer events to programs that read evdev + * (e.g. SDL2's KMSDRM backend on `/dev/input/event1`) rather than + * the PS/2 `/dev/input/mice` node `injectMouseEvent` targets. + */ + injectInputEvent?( + device: 0 | 1, + ev_type: number, + code: number, + value: number, + ): void; /** * Hand an `OffscreenCanvas` to the kernel worker as the scanout * target for KMS CRTC `crtcId`. Optional `stats` SAB receives @@ -439,6 +453,17 @@ export interface KmsDisplayHandle { * positive Y up). `buttons` uses PS/2 bits: bit0=left, bit1=right, * bit2=middle. No-op when the wrapped kernel lacks mouse injection. */ sendMouseEvent(dx: number, dy: number, buttons: number): void; + /** Inject an absolute-position pointer update into evdev + * `/dev/input/event1`. `x`/`y` are framebuffer pixels (origin + * top-left, matching the canvas drawing buffer); `buttons` uses the + * same bitmask as `sendMouseEvent` (bit0=left, bit1=right, + * bit2=middle). This is the path SDL2's KMSDRM/evdev backend reads — + * `sendMouseEvent` only feeds the PS/2 `/dev/input/mice` node, which + * evdev consumers like SDL2 ignore. The handle tracks button state + * internally so callers can pass the full bitmask every call and + * only press/release transitions are emitted. No-op when the wrapped + * kernel lacks evdev injection. */ + sendPointerAbs(x: number, y: number, buttons: number): void; /** Detach the canvas. Subsequent vblank ticks no-op for this CRTC. */ close(): void; } @@ -2343,12 +2368,50 @@ export class LiveKernelHost implements KernelHost { const offscreen = canvas.transferControlToOffscreen(); this.kernel.kmsAttachCanvas(crtcId, offscreen, statsSab, opts); const kernel = this.kernel; + // evdev codes for the pointer path (struct input_event). + const EV_SYN = 0x00, EV_KEY = 0x01, EV_REL = 0x02; + const SYN_REPORT = 0x00, REL_X = 0x00, REL_Y = 0x01; + const BTN_LEFT = 0x110, BTN_RIGHT = 0x111, BTN_MIDDLE = 0x112; + // event1 advertises REL_X+REL_Y, so SDL's evdev backend classifies + // it as a *relative* mouse and ignores EV_ABS entirely + // (SDL_evdev.c: `relative_mouse = test_bit(REL_X) && test_bit(REL_Y)`, + // and the ABS_X handler only stores position when `!relative_mouse`). + // To position absolutely we peg the relative integrator into the + // top-left corner with an over-large negative delta — SDL clamps the + // result to the window — then move to the target. Emulating an + // absolute device through relative deltas this way is the only path + // that reaches SDL without re-spec'ing the kernel's input device. + const PEG = 4096; // larger than any framebuffer axis, so the clamp pegs to 0 + let prevButtons = 0; const handle: KmsDisplayHandle = { crtcId, stats, sendMouseEvent: (dx, dy, buttons) => { kernel.injectMouseEvent?.(dx, dy, buttons); }, + sendPointerAbs: (x, y, buttons) => { + const inject = kernel.injectInputEvent; + if (!inject) return; + const rx = Math.round(x), ry = Math.round(y); + // Frame 1: peg the relative cursor to (0,0). + inject.call(kernel, 1, EV_REL, REL_X, -PEG); + inject.call(kernel, 1, EV_REL, REL_Y, -PEG); + inject.call(kernel, 1, EV_SYN, SYN_REPORT, 0); + // Frame 2: move to the absolute target. SDL flushes motion on + // this SYN, so its cursor sits at (rx, ry) before any button. + inject.call(kernel, 1, EV_REL, REL_X, rx); + inject.call(kernel, 1, EV_REL, REL_Y, ry); + inject.call(kernel, 1, EV_SYN, SYN_REPORT, 0); + // Frame 3: button transitions — now they register at (rx, ry). + const changed = buttons ^ prevButtons; + if (changed) { + if (changed & 1) inject.call(kernel, 1, EV_KEY, BTN_LEFT, buttons & 1 ? 1 : 0); + if (changed & 2) inject.call(kernel, 1, EV_KEY, BTN_RIGHT, buttons & 2 ? 1 : 0); + if (changed & 4) inject.call(kernel, 1, EV_KEY, BTN_MIDDLE, buttons & 4 ? 1 : 0); + inject.call(kernel, 1, EV_SYN, SYN_REPORT, 0); + prevButtons = buttons; + } + }, close: () => { // The worker auto-stops the pump tick for unused CRTCs on the // next teardown; there's no explicit detach API yet. Closing