Skip to content

G5P (Apple A9): graphics pipelines and draws, the first triangle - #3

Open
brkosebay wants to merge 13 commits into
OrchardLinux:mainfrom
brkosebay:g5p-pr-tri
Open

brkosebay wants to merge 13 commits into
OrchardLinux:mainfrom
brkosebay:g5p-pr-tri

Conversation

@brkosebay

Copy link
Copy Markdown

Depends on #2; please review it after #2. It is stacked on #2's branch (g5p-pr1), and its 5 commits come after
#2's 8.

What this is. The first feature PR after #2, as agreed: G5P graphics pipelines compiled by our backend, and
non-indexed draws. Together with #2's render job, an A9 renders a cleared target with a triangle on it,
pixel-exact on hardware. It is built only with -Dimagination-g5p-experimental=true, and it edits no apple_g9p
path.

What it builds, and what it refuses by name (G5P refuses: ... on stderr and VK_ERROR_FEATURE_NOT_PRESENT), so
nothing unbuilt runs:

  • Built: a vertex and a fragment shader through our backend (SPIR-V → NIR → G5P USC), 32-bit float vertex
    attributes from one vertex buffer, triangle lists/strips/fans and line lists/strips, one instance, a dynamic or
    static viewport and scissor, culling, and varyings (the coefficient program).
  • Refused: more than one colour target, blending and write masks, depth/stencil, descriptors and push constants,
    indexed draws, instancing, a scissor smaller than the attachment, rasterizer discard, multiview, MSAA. These are
    the next PRs (the order is at the end).

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

