Skip to content

G5P (Apple A9) on the shared PowerVR driver, behind an experimental option - #2

Open
brkosebay wants to merge 8 commits into
OrchardLinux:mainfrom
brkosebay:g5p-pr1
Open

brkosebay wants to merge 8 commits into
OrchardLinux:mainfrom
brkosebay:g5p-pr1

Conversation

@brkosebay

Copy link
Copy Markdown

What this is. Your first-PR scope, plus the smallest bring-up that lets you check it runs:

  • the A9 compiler backend as src/imagination/g5p/, built only with -Dimagination-g5p-experimental=true
    (default off);
  • the A9 device-info entry, BVNC 9.15.6.2, with an apple_g5p feature bit;
  • the G5P paths on your pvr_* skeleton, in the shape of your G9P ones:
    • a winsys for our kernel's typed UAPI;
    • device programs from pdsc and our encoders;
    • a clear-only render job;
    • one-line hooks in the shared files.

It edits no apple_g9p path. If you want only the literal scope (backend and device info), commits 1–5 are that PR
on their own; 6–8 are the bring-up.

This is AI-written. Every commit carries Generated-by: LLM. It is for your fork only, not for upstream Mesa.

Your conditions, one by one

# Condition How this PR meets it Evidence
1 A real branch, a fork of OrchardLinux/mesa off main brkosebay/mesa:g5p-pr1, 8 commits on 73a49abe247 (your current main) the commit list below
2 src/imagination/g5p/ behind an experimental option, the A9 device-info entry with apple_g5p, no edits to the apple_g9p paths commits 1–2; every edit outside new files is listed below git diff main -- src/imagination/g9p src/imagination/vulkan/*g9p* is empty. No G9P line is changed or removed (the only removed lines are 6 blank lines, each replaced by a one-line hook)
3a Option off: byte-identical libvulkan_powervr_mesa.so the hook design below PASS: 165 installed files identical to main's; cmp of libvulkan_powervr_mesa.so identical
3b Your offline compiler corpus: same output no G9P compiler source changes, and option off is byte-identical, so the G9P compiler in it is too. Option on, libpowervr_g9p gets only an unused -DPVR_APPLE_G5P PASS by construction (no compiler source changed; see 3b). Your corpus run is welcome as a check
3c CI passes see "CI" PASS: option off and on, 26 of 26 tests, 0 failures
4 No Apple binaries: the 153 helper instructions and 14 PDS programs can't be in it none of them; no program is held as bytes anywhere "No Apple binaries" below
5 One topic per commit, each with Generated-by: LLM 8 commits, 8 topics the commit list
6 ES 3.0 work as separate PRs, each with its hardware test program the follow-up plan below —

Commits

  1. pvr: add the Apple A9 GPU (G5P) device info behind an experimental option
    • the option, common/device_info/apple-a9.h, the table row and BVNC case;
    • has_apple_g5p in pvr_device_features' trailing padding: a bit beside yours would move every later field.
  2. g5p: add the Apple A9 (G5P) shader backend beside g9p (Co-developed-by: Henrik Klee)
    • our backend, moved out of pco/ into its own directory and static library, as g9p/ holds yours;
    • pco/ is untouched;
    • its one whole program, the shape matcher's 12-byte varying fragment program, is built by our encoders now
      (the same 12 bytes, checked by a host test).
  3. pco/g5p: no program size limit of the backend's own: a growing buffer instead of 16 KiB.
  4. g5p: jumps up to 16 MiB, in Apple's form (Co-developed-by: Henrik Klee: your encoder).
  5. imagination/g5p: g5p_usc_task_end(): the last word of the TA's end-of-stream program.
  6. pvr/winsys: apple_g5p, a winsys over the Apple A9 kernel's typed UAPI
    • new files under winsys/apple_g5p/;
    • nothing selects it until commit 8.
  7. pvr: apple_g5p: the G5P device programs and its clear-only render job
    • pvr_arch_g5p.c, pvr_g5p.h, and a g5p member in pvr_device and pvr_render_job;
    • in your shape: pvr_arch_g9p_job_init(), pvr_arch_g9p_frag_stream().
  8. pvr: apple_g5p: hook G5P into the shared driver files: the hooks below.

Every edit outside the new files

New files: src/imagination/g5p/*, common/device_info/apple-a9.h, vulkan/pvr_arch_g5p.c, vulkan/pvr_g5p.h,
vulkan/pvr_g5p_hooks.h, vulkan/winsys/apple_g5p/*.

File Lines What and why
meson.options +7 the option, default off
src/imagination/meson.build +9 -DPVR_APPLE_G5P and subdir('g5p'), only with the option
src/imagination/vulkan/meson.build +15 G5P sources, includes and the backend library, only with the option
common/pvr_device_info.h +7 has_apple_g5p, in the trailing padding (no field moves)
common/pvr_device_info.c +9 the A9 include, table slot and BVNC case, #ifdef PVR_APPLE_G5P
vulkan/pvr_device.h +5 struct pvr_g5p_device *g5p, #ifdef
vulkan/pvr_job_render.h +8 #include "pvr_g5p.h" and struct pvr_g5p_job g5p, #ifdef
vulkan/winsys/pvr_winsys.h +6 at its end: the hooks header, and the G5P winsys header with the option
vulkan/pvr_instance.c +1 −1 hook: our kernel's DRM name is a PowerVR node
vulkan/winsys/pvr_winsys.c +1 −1 hook: that node gets the apple_g5p winsys
vulkan/pvr_arch_device.c +15 #ifdef blocks: G5P device init, and finish on both paths
vulkan/pvr_arch_cmd_buffer.c +4 −4 hooks: G5P job init, its background object, no start-of-render attachment clear and no soft load op (clears are background objects, as on G9P)
vulkan/pvr_arch_job_render.c +14 #ifdef blocks: the fragment stream is G5P's pass, no partial render

How option-off stays byte-identical (the hook design). Your shared files pass __LINE__ to vk_error() and
friends, so an inserted line before such a call changes your binary even inside #ifdef.

  • Where code with __LINE__ follows a hook, the hook is one line, PVR_G5P_HOOK(name, ...), and it takes the place
    of a blank line of your file. Without the option it expands to nothing, so every line keeps its number. With it,
    it expands to PVR_G5P_HOOK_<name> in pvr_g5p_hooks.h, which lists every hook and what it does. A hook ending
    in else is the G5P branch of the if/else chain after it, in front of your G9P branch.
  • In one place (CmdBeginRenderPass2) the hook must sit right before the if, so the blank line it replaces is the
    one after that block.
  • The remaining hooks are ordinary #ifdef PVR_APPLE_G5P blocks, each with no __LINE__ user after it in its file.
  • No #line directives: once merged, every line number is real, so a later edit of yours can't make one stale.
  • The hook lines are clang-format clean with your .clang-format. Formatting the files leaves them as they are.

Gates

All builds: one container (Fedora, GCC 16, aarch64), git archive of the commit into fixed paths. The release gate
uses buildtype=release, strip=true, b_ndebug=true, vulkan-drivers=imagination, gallium-drivers=zink, LLVM,
EGL/GBM/GLES2/OpenGL and Wayland.

3a, option off: PASS, byte for byte. g5p-pr1 and your main (73a49abe247) install the same
165 files with identical bytes, and cmp of the two libvulkan_powervr_mesa.so finds no difference:
76e29856fe5011d2d63fd45756a6ed7fd9ad62de79a1a107df4d40c431cc7584 in both. That is also the hash this check gave for
milestone 1's backend-only branch. With the one-line hooks, no padding or line tricks are needed: it is a plain
build of the branch.

3b, compiler output: PASS by construction. git diff main -- src/imagination/pco src/imagination/g9p src/compiler
is empty: no compiler source changes. Option off, the installed files, libvulkan_powervr_mesa.so with your compiler in
it included, are byte-identical to main's (3a). Option on, the only change to your compiler's build is an unused
-DPVR_APPLE_G5P on libpowervr_g9p. We don't have your offline corpus. If you run it on this branch, it should give
the same output byte for byte.

3c, CI: PASS. What your workflow would build, run in its place (see "CI" below): a fresh meson setup of a
git archive of this branch, -Dbuildtype=debugoptimized -Dvulkan-drivers=imagination -Dgallium-drivers= -Dtools=imagination -Dbuild-tests=true -Dplatforms=wayland -Dglx=disabled, then ninja and meson test, once with
the option off and once with it on:

  • option off: 878 of 878 build steps, meson test 26 OK, 0 failures;
  • option on: 888 of 888 build steps, meson test 26 OK, 0 failures (including pvr symbols check);
  • 5 compiler warnings in each build, none in a G5P file.

No Apple binaries

  • The 153 helper instructions and 14 PDS programs are not in this PR, nor is any other captured program.

    • On your skeleton, the context store and resume programs are a lone PDS HALT (pdsc), as on G9P. The A9 renders
      and retires such jobs on hardware (below).
    • The TA's end-of-stream program and every clear and end-of-tile program come from pdsc and our encoders.
    • The only program our backend held as bytes (12 bytes) is now built by the encoders too (commit 2).
  • Scans:

    • an array scan of every added or changed C file;
    • a privacy and Apple-material scan (fingerprints of the material we hold privately);
    • a hex-density scan of the whole diff.

    What they find is encoding knowledge, not programs: opcode and field tables of g5p_usc.c (the largest is
    lit3_seen, 168 bytes: the measured routings of one literal-operand form), the field maps of the immediate
    encoder, and the parameterised instruction templates of g5p_mrt_programs.h.

  • What stays Apple-derived: register values, not code. They are named #defines in pvr_arch_g5p.c:

    • the context store/resume size words 0x24001002, 0x00030005, 0x21001002, 0x00030021, 0x00004820,
      0x20000001;
    • the TA context size word 0x24000081;
    • restore 0x01800000;
    • the TA scratch pointer offsets +0x20/+0x24/+0x28/+0x2c/+0x6240/+0x6040 in a 32 KiB scratch;
    • the V1 scissor and depth-bias pool sizes 0x80000 and 0xc0000.

    They are words of the kernel's job contract (what our kernel validates and writes to the firmware's records),
    in the same role as your G9P register block's values. They carry no instructions, and they are named field by
    field.

  • apple-a9.h holds the BVNC and feature values, from the hardware and the kext, like your apple-a10.h.

Hardware results (iPad 5, A9, our kernel)

  • Bring-up, on g5p-1b (this PR's code before the hook rework). Beside a live Hyprland session:

    • vulkaninfo shows PowerVR Apple A9 on our kernel's real render node;
    • device create, map, read back and bind;
    • 20 device destroy/recreate cycles back to zero BOs and files;
    • two empty submits signalled through the kernel's syncobjs;
    • 0 GPU events, the session undisturbed.
  • Route (b), our question 1. The A9 renders and retires jobs whose context store/resume programs are a bare PDS
    HALT, with no prologue or epilogue and every other template segment zeroed. Two contexts rendered at once.

  • The first real job. A clear-only render pass through pvr_*, the G5P hooks, the generated programs and the
    kernel's TYPED_V3 pass. 4096 of 4096 pixels exact, 0 faults, on two kernels. Run 2 also used the HALT as the
    TA context program (commit 7 has it), so the job runs no Apple program at all.

  • This PR's exact build (the tree of this branch's tip, the hooks as in this PR; 2026-11-04, kernel 7.3.0-rc1-release8-ga10a8556b98e,
    Apple's unmodified GPU firmware), beside a live Hyprland session: PASS.

    • vulkaninfo, smoke, and 20 device cycles back to zero BOs and files;
    • null submit PASS;
    • the clear-only pass rendered, 4096 of 4096 pixels exact. The words the driver used equal our reference job
      in 15 of 15 fields, and its TA context program is the HALT;
    • 0 GPU events, no fault lines, the session undisturbed.

    The one BO left after the run is the kernel's own: it holds a closed render context's VM until another context's
    job moves its parameter manager past it. It is reported, not leaked.

CI

Your only workflow, .github/workflows/macos.yml (Mesa's GitHub mirror job), runs on macos-11, a runner image
GitHub has retired. All 7 runs on your main were cancelled after 24 hours without starting a step, so it would show
the same on any PR. What it would run, we ran in its place (3c above). Updating the runner image is a one-line change
in your workflow; we left it alone because it isn't ours to change.

After this PR: one PR per feature, each with its test program

Each follows the same rules (G5P files and one-line hooks, option-off identical, no Apple bytes) and comes with a
small Vulkan test program that checks its feature pixel-exactly. The test program is plain Vulkan, so the same binary
runs on your A10 to check that your path is unchanged, and on our A9 to check ours:

  1. Draws: graphics pipelines through our backend, vertex fetch, a triangle. Pixel-exact on the A9; its PR is ready,
    stacked on this one.
  2. LOAD/STORE (a compiled background load, not Apple's program) and uniforms.
  3. Textures, samplers, formats, blend (13 of 13 rungs pass on the A9; blends within 1).
  4. Depth/stencil (5 of 5 rungs pixel-exact on the A9) and 4× MSAA.
  5. MRT (2 and 4 targets).
  6. Transform feedback.
  7. Full vertex input (formats, instancing, topologies).
  8. Queries and copies.
  9. WSI for labwc and Hyprland (dma-buf, modifiers, present syncs).

The goal at the end is OpenGL ES 3.0 through Zink on the A9, and labwc and Hyprland running on this driver.

🤖 Generated with Claude Code

…tion

Add -Dimagination-g5p-experimental (default off) and, only when it is on,
a device-info entry for the Apple A9 GPU, BVNC 9.15.6.2 as its legacy
CORE_REVISION/CORE_ID registers decode, with a new feature bit
apple_g5p next to the A10's apple_g9p.

The bit sits in the trailing padding of pvr_device_features (after the
derived features), so no other field moves; the entry, its table slot and
its BVNC case are compiled only under PVR_APPLE_G5P, which meson defines
for src/imagination when the option is on. With the option off the build
is unchanged: same installed files, byte for byte.

Nothing selects the entry yet. It is there so that later steps can gate
shared pvr_* code on PVR_HAS_FEATURE(dev_info, apple_g5p) the way G9P does.
Values marked "A9" are from the hardware or Apple's drivers; the others
are the A10 entry's placeholders.

Generated-by: LLM
Add src/imagination/g5p: the compiler backend of our A9 driver, moved out
of src/imagination/pco (where our fork kept it as pco/*g5p*) into its own
directory and static library, the way src/imagination/g9p holds the A10's.
It is built only with -Dimagination-g5p-experimental=true, and nothing
links it yet: our A9 Vulkan driver stays out of this tree for now (see
README.md). pco/ is not touched: the two entry points our fork declared in
pco/pco.h move to g5p/pco_g5p.h.

The files are those of our fork's g5p-bench@b20fcfdcdb6, unchanged apart
from that include, the attribution lines below, and one program: the
varying fragment program of pco_g5p.c's shape matcher, which our fork held
as a 12-byte literal, is built by g5p_usc.h's encoders (iterator, wait,
emit, end), to the same 12 bytes. No program is held as bytes. On our
offline corpus (270 compiles) the output is byte-identical to that
branch's and to the device-proven g5p-perf60-hypr@bc82302f291.

Parts of the backend were ported from Henrik Klee's G9P compiler and
encoders (OrchardLinux/mesa src/imagination/g9p, MIT): discards in
blending programs to a select (7013b55, 7a64e1d), multiply-add
fusion (28cdfce), control flow on the execution-mask stack and its
encoders (d914276), and registers above r31 with their moves
(d1ab45d). His copyright lines are kept at those parts, without the
e-mail address; README.md lists them.

Co-developed-by: Henrik Klee
Generated-by: LLM
The general backend wrote into a fixed 16 KiB buffer and refused any
program past it ("program exceeds 16384 bytes"). That bound was the
buffer's, not the GPU's: Apple's A9 compiler emits straight-line fragment
programs from 17546 to 1258052 bytes, each with one end and no jump,
USC code is a 32-bit offset from usc_base (code_window 4 GiB), and no
program state word holds a size.

The buffer now starts at 16 KiB and doubles, and is kept across the
compile retries. PCO_G5P_PROGRAM_MAX (1 MiB) only stops runaway growth.
What really bounds a program is the job memory the driver places it in;
our A9 ICD (not part of this series) refuses a pipeline at creation when
its programs cannot fit one job. Programs that fit in 16 KiB are
unchanged, byte for byte.

On the A9 (iPad 5, GPU at 723 MHz), fragment programs of 21234, 102834
and 2 x 74034 bytes from this backend ran pixel-exact (32768 of 32768
pixels against a CPU reference), with no GPU fault.

Generated-by: LLM
The jump encoder (ported from G9P, d914276) refused offsets past
64 KiB: it put offset bit 24 in bit 0 of the third word, so it could not
place bit 16. Apple's A9 compiler keeps a long loop or if as one program
and encodes the jump with the third word = offset >> 16, bit 16 in bit 0,
bit 24 left out (bit 8 of the last word is the next-word flag), so offsets
reach 16 MiB. Six of its jumps over long bodies (35 to 196 KiB programs,
long loops and ifs, forward and backward) re-encode byte for byte, and
every decoded target is an instruction start. Within 64 KiB both forms
give the same bytes for every even offset, so no program that compiled
before changes.

On the A9 (iPad 5, GPU at 723 MHz), a 71110-byte fragment loop with a
uniform trip count, whose two jumps are +0x11384 and -0x113a2, ran
pixel-exact (32768 of 32768 pixels against a CPU reference), with no GPU
fault.

Co-developed-by: Henrik Klee
Generated-by: LLM
The TA program slot of a G5P render (GET_RENDER_INFO's TA program) runs a
USC program at the end of the geometry stream: a branch to the next 64-byte
block, padding, and an end of task. Henrik Klee's G9P builds the same
program for the same register on the A10 ("Register 0x470's program").
Add the generator for its last word, beside g5p_usc_branch() and
g5p_usc_special(). Nothing calls it yet.

Generated-by: LLM
The A9 GPU's kernel driver (DRM name "apple_g5p") has its own typed UAPI:
GEM objects, one VM per file, VM_BIND, GET_RENDER_INFO (the render layout:
code window, user VA, PM/parameter-buffer limits) and render jobs submitted
as typed passes (SUBMIT_RENDER_TYPED, TYPED_V3: the kernel validates every
program address against the job's resource list and the layout, and builds
the firmware records itself). Add winsys/apple_g5p: the UAPI header
(apple_g5p_drm.h, with the layout asserts the A9's passes were proven with),
its transport calls, and the pvr_winsys ops over it:

- memory: BOs are slices of one arena placed at the code base, inside the
  kernel's code window, so any slice is a valid program address in any heap;
  heaps are carved from it, and vma_map returns the slice's address;
- render contexts and render targets: the kernel's, created on first use;
- render submit: the TYPED_V3 pass in the fragment stream, the arena as the
  job's resources, the two stages' waits and one signal (the kernel takes one
  per job; the geometry stage's syncobj takes the same fence through
  SYNCOBJ_TRANSFER);
- null jobs: syncobj transfers, as the powervr winsys's;
- compute and transfer submits are refused (not in this series).

Nothing selects this winsys yet. It builds only with
-Dimagination-g5p-experimental=true.

Generated-by: LLM
A G5P render is one TA + 3D pass, which the A9 kernel takes as a TYPED_V3
pass. Add what builds it, in the shape of Henrik Klee's G9P
(pvr_arch_g9p_job_init(), pvr_arch_g9p_frag_stream()):

- device programs, uploaded once (pvr_arch_g5p_device_init()). Every PDS
  program is built by pdsc, with the DOUT form of the A9's PDS
  (PDSC_FEATURE_DOUT_EXT); every USC program by the G5P backend's
  generators (g5p_mrt_programs.h, g5p_usc.h):
  - the context store and resume programs are a lone PDS HALT, as on G9P;
  - the TA's end-of-stream program: a branch to the next 64-byte block,
    padding and an end of task, and its PDS kick (the same program G9P
    builds for the A10's register 0x470);
  - per colour-target count (1, 2, 4): the clear and end-of-tile USC
    programs, their PDS kicks and DOUTD code;
- pvr_arch_g5p_job_init(): a clear-only render of 1, 2 or 4 linear
  B8G8R8A8/R8G8B8A8 targets: the PBE words, the clear colours, the
  background registers and the end-of-tile words. Everything else (draws,
  depth/stencil, MSAA, LOAD, other formats) is refused by name;
- pvr_arch_g5p_frag_stream(): the TYPED_V3 pass. Its register words that are
  not programs (context and TA size words, restore, scratch offsets, V1 pool
  sizes) are named constants with the A9's values.

No captured program is used: the job runs the generated programs only.
pvr_device and pvr_render_job gain a g5p member, and pvr_job_render.h
includes pvr_g5p.h, under PVR_APPLE_G5P only. Nothing calls the new
functions yet.

Generated-by: LLM
Select G5P where the driver selects G9P, with a G5P branch in front of
each G9P one:

- pvr_instance.c, winsys/pvr_winsys.c: the A9 kernel's DRM name is a PowerVR
  node, served by the apple_g5p winsys;
- pvr_arch_device.c: device init and finish of the G5P device programs;
- pvr_arch_cmd_buffer.c: a G5P render job takes pvr_arch_g5p_job_init()'s
  background and end of tile; clears are background objects, so the
  start-of-render attachment clear and the "soft" background load op are
  skipped, as for G9P;
- pvr_arch_job_render.c: the fragment stream is pvr_arch_g5p_frag_stream()'s
  pass, without a partial render. The geometry stream is left as is; the
  apple_g5p winsys does not read it.

A build without -Dimagination-g5p-experimental keeps every byte of
libvulkan_powervr_mesa.so. Where code after a hook passes __LINE__
(vk_error() and friends), the hook is one line, PVR_G5P_HOOK(name, ...),
that takes the place of a blank line of the file and expands to nothing
without the option (pvr_g5p_hooks.h lists every one and what it expands
to). The other hooks are #ifdef PVR_APPLE_G5P blocks with no such code
after them. Nothing on a G9P or Rogue path changes.

Generated-by: LLM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant