Документацію написано англійською.
Metal on the Adreno: the plan, and where it stands
Read docs/ru/metal.md (why Metal is not simply missing, and what is on each side of the wall) and docs/air.md (the
shader compiler) first. This file is the order of work, with how each step is checked and where.
The three parts
| Part | What it is | State |
|---|---|---|
| A. The shader compiler | Apple's shader bitcode (AIR, in a .metallib) into Mesa's NIR, so that ir3 (the compiler behind the OpenGL ES that already runs) makes Adreno code |
largely done: 321 of the system's 456 shader modules reach ir3, 54 have run on the phone's GPU (docs/air.md, docs/air-report.md) |
| B. The device | what Metal loads: a bundle with a subclass of Metal's private _MTLDevice, and the objects it hands out (buffers, libraries, pipeline states, queues, command buffers, encoders: 53 classes and about 1200 methods in Apple's own) |
step 1 ran in QEMU; buffers and libraries (step two, below) are written and checked on a Mac |
| C. The kernel side | submitting work to the GPU for a Metal client, with memory and fences | the GPU already has a user client for Mesa (drivers/gpu/hakios.gpu.cpp, HakiosGPUUserClient); it is not connected to B |
What "real Metal" would be, and why games are out of reach, is argued in docs/ru/metal.md section 9.4: that has not
changed. What this plan aims at is the part that is within reach: Metal clients of the system itself (the window
server's compositor, CoreImage, the camera's viewfinder) running on the Adreno through B and C, with shaders from A.
The order, and how each step is checked
| # | Step | Checked by | Where |
|---|---|---|---|
| 1 | A device that exists. metal=1 makes the (so far empty) IOAcceleratorES service name HakiosMetal; the bundle makes HakiosMetalDevice, a _MTLDevice subclass; MTLCreateSystemDefaultDevice() returns it |
metal_test (RC_JOBS=rc.jobs.metal) prints the device and HakiosMetal: lines say which messages Metal sent that the device did not answer |
QEMU, no GPU needed |
| 2 | Learn what Metal asks first, from step 1's "not implemented" lines: properties, family support, queue, buffer | the log, in order | QEMU |
| 3 | Buffers and textures over the GPU memory the kernel hands out (hakios.gpu.cpp: kMsmGemNew), IOSurface-backed |
a program writes a buffer and reads it back | phone |
| 4 | A library and a compute pipeline: newLibraryWithData: takes a .metallib, the function goes through A, the kernel runs |
the CPU reference in tools/air and then the phone |
phone |
| 5 | Command queue, command buffer, compute encoder, commit and wait, on fences | a compute kernel dispatched through the Metal API gives the numbers tools/air/cpuref.py expects |
phone |
| 6 | Render pipeline and render command encoder for the three vertex and 271 fragment shaders A already handles | a triangle through Metal | phone |
| 7 | Present: a drawable, and the frame reaching the screen without being dropped | the frame counters (below) | phone |
Without a phone, steps 1 and 2 are all that can be done and checked. Steps 3 to 7 each need the GPU.
Step 1: what is in the tree
xnu/files/iokit/Kernel/MiniIOMobileFramebuffer.cpp: with boot-argmetal=1theIOAcceleratorESservice gets the propertiesMetalPluginName=HakiosMetalandMetalPluginClassName=HakiosMetalDevice. Without the argument nothing changes: the properties stay off by default, on purpose. With them a program that asks for Metal gets a device, and CoreAnimation would pick Metal for the screen; until that device can draw, the screen without it is the working one.userland/src/HakiosMetal.m,userland/src/HakiosMetal.plist: the bundle, built into/redwood/lib/HakiosMetal.bundleand linked at/System/Library/Extensions/HakiosMetal.bundle(userland/build.sh).userland/src/metal_test.manduserland/rc.jobs.metal: the check that already existed.
What steps 1 and 2 found (QEMU, 27 October 2026, metal=1, a clean image)
Run in a loop of rebuild-image, boot, read the log; each pass gave the next requirement:
Metal found the service, loaded the bundle and made the class:
HakiosMetal: the class HakiosMetalDevice is registeredinbackboarddandmediaserverd, thena device for the accelerator port N (made):-initWithAcceleratorPort:was called and_MTLDevice's own-initreturned an object. So the discovery chain ofdocs/ru/metal.mdsection 1 is as described.MTLAddDevicethen asserted that the class conforms to the protocolMTLDevice, and, once it did, to the private protocolMTLDeviceSPI(neither is declared by_MTLDeviceitself, so the class declares them).With both declared, the first messages sent to the device, in this order:
-featureProfile,-maxBufferLength, and thenbackboarddtries to create a command queue ([Metal] Failed to create MTLCommandQueue) and exits. The window server restarts and fails the same way (launchd: "service has crashed N times in a row"). That is what a Metal device that exists and cannot do anything does to the system, and the reasonmetal=1is off by default.A command queue is now made (
HakiosMetalQueue, a subclass of_MTLCommandQueue; it queues nothing). Metal then sends it-setCompletionQueue:, which is not inMTLCommandQueuebut in the private protocolMTLCommandQueueSPI: found by taking every protocol of the process whose name begins withMTLCommandQueue. The window server then asks for-setGPUPriority:and-newDepthStencilStateWithDescriptor:.The objects a device hands out are stub classes made from a table in
HakiosMetal.m(depth-stencil state, sampler state, texture, render and compute pipeline states, event, fence): a subclass of Metal's private base where the system has one, the protocol family declared, every other message logged once and answered with zero. Textures and events have no private base class on this system (NSObject is used).The next wall, with those in place: CoreAnimation's Metal renderer stops with
Assertion failed: (metallib_url != nil), function MetalContext, file ogl-metal.mm, line 3308.It needs the URL of a.metallib(the system hasAppleMetalGLRenderer.bundle/default.metallib, 274 shader modules, which the AIR work translates) and will then ask the device for a library from it, which is where the shader compiler (part A) is first needed for real. Where CoreAnimation takes that URL from is not known yet; the log shows the device was asked for nothing before it.Who is shown the device, then, is a boot-arg level (the plug-in reads
sysctl kern.bootargs):metal=1only the programmetal_test,metal=2every process but the window server,metal=3every process. With the window server shown the device it stops on the missing metallib (QuartzCore.framework has adefault.pipelinelibthat names adefault.metallibwhich is not in this image), so it cannot run on Metal here, and it has to be left on its OpenGL ES path. Withmediaserverdshown it, it ends and starts again. Atmetal=1the window server, SpringBoard and mediaserverd were each logged as not shown the device and none of them ended in a QEMU run (the result ofmetal_testitself is not in this file yet).
So the road from here is the library (step 4 of the order below: newLibraryWithURL: / …Data: through the AIR compiler), then
buffers and textures with real memory, and the command encoders; none of that can be tried without the GPU.
Step two: buffers, libraries, functions (27 October 2026)
What the plug-in now makes, in userland/src/HakiosMetal.m:
- Buffers:
-newBufferWithLength:options:,-newBufferWithBytes:length:options:,-newBufferWithBytesNoCopy:length:options:deallocator:give aHakiosMetalBuffer(a subclass of_MTLResource, declaringMTLBuffer,MTLResourceand their SPI protocols) over memory of the process: zeroed pages, the caller's own for...NoCopy(its deallocator called when the buffer goes).-contents,-length, the storage, cache and hazard modes of its options,-allocatedSize, a label.-gpuAddressis 0: the GPU's own buffers (hakios.gpu.cpp,kMsmGemNew, mapped into the GPU's address space) come with the GPU, and since the Adreno shares the system's memory, a shared-storage buffer's bytes can stay where they are when they do. - Libraries:
-newLibraryWithURL:error:,...WithFile:,...WithData:,-newDefaultLibraryand...WithBundle:read the.metallibwithuserland/src/hakios_metallib.c(the reading oftools/metallib.pyin C, every offset checked; the file is the program's to give), and give aHakiosMetalLibrary(on_MTLLibrary) with-functionNames. A file that is not a metallib is anMTLLibraryErrorDomainerror that says why. - Functions:
-newFunctionWithName:gives aHakiosMetalFunction(on_MTLFunction) with its name, itsMTLFunctionTypefrom the container's TYPE tag, and-hakiosBitcode, its AIR module unwrapped: what the shader compiler (part A) takes. Function constants are accepted and said not to be applied.
How it was checked, and what that does not show:
tools/metallib-test.sh DIR: the C reading andtools/metallib.pyagree on every function of every.metallibof the iOS 16.5 system (the five MTLB files; the five runtime libraries ofAGXMetalA11.bundleare another format, and both refuse them) and of macOS's; a file made by the test and then cut short at every length, and with each offset and length bent outside it, is refused every time, under the address and undefined-behaviour sanitizers. One correction came out of it: the header's second word at 0x20 is the function list's length after its count, not its end (tools/metallib.pycalls itfuncs_endand does not use it).tools/metal-plugin-test.sh DIR: the plug-in built for macOS, its device made the way Metal makes it, on the Mac's own_MTLDevice,_MTLResource,_MTLLibrary,_MTLFunction: buffers written, read back, copied, refused overmaxBufferLength, freed (and the caller's deallocator called); the five iOS libraries read (36, 135, 11, 274 and 0 functions; the last is SafariSharedUI's, a header and nothing else), every function made by its name with its type, every module LLVM bitcode. All under the address sanitizer. macOS's Metal is not iOS 16.5's: this shows the plug-in's own logic, not that iOS's Metal is content with these objects.- Built for iOS 16.5 (
-Wall -Wextra, no warnings). Not yet run on iOS:metal_testasks "what the device makes" in a process of its own (RC_JOBS=rc.jobs.metal,metal=1), and its line is the result that goes here.
Still nothing is drawn or computed: a pipeline made of these functions is the empty stand-in of step one. The next step is the
compiler behind -newComputePipelineStateWithFunction:error: (part A: the function's bitcode through NIR to ir3), and then the
command buffer and compute encoder on the GPU's buffers (part C), which need the phone.
What UniXNU's AdrenoMetal showed, and what was taken
UniXNU runs iPadOS 17 on a Xiaomi Pad 6S Pro (SM8550, Adreno 740), and its AdrenoMetal (MIT) composes the system UI on the GPU. Read on 27 October 2026; no code of it is in this repository, the facts below are written here in our own words and code, with the source named where it is used.
- Their route is Metal on Vulkan: the plug-in's device is a subclass of
_MTLDevice(as ours), and every Metal object is translated by a runtime of theirs ("metal2vulkan",bridge/NativeMetalObjects.rs) onto Turnip, Mesa's Vulkan driver for Adreno, over the kernel's GPU interface. Ours is AIR through NIR to ir3 directly (part A), on the same Mesa. Turnip supports the A6xx generation our Adreno 642L belongs to, so their route is open to this port as well; which of the two is less work from here is the question of the next step, and it is not decided here. - Universal metallibs. Newer systems ship framework libraries as a fat container (0xCAFEBABE) of an AIR64 slice and a binary
for Apple's GPUs (QuartzCore's
default.metallibon iPadOS 17).hakios_metallib_thin()takes the AIR64 slice, as Metal does;tools/metallib-test.shandtools/metal-plugin-test.shcheck it. iOS 16.5's libraries are all thin. - CoreImage's limits.
CIMetalDeviceGetLimitsreadsdedicatedMemorySizeandsharedMemorySize, which_MTLDevicedoes not answer; without them everyCIContextthrows (iconservicesagent ended over symbol glyphs there). The device now answers them,isLowPower,isRemovable,isHeadless, andsupportsFeatureSet:(NO: the base reads Apple's tables). - Clients by a file. A file beside the bundle names the processes shown the device.
HakiosMetal.clientsdoes the same here, on top ofmetal=N. - The window server needs QuartzCore's own
default.metallib. Their system image has it; iOS 16.5's (this port's) does not, which is the wall of step 7 above. It is not something the plug-in can supply: the library is Apple's.
Frame skips
A dropped frame is not a Metal problem, and Metal would not cure it: the screen of this port is drawn today by
CoreAnimation's OpenGL ES renderer through Mesa (gpu=1), and its pace depends on the submit path, the fences and the
present. What to measure first, on the phone, is already counted: gGPUStat in drivers/gpu/hakios.gpu.cpp (submissions,
waits, what each cost, every five seconds). Whether the framebuffer stand-in (MiniIOMobileFramebuffer.cpp) counts
frames and drops is not known to me: it records when the first frame was shown and keeps a small copy of frames
(record()), and a counter of presented and dropped frames would be the first thing to add. A frame skip that is not seen
in those numbers is in the compositor, not the driver. This needs a stable GPU run on the phone first; the
gpu=1 hang being bisected (see docs/driver-review/README.md) comes before any of it.
What is not claimed
Nothing here draws. No Metal client has used this device for anything: the first one that tried (the window server) asked for a command queue and stopped. Everything above was seen in QEMU; none of it was run on the phone.