Commits

  1. 1bb4e34b7dc pvr: apple_g5p: SPIR-V to NIR for the G5P backend
    • pvr_g5p_nir.c: a stage's SPIR-V becomes our backend's program the way our A9 driver has always prepared it.
      That means its NIR options and pass sequence (the backend's recognised forms depend on them), the point-size,
      unattached-output and sample-mask lowerings, and the vertex system values.
    • Then pco_g5p_compile_general_link(). The general backend only: no captured-shape matcher, no out-of-process
      fallback.
  2. 3b99268eda8 pvr: apple_g5p: graphics pipelines and draws: pvr_arch_g5p_gfx.c, in the shape of your
    pvr_arch_g9p_gfx.c (graphics_pipeline_compile, cmd_draw, emit_draw).
    • A pipeline compiles the fragment program first and links the vertex program to the varyings it reads. It
      uploads both USC programs and three pdsc programs in Apple's DOUT form: the vertex fetch, the fragment launch
      and the coefficient program. It also uploads its two static PPP blocks.
    • A draw records the vertex-fetch data, six per-draw PPP blocks and its VDM words (csbgen). The stream-out
      program is G5P (Apple A9) on the shared PowerVR driver, behind an experimental option #2's device HALT, as your stream_out is.
    • Also: a g5p pointer in pvr_graphics_pipeline, and pvr_pipeline.h includes pvr_g5p.h at its end (both
      #ifdef).
  3. aaebfeda7f0 pvr/winsys: apple_g5p: log the control stream and its PPP terminate: with PVR_G5P_WS_LOG, the
    submit also dumps the VDM stream, so a job's words can be compared offline.
  4. 176a6c781e9 pvr: apple_g5p: hook graphics pipelines and draws: five one-line hooks (below).
  5. 1a183f6a6b5 imagination/g5p: triangle, a hardware test of the draw path: the test program below, in
    src/imagination/g5p/tests/triangle/ (GLSL compiled by its build.sh, not built by meson).

Every edit outside the new files

New files: vulkan/pvr_g5p_nir.c, vulkan/pvr_arch_g5p_gfx.c. G5P files changed: pvr_g5p.h, pvr_arch_g5p.c (a
check that the HALT's code sits at data + 16, the layout the stream-out block points at), the winsys, and
pvr_g5p_hooks.h (four new hooks; load_op is #2's).

File Lines What and why
src/imagination/vulkan/meson.build +2 the two new files, in the G5P-only block
vulkan/pvr_pipeline.h +10 struct pvr_g5p_pipeline *g5p at the end of pvr_graphics_pipeline, and #include "pvr_g5p.h" at the end of the header, both #ifdef PVR_APPLE_G5P. The header's only __LINE__ users after them are two assert()s, which b_ndebug compiles out
vulkan/pvr_arch_pipeline.c +2 −2 hooks: pipeline_destroy (a G5P pipeline frees its programs and returns), pipeline_compile (a G5P pipeline is compiled by pvr_arch_g5p_graphics_pipeline_compile())
vulkan/pvr_arch_cmd_buffer.c +3 −3 hooks: draw (CmdDraw → pvr_arch_g5p_cmd_draw(), then CmdDraw's own end label), draw_indexed (refused by name), load_op in CmdNextSubpass2 (#2's hook: no soft load op; clears and loads are background objects, as on G9P)

Each of the five hooks takes the place of a blank line right before your G9P if, since vk_error() and friends
follow in both files. So the hook design is #2's:

  • PVR_G5P_HOOK(name, ...) is one line ≤ 80 columns and expands to nothing without the option. Every line keeps its
    number, and every byte of the option-off driver stays the same.
  • With the option, it expands to PVR_G5P_HOOK_<name> in pvr_g5p_hooks.h, which documents each one.
  • The draw hook reads CmdDraw's parameters (vertexCount, instanceCount, firstVertex, firstInstance) by
    name and jumps to the label it is given. Passing the four parameters would not fit one 80-column line.
  • No #line directives. The hook lines and the new macros are clang-format clean with src/imagination/.clang-format.

git diff aad20d6e7e7 176a6c781e9 -- src/imagination/g9p 'src/imagination/vulkan/*g9p*' is empty.

Gates

Same container and options as #2 (Fedora, GCC 16, aarch64, git archive into fixed paths; release, stripped,
b_ndebug). The gates ran on 3ea65ec6bcd, whose tree is identical to this branch's 176a6c781e9, commit by
commit.

  • Option off, byte for byte: PASS.
    • 3ea65ec6bcd (tree = 176a6c781e9) installs the same 165 files as your main (73a49abe247), with identical
      bytes. Commit 5 adds only the test program's files, which meson doesn't build.
    • cmp of the two libvulkan_powervr_mesa.so finds no difference:
      76e29856fe5011d2d63fd45756a6ed7fd9ad62de79a1a107df4d40c431cc7584 in both, the same hash as G5P (Apple A9) on the shared PowerVR driver, behind an experimental option #2's.
  • Option on, the offline smoke over our fake node (it runs our kernel's checks on every job; it does not render):
    • smoke, the clear-only render and the triangle are submitted and retired: G5P-PVR-SMOKE PASS,
      G5P-PVR-RENDER PASS, G5P-PVR-TRIANGLE PASS;
    • G5P (Apple A9) on the shared PowerVR driver, behind an experimental option #2's 15 render-job fields still equal our reference job: step6-compare: PASS (15 of 15 fields equal);
    • the draw's words equal our A9 driver's recorded job for the same scene: step7-compare: PASS (28 of 28 fields equal), against both recorded placements (general-triangle, p1-triangle):
      • the VDM stream: 30 words, addresses masked;
      • every program word: the vertex fetch, the fragment launch, the HALTs;
      • the eight PPP blocks;
      • the fetch data;
      • the size words;
      • 17 pointer relations, each address landing on its object;
      • the USC bytes: the vertex shader's 70 and the fragment shader's 32 are byte-identical.

No Apple binaries

  • No program is held as bytes. The USC programs come from our backend, from SPIR-V. The PDS programs come from pdsc:
    the vertex fetch (DDMAD, DOUTU, HALT), the fragment launch (DOUTU) and the coefficient program (DOUTI). The
    stream-out program is G5P (Apple A9) on the shared PowerVR driver, behind an experimental option #2's pdsc HALT.

  • Every constant initializer in the new and changed files was grepped (static const, array initializers, hex
    literals). The only hits:

    • three named register values (below);
    • zero-initialised work arrays;
    • a const float f[6] of the viewport's computed scale and offset;
    • a launch_data[4] holding the computed DOUTU word;
    • the dump labels.
  • What stays Apple-derived: four register values, named #defines in pvr_arch_g5p_gfx.c:

    Name Value What it is
    G5P_VS_STATE5 0x1082 the VDM_STATE5 base: PDS temps 2 (the data size and unified store are set per draw)
    G5P_VDM_CAM_SIZE 95 the vertex cache size for vertices of up to 32 dwords
    G5P_WCLAMP 0x1e3ce508 the W clamp of every captured draw (~1e-20f)
    G5P_STREAM_OUT1 0x4022 STREAM_OUT1, your G9P's 0x4022 too

    Everything else is packed by csbgen from field values: the ISP words, region clip, output select, PPP control and
    the index list.

Hardware result (iPad 5, A9, our kernel)

  • Flown on g5p-1b cb42bf093fe: this PR's code with the older, padded hook placement. Kernel
    7.3.0-rc1-release8-ga10a8556b98e, with Apple's unmodified GPU firmware, beside a live Hyprland session.
    • G5P-1B-DEVICE RESULT PASS vulkaninfo, smoke, 20 device cycles back to zero, clear-only render exact, triangle exact, ... gpu events 0.
    • The triangle: 1012 inside and 2796 outside exact, edge band 288 (140 red, 148 clear, 0 other). Every pixel
      at least 1 px from an edge is exact. The edge band holds only the two colours.
    • The words the device's driver used (its own dump): step7-compare 28 of 28. VDM 30 words; VS 70 B and FS 32 B
      byte-identical to the reference.
  • The hardware run was on cb42bf093fe, not on this branch. This PR's code differs from what flew only in where
    the hooks sit; the option-on smoke and step7-compare above (28 of 28, the same words) ran on this PR's tree itself.
    • git diff cb42bf093fe 176a6c781e9 over pvr_arch_g5p_gfx.c, pvr_g5p_nir.c, pvr_arch_g5p.c and
      src/imagination/g5p is empty.
    • In pvr_g5p.h, one prototype moved under its own heading (the commit split).
    • The winsys diff is G5P (Apple A9) on the shared PowerVR driver, behind an experimental option #2's own (it no longer checks the geometry stream).
    • The rest is hook placement: padded #ifdef blocks became PVR_G5P_HOOK lines, and their bodies moved into
      pvr_g5p_hooks.h unchanged.

Its hardware test program

triangle, in this PR at src/imagination/g5p/tests/triangle/ (commit 5). It is plain Vulkan with no driver
internals, so the same binary runs on your A10 against your G9P path:

src/imagination/g5p/tests/triangle/build.sh /tmp/triangle
/tmp/triangle/triangle --triangle --verify
  • It needs glslangValidator, a C compiler, and the Vulkan headers and loader. On the device it needs a
    host-visible memory type, a linear B8G8R8A8_UNORM colour attachment of 64×64, and a render pass object. The
    shaders are GLSL; no SPIR-V binary is committed.

  • What it does:

    1. It clears the image, which it pre-fills with 0x5a.
    2. It draws one red triangle from a vertex buffer, with a negative-height viewport.
    3. It checks every pixel at least 1 px from an edge: red inside, the clear colour outside. The edge band is
      reported apart and may hold only those two colours.

    The verdict is G5P-PVR-TRIANGLE PASS|FAIL.

  • Reference run on lavapipe (LLVM 22.1.8): G5P-PVR-TRIANGLE PASS, 1012 inside and 2796 outside exact, edge
    band 288 (140 red, 148 clear, 0 other). These are the same counts as the A9's.

  • --render --verify checks a clear-only pass. --load --verify (two passes, the second loading the first) is for
    the follow-up LOAD PR.

  • --bos PATH reads our kernel's BO accounting (debugfs). It's optional; leave it out on the A10.

After this PR

Each comes as its own PR on this one, with the same gates:

  • textures, samplers and blend (on hardware: 13 of 13 exact);
  • depth/stencil (on hardware: 5 of 5 rungs exact);
  • WSI (on hardware: 300 frames presented into Hyprland);
  • LOAD/STORE as a compiled background program (in progress), and uniforms;
  • 4× MSAA, MRT, transform feedback;
  • full vertex input (formats, instancing, topologies, indexed draws);
  • queries and copies.

🤖 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
A graphics stage's SPIR-V becomes the G5P backend's program as the A9
ICD prepares its stages: its NIR option set and pass sequence (the
backend's recognised forms depend on them), the point-size,
unattached-output and sample-mask lowerings and the vertex system values,
then pco_g5p_compile_general_link() (src/imagination/g5p). There is no
captured-shape matcher and no out-of-process fallback here: the general
backend only. Not reachable yet; the graphics pipelines call it.

Generated-by: LLM
Graphics on Apple's A9 (G5P) in the shape of Henrik Klee's G9P graphics
(pvr_arch_g9p_gfx.c: graphics_pipeline_compile, cmd_draw, emit_draw), not
reachable yet (the hooks follow):

- A pipeline compiles the fragment program first and links the vertex
  program to the varyings it reads (pvr_g5p_nir.c). It uploads both USC
  programs, the vertex-fetch PDS code, the fragment launch and the
  coefficient program (pdsc, Apple's DOUT form), and its two static PPP
  blocks (varying format; fragment pointers and sizes).
- A draw records the vertex-fetch data, the per-draw PPP blocks (output
  select, ISP, region clip and viewport, W clamp and PPP control, ISPDBSC,
  stream out) and its VDM words (csbgen), in the order the A9 ICD's
  recorder emits them. The stream-out program is the device HALT, as
  G9P's stream_out.
- What is not built yet is refused by name: one B8G8R8A8/R8G8B8A8 target,
  no blending, no depth/stencil, no descriptors or push constants, 32-bit
  float attributes from one vertex buffer, one instance, the scissor at
  the whole attachment, no indexed draws.
- The Apple-derived register values are named #defines (VDM_STATE5 base,
  cam size, W clamp, STREAM_OUT1); every program is generated.
- PVR_G5P_WS_LOG dumps each pipeline's programs and each draw's words.
- pvr_graphics_pipeline gains a g5p pointer, and pvr_pipeline.h includes
  pvr_g5p.h at its end, both under PVR_APPLE_G5P.

Generated-by: LLM
With PVR_G5P_WS_LOG, a render submit also dumps the whole VDM stream and
the PPP block of its tail (pvr's terminate: PPP_STATE, STREAM_TERMINATE),
so a draw's words can be compared offline.

Generated-by: LLM
Where G9P branches for its graphics, G5P branches first, in the shape of
Henrik Klee's G9P hooks: the pipeline compile and destroy call
pvr_arch_g5p_graphics_pipeline_{compile,destroy}(), vkCmdDraw calls
pvr_arch_g5p_cmd_draw(), vkCmdDrawIndexed is refused by name, and the
next subpass's load-op program is skipped (clears and loads are background
objects; the existing load_op hook).

Every hook is one line, PVR_G5P_HOOK(name, ...), in the place of a blank
line of the file, since vk_error() and friends after each one pass
__LINE__: a build without -Dimagination-g5p-experimental keeps every byte
of libvulkan_powervr_mesa.so. pvr_g5p_hooks.h gains the four new hooks.
Nothing on a G9P or Rogue path changes.

Generated-by: LLM
A plain Vulkan program that does not depend on the driver, so the same
test checks the A9 (G5P) and the A10 (G9P). --triangle --verify clears a
64x64 linear BGRA8 target and draws one red triangle from a vertex
buffer with a negative-height viewport, then checks every pixel at least
1 px from an edge: red inside, the clear colour outside, and only those
two colours in the edge band. --render checks a clear-only pass, --load
two passes where the second loads the first (for the LOAD follow-up).

The shaders are GLSL, compiled by build.sh with glslangValidator. No
SPIR-V binaries are committed. Not built by meson.

Checked on lavapipe (LLVM 22.1.8): G5P-PVR-TRIANGLE PASS, 1012 inside
and 2796 outside exact, edge band 288 (140 red, 148 clear, 0 other),
the same counts as on the A9; --render and --load PASS.

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