tools: add the icon-pixels guest pixel test

The A16 showed partial System Settings icons while neither the plug-in
nor the kext refused anything, so the GPU ran the icon kernels and some
produced wrong or empty pixels; the encoding tests cannot see that.

build-icon-pixels.py builds and ad-hoc signs SnapMetal's icon-pixels
for arm64e and records its size, SHA-256 and CDHash (and the trust
row the guest would need if its AMFI refused an ad-hoc executable).

run-icon-pixels.py runs it in the guest from the root console shell:
pushes it with deploy-guest-plugin.py's checked chunk transfer, checks
that it executes, runs the synthetic CoreImage cases as root and the
system icons both in-process (root) and through NSWorkspace as the
console user (launchctl asuser + sudo -u), fetches every output file
three CRC-checked lines per command, optionally renames the icon
caches aside and restarts iconservicesd, iconservicesagent, the Dock
and System Settings (multi-user), collects the plug-in's per-process
and unified-log diagnostics, and compares. Offline it refuses the
steps that would act on this Mac's own processes.

compare-icon-pixels.py compares the guest's lines with this Mac's
within a tolerance and names the first matching explanation per case:
unwritten or partial output, readback or repeat differences, black or
transparent output, swapped red/blue, an extra or missing sRGB decode,
the missing or foreign input of a multi-input kernel (with its Metal
texture index), a broken blend band, otherwise the channels and tiles
that are off; for icons empty, black, partial, wrong colour or shape.
--census-only compares the dispatched kernels and formats of an
intercepted plug-in run.

icon-pixels-reference-synth.txt is this Mac's synthetic reference
(generated inputs only; M3 Max, 26A428, capabilities answered like the
deployed plug-in 378ff525). The icons reference summarises Apple's
icons and is not committed.

test-icon-pixels.py: the reference reproduces exactly on this Mac;
the plug-in device (commits intercepted) dispatches the same kernels
and formats with 8 CS sampler units and drops the multi-input ones
without; 20 injected faults are each diagnosed and a within-tolerance
perturbation passes; the whole console procedure runs over the
console stand-in with loss and noise and fetches lines identical to a
direct run.
This commit is contained in:
Acelogic committed 2026-10-24 19:31:58 -04:00
1 parent 2986fc3ba9
commit d43df34e40
5 files changed
+1008

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Build and ad-hoc sign the icon-pixels guest tool (arm64e), without running it in the guest.
The tool (gpu/adreno-kext/plugin/harness/icon-pixels*.m, SnapMetal) runs the CoreImage kernels
of the System Settings pane icons on the default Metal device and prints checksummed lines per case; see its header.
Built like the other guest probes (tools/build-adreno-clear-probe.py): xcrun clang -arch arm64e for macOS 27, ad-hoc
signature, architecture and signature checked. Writes OUTPUT/icon-pixels and OUTPUT/build.json (size, SHA-256, CDHash,
compiler, command lines, source SHA-256s), and prints the record. OUTPUT must not exist.
python3 tools/build-icon-pixels.py --output build/icon-pixels/tool-YYYYMMDD
The CDHash is the trust-cache row the guest needs only if its AMFI refuses an ad-hoc arm64e executable
(tools/run-icon-pixels.py checks that first and stops with this hint).
"""
from pathlib import Path
import argparse
import hashlib
import json
import re
import subprocess
ROOT = Path(__file__).resolve().parents[1]
HARNESS = ROOT / 'gpu/adreno-kext/plugin/harness'
SOURCES = [HARNESS / 'icon-pixels.m', HARNESS / 'icon-pixels-census.m', HARNESS / 'icon-pixels-icons.m',
HARNESS / 'icon-pixels.h']
FRAMEWORKS = ['Foundation', 'Metal', 'CoreImage', 'CoreGraphics', 'IOKit', 'AppKit', 'UniformTypeIdentifiers']
def sha(p):
return hashlib.sha256(Path(p).read_bytes()).hexdigest()
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--output', type=Path, required=True)
a = ap.parse_args()
out = a.output.resolve()
out.mkdir(parents=True, exist_ok=False)
binary = out / 'icon-pixels'
sources = {str(p.relative_to(ROOT)): sha(p) for p in SOURCES}
commands = []
def run(name, argv):
commands.append(argv)
r = subprocess.run(argv, cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(out / f'{name}.log').write_text(r.stdout)
if r.returncode:
raise SystemExit(f'{name} failed ({r.returncode}): {out / (name + ".log")}')
return r.stdout
compiler = run('compiler', ['xcrun', 'clang', '--version']).splitlines()[0]
argv = ['xcrun', 'clang', '-arch', 'arm64e', '-mmacosx-version-min=27.0', '-fobjc-arc', '-fobjc-arc-exceptions', '-O2',
'-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter',
*[str(p.relative_to(ROOT)) for p in SOURCES if p.suffix == '.m'], '-o', str(binary)]
for f in FRAMEWORKS:
argv += ['-framework', f]
# -x drops local symbols: a smaller binary is fewer console chunks to push.
run('build', argv + ['-lz', '-Wl,-dead_strip', '-Wl,-x'])
run('sign', ['codesign', '-f', '-s', '-', str(binary)])
run('verify-signature', ['codesign', '--verify', '--strict', '--verbose=2', str(binary)])
arch = run('architecture', ['lipo', '-archs', str(binary)]).strip()
if arch != 'arm64e':
raise SystemExit(f'architecture {arch}, not arm64e')
signing = run('signature', ['codesign', '-dvvv', str(binary)])
cdhash, = re.findall(r'^CDHash=([0-9a-f]+)$', signing, re.M)
version = run('version', [str(binary), 'version']).splitlines()[0]
if sources != {k: sha(ROOT / k) for k in sources}:
raise SystemExit('a source changed during the build')
record = dict(status='ICON_PIXELS_BUILT_NOT_RUN_ON_A16', binary=str(binary), bytes=binary.stat().st_size,
sha256=sha(binary), cdhash=cdhash, architecture=arch, compiler=compiler, host_version_line=version,
sources_sha256=sources, commands=commands,
trust_row=dict(name='icon-pixels', cdhash=cdhash, category=2, flags=0))
(out / 'build.json').write_text(json.dumps(record, indent=1) + '\n')
print(json.dumps({k: record[k] for k in ('status', 'binary', 'bytes', 'sha256', 'cdhash')}, indent=1))
if __name__ == '__main__':
main()
+312
View File
@@ -0,0 +1,312 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Compare icon-pixels output from the guest's GPU with this Mac's reference, case by case.
icon-pixels (SnapMetal harness/icon-pixels.m, built by tools/build-icon-pixels.py) prints one checksummed line per case.
This reads two such outputs (lines whose CRC does not match, such as console noise or a torn line, are dropped and
counted) and prints PASS or FAIL per case with what is off:
python3 tools/compare-icon-pixels.py REFERENCE.txt GUEST.txt [--tolerance 6] [--census-only] [--cases REGEX]
[--json OUT.json]
synth cases (C lines). A case passes when its status is ok, no destination pixel kept the sentinel, both readbacks and
all repeats agree, and every sampled value (9 points, 16 middle-row samples, 64 tile means, per-stripe means) is within
--tolerance (8-bit units) of the reference in every channel. A failing case names the first matching explanation:
ERROR / MISSING the tool raised or never printed the case
UNWRITTEN / PARTIAL destination pixels still hold the sentinel: the dispatch never ran, or ran in part
READBACK getBytes and a GPU blit of the same texture differ (CPU cache maintenance or coherency)
NONDETERMINISTIC repeats from identical inputs differ (synchronisation)
BLACK / TRANSPARENT the kernel read zeros (an unbound or null T#, a wrong T# slot, a wrong sampler)
SWAP-RB red and blue exchanged (the T# SWAP field: WZYX vs WXYZ)
SRGB-EXTRA / SRGB-MISSING colour channels look decoded twice / not decoded (the T# SRGB bit vs CoreImage's own)
INPUT-k multi-input cases: the stripe of input k (Metal texture index from the census) is wrong,
missing (transparent) or shows input j's content
CHANNEL otherwise: which channels are off, by how much, and where (tiles)
Icon cases (I lines): coverage, mean colour and the 8x8 alpha/luminance grids against the reference: EMPTY, BLACK,
PARTIAL (alpha cells lost), WRONG-COLOUR, SHAPE.
K lines (census) are compared too: a different kernel or texture format set is reported as a note, since the pixel
comparison is then of different work. --census-only compares only status, census and diagnostics (for an output of
the plug-in device with intercepted commits, whose pixels were never computed).
Exit status: 0 every case passes, 1 a case fails, 2 unusable input.
"""
import argparse
import json
from pathlib import Path
import re
import sys
import zlib
SENTINEL = '3ca55ac3'
def parse(path):
"""{'V': [...], 'C': {name: fields}, 'K': {name: line}, 'I': {...}, 'D': [...], 'E': [...], 'END': ..., 'noise': n}"""
out = dict(V=[], C={}, K={}, I={}, D=[], E=[], W=[], END=None, noise=0, lines=0)
for raw in Path(path).read_text(errors='replace').splitlines():
raw = raw.strip()
if not raw:
continue
body, sep, crc = raw.rpartition(' *')
if not sep or not re.fullmatch(r'[0-9a-f]{8}', crc) or f'{zlib.crc32(body.encode()) & 0xffffffff:08x}' != crc:
out['noise'] += 1
continue
out['lines'] += 1
kind = body.split(' ', 1)[0]
if kind in ('C', 'I'):
words = body.split(' ')
fields = dict(name=words[1], size=words[2])
for w in words[3:]:
k, eq, v = w.partition('=')
if eq:
fields[k] = v
out[kind][words[1]] = fields
elif kind == 'K':
words = body.split(' ', 2)
out['K'][words[1]] = words[2] if len(words) > 2 else ''
elif kind == 'END':
out['END'] = body
elif kind in out:
out[kind].append(body)
return out
def px(h):
return [int(h[i:i + 2], 16) for i in (0, 2, 4, 6)]
def values(f):
"""Every sampled RGBA value of a C line, labelled by where it came from."""
vals = []
for i, h in enumerate(f.get('p', '').split(',')):
if len(h) == 8:
vals.append((f'point{i}', px(h)))
for i, h in enumerate(f.get('g', '').split(',')):
if len(h) == 8:
vals.append((f'row{i}', px(h)))
t = f.get('t', '')
for i in range(len(t) // 8):
vals.append((f'tile{i // 8},{i % 8}', px(t[8 * i:8 * i + 8])))
return vals
def stripes(f):
s = f.get('s')
if not s:
return None, None
top, _, band = s.partition('/')
return [px(h) for h in top.split(',')], [px(h) for h in band.split(',')]
def to_linear(v):
c = v / 255.0
return 255.0 * (c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
def to_srgb(v):
c = v / 255.0
return 255.0 * (c * 12.92 if c <= 0.0031308 else 1.055 * c ** (1 / 2.4) - 0.055)
def close(a, b, tol, channels=range(4)):
return all(abs(a[c] - b[c]) <= tol for c in channels)
def explain_values(ref, got, tol):
"""The explanation for sampled values that differ, from the most specific pattern to the generic one."""
pairs = [(label, r, g) for (label, r), (_, g) in zip(ref, got)]
bad = [(label, r, g) for label, r, g in pairs if not close(r, g, tol)]
if not bad:
return None
if all(g == [0, 0, 0, 0] for _, _, g in bad):
return 'TRANSPARENT: the kernel wrote transparent black where the reference has content (null or wrong T#, sampler)'
if all(g[:3] == [0, 0, 0] for _, r, g in bad if max(r[:3]) > 24) and any(max(r[:3]) > 24 for _, r, _ in bad):
return 'BLACK: colour channels read zero (unbound or null T#, wrong T# slot, or wrong format)'
swapped = [(r, g) for _, r, g in pairs if abs(r[0] - r[2]) > 2 * tol]
if swapped and all(abs(g[0] - r[2]) <= tol and abs(g[2] - r[0]) <= tol and abs(g[1] - r[1]) <= tol for r, g in swapped):
return 'SWAP-RB: red and blue are exchanged (T# SWAP: WZYX for RGBA, WXYZ for BGRA)'
mid = [(r, g) for _, r, g in bad if any(40 <= r[c] <= 215 for c in range(3))]
def frac(fn):
hits = [all(abs(g[c] - fn(r[c])) <= tol + 2 for c in range(3) if 40 <= r[c] <= 215) for r, g in mid]
return sum(hits) / len(hits) if hits else 0.0
if mid and frac(to_linear) >= 0.8:
return 'SRGB-EXTRA: colour channels are darker as if sRGB-decoded once more (T# SRGB bit on top of the kernel\'s decode)'
if mid and frac(to_srgb) >= 0.8:
return 'SRGB-MISSING: colour channels are lighter as if never sRGB-decoded (T# SRGB bit absent)'
names = 'RGBA'
worst = [max(abs(r[c] - g[c]) for _, r, g in bad) for c in range(4)]
off = [names[c] for c in range(4) if worst[c] > tol]
zeroed = [names[c] for c in range(4) if all(g[c] == 0 for _, r, g in bad if r[c] > 24)
and any(r[c] > 24 for _, r, _ in bad)]
tiles = sorted({label for label, _, _ in bad if label.startswith('tile')})
where = ''
if tiles:
rows = sorted({int(t[4:].split(',')[0]) for t in tiles})
cols = sorted({int(t.split(',')[1]) for t in tiles})
where = f'; {len(tiles)}/64 tiles off (rows {rows[0]}-{rows[-1]}, cols {cols[0]}-{cols[-1]})'
first = bad[0]
detail = f'channels {"".join(off)} off (max delta ' + ','.join(str(w) for w in worst) + ')'
if zeroed:
detail += f'; {"".join(zeroed)} reads 0'
return f'CHANNEL: {detail}{where}; first at {first[0]}: reference {"".join(f"{x:02x}" for x in first[1])} guest ' \
f'{"".join(f"{x:02x}" for x in first[2])}'
def texture_index_of_inputs(census):
"""'1=rgba8s:i5,2=rgba8s:i4,...' -> {5: 1, 4: 2, ...} (input k -> Metal texture index)."""
out = {}
for m in re.finditer(r'(\d+)=[a-z0-9]+:i(\d+)', census or ''):
out.setdefault(int(m.group(2)), int(m.group(1)))
return out
def compare_synth(name, r, g, rk, gk, tol, census_only):
notes = []
if gk is not None and rk is not None and gk != rk:
notes.append(f'census differs: reference {rk[:160]} | guest {gk[:160]}')
if g is None:
return 'MISSING: the case printed no line (the tool stopped or the case was filtered)', notes
if g.get('st') != 'ok':
return f'ERROR: status {g.get("st")}', notes
if census_only:
if gk != rk:
return 'CENSUS: CoreImage dispatched other kernels or bound other texture formats', notes
return None, notes
w, _, h = g['size'].partition('x')
total = int(w) * int(h) if w.isdigit() and h.isdigit() else 0
sent = int(g.get('sent', '0'))
if total and sent >= total:
return 'UNWRITTEN: no destination pixel was written (dispatch dropped or refused, or the GPU never ran it)', notes
hashes = g.get('h', '').split(',')
if sent:
return f'PARTIAL: {sent} of {total} pixels still hold the sentinel (part of the dispatch never wrote)', notes
if g.get('hb') not in ('na', hashes[0]):
return 'READBACK: getBytes and the GPU blit of the same texture differ (CPU cache maintenance/coherency)', notes
if len(set(hashes)) > 1:
notes.append('repeats differ')
if r is None:
return 'MISSING-REFERENCE: the reference has no such case', notes
if r['size'] != g['size']:
return f'SIZE: {g["size"]} instead of {r["size"]}', notes
rt, rb = stripes(r)
gt, gb = stripes(g)
if rt and gt:
index = texture_index_of_inputs(rk)
for k, (want, got) in enumerate(zip(rt, gt)):
if close(want, got, tol):
continue
slot = f' (texture({index[k]}))' if k in index else ''
if got[3] <= tol:
return f'INPUT-{k}: stripe {k} is transparent: input {k}{slot} was not read', notes
other = [j for j, o in enumerate(rt) if j != k and close(o, got, tol)]
if other:
return f'INPUT-{k}: stripe {k} shows input {other[0]}\'s colour: input {k}{slot} reads another texture', notes
return f'INPUT-{k}: stripe {k} is {"".join(f"{x:02x}" for x in got)}, reference ' \
f'{"".join(f"{x:02x}" for x in want)} (input {k}{slot})', notes
for k, (want, got) in enumerate(zip(rb, gb)):
if not close(want, got, tol):
return f'BLEND: the half-transparent band under stripe {k} is {"".join(f"{x:02x}" for x in got)}, ' \
f'reference {"".join(f"{x:02x}" for x in want)} (blend order or premultiplication)', notes
why = explain_values(values(r), values(g), tol)
if why:
return why, notes
if len(set(hashes)) > 1:
return 'NONDETERMINISTIC: repeats from identical inputs differ (within tolerance of the reference at the samples)', notes
return None, notes
def grid(s):
return [int(c, 16) for c in s]
def compare_icon(name, r, g, rk, gk, tol):
notes = []
if g is None:
return 'MISSING: the icon printed no line', notes
if g.get('st') != 'ok':
return f'ERROR: status {g.get("st")}', notes
if r is None:
return 'MISSING-REFERENCE: the reference has no such icon', notes
if r.get('st') != 'ok':
return f'REFERENCE-ERROR: the reference status is {r.get("st")}', notes
rc, gc = int(r['cov']), int(g['cov'])
if gc == 0 and rc > 0:
return 'EMPTY: the icon has no visible pixel', notes
rrgb, grgb = px(r['rgb'] + 'ff')[:3], px(g['rgb'] + 'ff')[:3]
if max(grgb) < 0x10 and max(rrgb) >= 0x30:
return f'BLACK: covered pixels are black (mean {g["rgb"]}, reference {r["rgb"]})', notes
ra, ga = grid(r['ag']), grid(g['ag'])
lost = [i for i, (a, b) in enumerate(zip(ra, ga)) if a >= 3 and b <= a - 3]
if lost:
return f'PARTIAL: {len(lost)} of 64 alpha cells lost (first row {lost[0] // 8} col {lost[0] % 8}; ' \
f'coverage {gc} vs {rc} per mille)', notes
if abs(gc - rc) > 30:
return f'SHAPE: coverage {gc} vs reference {rc} per mille', notes
if any(abs(a - b) > 2 * tol for a, b in zip(rrgb, grgb)):
d = ','.join(f'{b - a:+d}' for a, b in zip(rrgb, grgb))
return f'WRONG-COLOUR: mean colour {g["rgb"]} vs reference {r["rgb"]} (R,G,B delta {d})', notes
rl, gl = grid(r['lg']), grid(g['lg'])
off = [i for i, (a, b) in enumerate(zip(rl, gl)) if abs(a - b) > 1]
if off or any(abs(a - b) > 1 for a, b in zip(ra, ga)):
return f'SHAPE: {len(off)} luminance cells differ by more than one level', notes
return None, notes
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('reference', type=Path)
ap.add_argument('guest', type=Path)
ap.add_argument('--tolerance', type=int, default=6, help='per-channel tolerance, 8-bit units (default 6)')
ap.add_argument('--census-only', action='store_true')
ap.add_argument('--cases', help='compare only the cases whose name matches this regular expression')
ap.add_argument('--json', type=Path)
a = ap.parse_args(argv)
ref, got = parse(a.reference), parse(a.guest)
if not ref['lines'] or not ref['V']:
print(f'unusable reference {a.reference}: {ref["lines"]} valid lines')
return 2
if a.cases:
keep = re.compile(a.cases)
for kind in ('C', 'I'):
for side in (ref, got):
side[kind] = {n: f for n, f in side[kind].items() if keep.search(n)}
rows = []
for name in list(ref['C']) + [n for n in got['C'] if n not in ref['C']]:
why, notes = compare_synth(name, ref['C'].get(name), got['C'].get(name), ref['K'].get(name), got['K'].get(name),
a.tolerance, a.census_only)
rows.append(dict(case=name, kind='synth', verdict='FAIL' if why else 'PASS', why=why, notes=notes))
for name in list(ref['I']) + [n for n in got['I'] if n not in ref['I']]:
why, notes = compare_icon(name, ref['I'].get(name), got['I'].get(name), ref['K'].get(name), got['K'].get(name),
a.tolerance)
rows.append(dict(case=name, kind='icon', verdict='FAIL' if why else 'PASS', why=why, notes=notes))
# The plug-in's own record of work it would not encode (dropped: lenient builds, refused: strict ones).
for d in got['D']:
m = re.search(r' dropped=(\d+) refused=(\d+)', d)
if m and (int(m.group(1)) or int(m.group(2))):
rows.append(dict(case='plugin-diagnostics', kind='diag', verdict='FAIL', notes=[],
why=f'the plug-in dropped {m.group(1)} and refused {m.group(2)} pieces of work in this process '
'(the D lines below name them)'))
for row in rows:
line = f'{row["verdict"]} {row["case"]}' + (f': {row["why"]}' if row['why'] else '')
if row['notes']:
line += ' [' + '; '.join(row['notes']) + ']'
print(line)
fails = [r for r in rows if r['verdict'] == 'FAIL']
print(f'guest: {got["V"][0] if got["V"] else "no V line"}')
print(f'guest lines {got["lines"]}, noise dropped {got["noise"]}, errors {len(got["E"])}, end {got["END"] or "absent"}')
for d in got['D'][:7]:
print(f'guest {d[:300]}')
for w in got['W']:
print(f'guest {w[:300]}')
print(f'{"PASS" if not fails else "FAIL"}: {len(rows) - len(fails)} of {len(rows)} cases pass'
+ (f'; failing: {", ".join(r["case"] for r in fails[:20])}' if fails else ''))
if a.json:
a.json.write_text(json.dumps(dict(rows=rows, guest_v=got['V'], guest_d=got['D'], guest_end=got['END'],
noise=got['noise']), indent=1) + '\n')
if not got['V'] or not rows:
return 2
return 1 if fails else 0
if __name__ == '__main__':
sys.exit(main())
+79
View File
@@ -0,0 +1,79 @@
V icon-pixels 1 mode=host-caps device=Apple_M3_Max os=26A428 pid=2871 repeat=2 cs=- plugin=local:AdrenoMetal.bundle@378ff525b00426d1 *57bb7186
C tex-rgba8s-lin 128x128 st=ok in=rgba8s h=b42791ae5393cd61,b42791ae5393cd61 hb=b42791ae5393cd61 sent=0 p=00000dff,f80086ff,00f886ff,f8f80dff,36363bff,0d0d3bff,850d3bff,0d853bff,85853bff g=01362bff,02362bff,053668ff,0a3668ff,10362bff,18362bff,233668ff,2f3668ff,3e362bff,4f362bff,633668ff,793668ff,92362bff,ad362bff,cc3668ff,ed3668ff t=010111ff070180ff140113ff280180ff450113ff6c0180ff9e0113ffda0182ff010780ff070714ff14077fff280714ff45077fff6c0714ff9e077fffda0713ff011413ff07147fff141414ff28147fff451414ff6c147fff9e1414ffda1480ff012880ff072814ff14287fff282814ff45287fff6c2814ff9e287fffda2813ff014513ff07457fff144514ff28457fff454514ff6c457fff9e4514ffda4580ff016c80ff076c14ff146c7fff286c14ff456c7fff6c6c14ff9e6c7fffda6c13ff019e13ff079e7fff149e14ff289e7fff459e14ff6c9e7fff9e9e14ffda9e80ff01da82ff07da13ff14da80ff28da13ff45da80ff6cda13ff9eda80ffdada11ff ms=120 *c2f928ab
K tex-rgba8s-lin n=2 d=0 ci_affine[0=bgra8:o,1=rgba8s:i0,s0]*2 *b29b244c
C tex-rgba8s-near 128x128 st=ok in=rgba8s h=18fd86522e0c0a7d,18fd86522e0c0a7d hb=18fd86522e0c0a7d sent=0 p=00000dff,f80086ff,00f886ff,f8f80dff,37370dff,0d0d0dff,860d0dff,0d860dff,86860dff g=01370dff,02370dff,053786ff,0a3786ff,11370dff,19370dff,233786ff,303786ff,3f370dff,50370dff,643786ff,7a3786ff,93370dff,af370dff,ce3786ff,ef3786ff t=01010dff070186ff14010dff280186ff46010dff6c0186ff9e010dffdb0186ff010786ff07070dff140786ff28070dff460786ff6c070dff9e0786ffdb070dff01140dff071486ff14140dff281486ff46140dff6c1486ff9e140dffdb1486ff012886ff07280dff142886ff28280dff462886ff6c280dff9e2886ffdb280dff01460dff074686ff14460dff284686ff46460dff6c4686ff9e460dffdb4686ff016c86ff076c0dff146c86ff286c0dff466c86ff6c6c0dff9e6c86ffdb6c0dff019e0dff079e86ff149e0dff289e86ff469e0dff6c9e86ff9e9e0dffdb9e86ff01db86ff07db0dff14db86ff28db0dff46db86ff6cdb0dff9edb86ffdbdb0dff ms=3 *c0b0203f
K tex-rgba8s-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=rgba8s:i0,s0]*2 *175e24da
C tex-bgra8s-lin 128x128 st=ok in=bgra8s h=b42791ae5393cd61,b42791ae5393cd61 hb=b42791ae5393cd61 sent=0 p=00000dff,f80086ff,00f886ff,f8f80dff,36363bff,0d0d3bff,850d3bff,0d853bff,85853bff g=01362bff,02362bff,053668ff,0a3668ff,10362bff,18362bff,233668ff,2f3668ff,3e362bff,4f362bff,633668ff,793668ff,92362bff,ad362bff,cc3668ff,ed3668ff t=010111ff070180ff140113ff280180ff450113ff6c0180ff9e0113ffda0182ff010780ff070714ff14077fff280714ff45077fff6c0714ff9e077fffda0713ff011413ff07147fff141414ff28147fff451414ff6c147fff9e1414ffda1480ff012880ff072814ff14287fff282814ff45287fff6c2814ff9e287fffda2813ff014513ff07457fff144514ff28457fff454514ff6c457fff9e4514ffda4580ff016c80ff076c14ff146c7fff286c14ff456c7fff6c6c14ff9e6c7fffda6c13ff019e13ff079e7fff149e14ff289e7fff459e14ff6c9e7fff9e9e14ffda9e80ff01da82ff07da13ff14da80ff28da13ff45da80ff6cda13ff9eda80ffdada11ff ms=1 *14caf135
K tex-bgra8s-lin n=2 d=0 ci_affine[0=bgra8:o,1=bgra8s:i0,s0]*2 *5a84bd88
C tex-bgra8s-near 128x128 st=ok in=bgra8s h=18fd86522e0c0a7d,18fd86522e0c0a7d hb=18fd86522e0c0a7d sent=0 p=00000dff,f80086ff,00f886ff,f8f80dff,37370dff,0d0d0dff,860d0dff,0d860dff,86860dff g=01370dff,02370dff,053786ff,0a3786ff,11370dff,19370dff,233786ff,303786ff,3f370dff,50370dff,643786ff,7a3786ff,93370dff,af370dff,ce3786ff,ef3786ff t=01010dff070186ff14010dff280186ff46010dff6c0186ff9e010dffdb0186ff010786ff07070dff140786ff28070dff460786ff6c070dff9e0786ffdb070dff01140dff071486ff14140dff281486ff46140dff6c1486ff9e140dffdb1486ff012886ff07280dff142886ff28280dff462886ff6c280dff9e2886ffdb280dff01460dff074686ff14460dff284686ff46460dff6c4686ff9e460dffdb4686ff016c86ff076c0dff146c86ff286c0dff466c86ff6c6c0dff9e6c86ffdb6c0dff019e0dff079e86ff149e0dff289e86ff469e0dff6c9e86ff9e9e0dffdb9e86ff01db86ff07db0dff14db86ff28db0dff46db86ff6cdb0dff9edb86ffdbdb0dff ms=2 *7880f28f
K tex-bgra8s-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=bgra8s:i0,s0]*2 *8fc2a065
C tex-rgba8-lin 128x128 st=ok in=rgba8 h=c89ba91151c892b9,c89ba91151c892b9 hb=c89ba91151c892b9 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,7f7f70ff,3f3f70ff,bf3f70ff,3fbf70ff,bfbf70ff g=077f60ff,177f60ff,277fa0ff,377fa0ff,477f60ff,577f60ff,677fa0ff,777fa0ff,877f60ff,977f60ff,a77fa0ff,b77fa0ff,c77f60ff,d77f60ff,e77fa0ff,f77fa0ff t=0e0e44ff2e0ebaff4e0e46ff6e0ebaff8e0e46ffae0ebaffce0e46ffee0ebcff0e2ebaff2e2e48ff4e2eb8ff6e2e48ff8e2eb8ffae2e48ffce2eb8ffee2e46ff0e4e46ff2e4eb8ff4e4e48ff6e4eb8ff8e4e48ffae4eb8ffce4e48ffee4ebaff0e6ebaff2e6e48ff4e6eb8ff6e6e48ff8e6eb8ffae6e48ffce6eb8ffee6e46ff0e8e46ff2e8eb8ff4e8e48ff6e8eb8ff8e8e48ffae8eb8ffce8e48ffee8ebaff0eaebaff2eae48ff4eaeb8ff6eae48ff8eaeb8ffaeae48ffceaeb8ffeeae46ff0ece46ff2eceb8ff4ece48ff6eceb8ff8ece48ffaeceb8ffcece48ffeecebaff0eeebcff2eee46ff4eeebaff6eee46ff8eeebaffaeee46ffceeebaffeeee44ff ms=2 *0db0258a
K tex-rgba8-lin n=2 d=0 ci_affine[0=bgra8:o,1=rgba8:i0,s0]*2 *b24b21b5
C tex-rgba8-near 128x128 st=ok in=rgba8 h=93f58ae72abcc325,93f58ae72abcc325 hb=93f58ae72abcc325 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,808040ff,404040ff,c04040ff,40c040ff,c0c040ff g=088040ff,188040ff,2880c0ff,3880c0ff,488040ff,588040ff,6880c0ff,7880c0ff,888040ff,988040ff,a880c0ff,b880c0ff,c88040ff,d88040ff,e880c0ff,f880c0ff t=0e0e40ff2e0ec0ff4e0e40ff6e0ec0ff8e0e40ffae0ec0ffce0e40ffee0ec0ff0e2ec0ff2e2e40ff4e2ec0ff6e2e40ff8e2ec0ffae2e40ffce2ec0ffee2e40ff0e4e40ff2e4ec0ff4e4e40ff6e4ec0ff8e4e40ffae4ec0ffce4e40ffee4ec0ff0e6ec0ff2e6e40ff4e6ec0ff6e6e40ff8e6ec0ffae6e40ffce6ec0ffee6e40ff0e8e40ff2e8ec0ff4e8e40ff6e8ec0ff8e8e40ffae8ec0ffce8e40ffee8ec0ff0eaec0ff2eae40ff4eaec0ff6eae40ff8eaec0ffaeae40ffceaec0ffeeae40ff0ece40ff2ecec0ff4ece40ff6ecec0ff8ece40ffaecec0ffcece40ffeecec0ff0eeec0ff2eee40ff4eeec0ff6eee40ff8eeec0ffaeee40ffceeec0ffeeee40ff ms=2 *8c9b9633
K tex-rgba8-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=rgba8:i0,s0]*2 *1b963012
C tex-bgra8-lin 128x128 st=ok in=bgra8 h=c89ba91151c892b9,c89ba91151c892b9 hb=c89ba91151c892b9 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,7f7f70ff,3f3f70ff,bf3f70ff,3fbf70ff,bfbf70ff g=077f60ff,177f60ff,277fa0ff,377fa0ff,477f60ff,577f60ff,677fa0ff,777fa0ff,877f60ff,977f60ff,a77fa0ff,b77fa0ff,c77f60ff,d77f60ff,e77fa0ff,f77fa0ff t=0e0e44ff2e0ebaff4e0e46ff6e0ebaff8e0e46ffae0ebaffce0e46ffee0ebcff0e2ebaff2e2e48ff4e2eb8ff6e2e48ff8e2eb8ffae2e48ffce2eb8ffee2e46ff0e4e46ff2e4eb8ff4e4e48ff6e4eb8ff8e4e48ffae4eb8ffce4e48ffee4ebaff0e6ebaff2e6e48ff4e6eb8ff6e6e48ff8e6eb8ffae6e48ffce6eb8ffee6e46ff0e8e46ff2e8eb8ff4e8e48ff6e8eb8ff8e8e48ffae8eb8ffce8e48ffee8ebaff0eaebaff2eae48ff4eaeb8ff6eae48ff8eaeb8ffaeae48ffceaeb8ffeeae46ff0ece46ff2eceb8ff4ece48ff6eceb8ff8ece48ffaeceb8ffcece48ffeecebaff0eeebcff2eee46ff4eeebaff6eee46ff8eeebaffaeee46ffceeebaffeeee44ff ms=2 *25d45f8a
K tex-bgra8-lin n=2 d=0 ci_affine[0=bgra8:o,1=bgra8:i0,s0]*2 *e8c1b4d0
C tex-bgra8-near 128x128 st=ok in=bgra8 h=93f58ae72abcc325,93f58ae72abcc325 hb=93f58ae72abcc325 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,808040ff,404040ff,c04040ff,40c040ff,c0c040ff g=088040ff,188040ff,2880c0ff,3880c0ff,488040ff,588040ff,6880c0ff,7880c0ff,888040ff,988040ff,a880c0ff,b880c0ff,c88040ff,d88040ff,e880c0ff,f880c0ff t=0e0e40ff2e0ec0ff4e0e40ff6e0ec0ff8e0e40ffae0ec0ffce0e40ffee0ec0ff0e2ec0ff2e2e40ff4e2ec0ff6e2e40ff8e2ec0ffae2e40ffce2ec0ffee2e40ff0e4e40ff2e4ec0ff4e4e40ff6e4ec0ff8e4e40ffae4ec0ffce4e40ffee4ec0ff0e6ec0ff2e6e40ff4e6ec0ff6e6e40ff8e6ec0ffae6e40ffce6ec0ffee6e40ff0e8e40ff2e8ec0ff4e8e40ff6e8ec0ff8e8e40ffae8ec0ffce8e40ffee8ec0ff0eaec0ff2eae40ff4eaec0ff6eae40ff8eaec0ffaeae40ffceaec0ffeeae40ff0ece40ff2ecec0ff4ece40ff6ecec0ff8ece40ffaecec0ffcece40ffeecec0ff0eeec0ff2eee40ff4eeec0ff6eee40ff8eeec0ffaeee40ffceeec0ffeeee40ff ms=2 *13ecd36a
K tex-bgra8-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=bgra8:i0,s0]*2 *cb4d9571
C tex-rg8-lin 128x128 st=ok in=rg8 h=b31b3210953ea559,b31b3210953ea559 hb=b31b3210953ea559 sent=0 p=000000ff,fc0000ff,00fc00ff,fcfc00ff,7f7f00ff,3f3f00ff,bf3f00ff,3fbf00ff,bfbf00ff g=077f00ff,177f00ff,277f00ff,377f00ff,477f00ff,577f00ff,677f00ff,777f00ff,877f00ff,977f00ff,a77f00ff,b77f00ff,c77f00ff,d77f00ff,e77f00ff,f77f00ff t=0e0e00ff2e0e00ff4e0e00ff6e0e00ff8e0e00ffae0e00ffce0e00ffee0e00ff0e2e00ff2e2e00ff4e2e00ff6e2e00ff8e2e00ffae2e00ffce2e00ffee2e00ff0e4e00ff2e4e00ff4e4e00ff6e4e00ff8e4e00ffae4e00ffce4e00ffee4e00ff0e6e00ff2e6e00ff4e6e00ff6e6e00ff8e6e00ffae6e00ffce6e00ffee6e00ff0e8e00ff2e8e00ff4e8e00ff6e8e00ff8e8e00ffae8e00ffce8e00ffee8e00ff0eae00ff2eae00ff4eae00ff6eae00ff8eae00ffaeae00ffceae00ffeeae00ff0ece00ff2ece00ff4ece00ff6ece00ff8ece00ffaece00ffcece00ffeece00ff0eee00ff2eee00ff4eee00ff6eee00ff8eee00ffaeee00ffceee00ffeeee00ff ms=2 *21f1215a
K tex-rg8-lin n=2 d=0 ci_affine[0=bgra8:o,1=rg8:i0,s0]*2 *715936a4
C tex-rg8-near 128x128 st=ok in=rg8 h=ea80530e6ae1d325,ea80530e6ae1d325 hb=ea80530e6ae1d325 sent=0 p=000000ff,fc0000ff,00fc00ff,fcfc00ff,808000ff,404000ff,c04000ff,40c000ff,c0c000ff g=088000ff,188000ff,288000ff,388000ff,488000ff,588000ff,688000ff,788000ff,888000ff,988000ff,a88000ff,b88000ff,c88000ff,d88000ff,e88000ff,f88000ff t=0e0e00ff2e0e00ff4e0e00ff6e0e00ff8e0e00ffae0e00ffce0e00ffee0e00ff0e2e00ff2e2e00ff4e2e00ff6e2e00ff8e2e00ffae2e00ffce2e00ffee2e00ff0e4e00ff2e4e00ff4e4e00ff6e4e00ff8e4e00ffae4e00ffce4e00ffee4e00ff0e6e00ff2e6e00ff4e6e00ff6e6e00ff8e6e00ffae6e00ffce6e00ffee6e00ff0e8e00ff2e8e00ff4e8e00ff6e8e00ff8e8e00ffae8e00ffce8e00ffee8e00ff0eae00ff2eae00ff4eae00ff6eae00ff8eae00ffaeae00ffceae00ffeeae00ff0ece00ff2ece00ff4ece00ff6ece00ff8ece00ffaece00ffcece00ffeece00ff0eee00ff2eee00ff4eee00ff6eee00ff8eee00ffaeee00ffceee00ffeeee00ff ms=2 *0cde3fc2
K tex-rg8-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=rg8:i0,s0]*2 *4102bcce
C tex-rg16f-lin 128x128 st=ok in=rg16f h=8d61eb2aececb90d,8d61eb2aececb90d hb=8d61eb2aececb90d sent=0 p=000000ff,fe0000ff,00fe00ff,fefe00ff,bbbb00ff,888800ff,e08800ff,88e000ff,e0e000ff g=2ebb00ff,55bb00ff,6dbb00ff,80bb00ff,90bb00ff,9ebb00ff,aabb00ff,b6bb00ff,c0bb00ff,cabb00ff,d3bb00ff,dcbb00ff,e5bb00ff,edbb00ff,f4bb00ff,fbbb00ff t=3c3c00ff753c00ff963c00ffaf3c00ffc53c00ffd73c00ffe83c00fff73c00ff3c7500ff757500ff967500ffaf7500ffc57500ffd77500ffe87500fff77500ff3c9600ff759600ff969600ffaf9600ffc59600ffd79600ffe89600fff79600ff3caf00ff75af00ff96af00ffafaf00ffc5af00ffd7af00ffe8af00fff7af00ff3cc500ff75c500ff96c500ffafc500ffc5c500ffd7c500ffe8c500fff7c500ff3cd700ff75d700ff96d700ffafd700ffc5d700ffd7d700ffe8d700fff7d700ff3ce800ff75e800ff96e800ffafe800ffc5e800ffd7e800ffe8e800fff7e800ff3cf700ff75f700ff96f700ffaff700ffc5f700ffd7f700ffe8f700fff7f700ff ms=3 *a0d34c03
K tex-rg16f-lin n=2 d=0 ci_affine_linear_to_srgb[0=bgra8:o,1=rg16f:i0,s0]*2 *d8fd156b
C tex-rg16f-near 128x128 st=ok in=rg16f h=98714485f6035aed,98714485f6035aed hb=98714485f6035aed sent=0 p=000000ff,fe0000ff,00fe00ff,fefe00ff,bcbc00ff,898900ff,e18900ff,89e100ff,e1e100ff g=32bc00ff,56bc00ff,6ebc00ff,81bc00ff,91bc00ff,9fbc00ff,abbc00ff,b7bc00ff,c1bc00ff,cbbc00ff,d4bc00ff,ddbc00ff,e5bc00ff,edbc00ff,f5bc00ff,fcbc00ff t=3b3b00ff753b00ff963b00ffaf3b00ffc53b00ffd83b00ffe83b00fff73b00ff3b7500ff757500ff967500ffaf7500ffc57500ffd87500ffe87500fff77500ff3b9600ff759600ff969600ffaf9600ffc59600ffd89600ffe89600fff79600ff3baf00ff75af00ff96af00ffafaf00ffc5af00ffd8af00ffe8af00fff7af00ff3bc500ff75c500ff96c500ffafc500ffc5c500ffd8c500ffe8c500fff7c500ff3bd800ff75d800ff96d800ffafd800ffc5d800ffd8d800ffe8d800fff7d800ff3be800ff75e800ff96e800ffafe800ffc5e800ffd8e800ffe8e800fff7e800ff3bf700ff75f700ff96f700ffaff700ffc5f700ffd8f700ffe8f700fff7f700ff ms=3 *22f3a87f
K tex-rg16f-near n=2 d=0 ci_affine_nearest_linear_to_srgb[0=bgra8:o,1=rg16f:i0,s0]*2 *99d83cef
C tex-r8-lin 128x128 st=ok in=r8 h=d830594d4eb56325,d830594d4eb56325 hb=d830594d4eb56325 sent=0 p=000000ff,fc0000ff,000000ff,fc0000ff,7f0000ff,3f0000ff,bf0000ff,3f0000ff,bf0000ff g=070000ff,170000ff,270000ff,370000ff,470000ff,570000ff,670000ff,770000ff,870000ff,970000ff,a70000ff,b70000ff,c70000ff,d70000ff,e70000ff,f70000ff t=0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff ms=2 *9ad3db8f
K tex-r8-lin n=2 d=0 ci_affine[0=bgra8:o,1=r8:i0,s0]*2 *62fafdfa
C tex-r8-near 128x128 st=ok in=r8 h=a3104ca9de072325,a3104ca9de072325 hb=a3104ca9de072325 sent=0 p=000000ff,fc0000ff,000000ff,fc0000ff,800000ff,400000ff,c00000ff,400000ff,c00000ff g=080000ff,180000ff,280000ff,380000ff,480000ff,580000ff,680000ff,780000ff,880000ff,980000ff,a80000ff,b80000ff,c80000ff,d80000ff,e80000ff,f80000ff t=0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff0e0000ff2e0000ff4e0000ff6e0000ff8e0000ffae0000ffce0000ffee0000ff ms=2 *39ff57e3
K tex-r8-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=r8:i0,s0]*2 *2cd96ce3
C tex-r16f-lin 128x128 st=ok in=r16f h=2202a2ffe05b7d25,2202a2ffe05b7d25 hb=2202a2ffe05b7d25 sent=0 p=000000ff,fe0000ff,000000ff,fe0000ff,bb0000ff,880000ff,e00000ff,880000ff,e00000ff g=2e0000ff,550000ff,6d0000ff,800000ff,900000ff,9e0000ff,aa0000ff,b60000ff,c00000ff,ca0000ff,d30000ff,dc0000ff,e50000ff,ed0000ff,f40000ff,fb0000ff t=3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff3c0000ff750000ff960000ffaf0000ffc50000ffd70000ffe80000fff70000ff ms=2 *3a098d33
K tex-r16f-lin n=2 d=0 ci_affine_linear_to_srgb[0=bgra8:o,1=r16f:i0,s0]*2 *ae33193c
C tex-r16f-near 128x128 st=ok in=r16f h=a49e6124c80b5b25,a49e6124c80b5b25 hb=a49e6124c80b5b25 sent=0 p=000000ff,fe0000ff,000000ff,fe0000ff,bc0000ff,890000ff,e10000ff,890000ff,e10000ff g=320000ff,560000ff,6e0000ff,810000ff,910000ff,9f0000ff,ab0000ff,b70000ff,c10000ff,cb0000ff,d40000ff,dd0000ff,e50000ff,ed0000ff,f50000ff,fc0000ff t=3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff3b0000ff750000ff960000ffaf0000ffc50000ffd80000ffe80000fff70000ff ms=2 *4ca8686b
K tex-r16f-near n=2 d=0 ci_affine_nearest_linear_to_srgb[0=bgra8:o,1=r16f:i0,s0]*2 *7df56571
C tex-a8-lin 128x128 st=ok in=a8 h=01928e67ca062325,01928e67ca062325 hb=01928e67ca062325 sent=0 p=00000000,000000fc,00000000,000000fc,0000007f,0000003f,000000bf,0000003f,000000bf g=00000007,00000017,00000027,00000037,00000047,00000057,00000067,00000077,00000087,00000097,000000a7,000000b7,000000c7,000000d7,000000e7,000000f7 t=0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee ms=2 *5d340e59
K tex-a8-lin n=2 d=0 ci_affine[0=bgra8:o,1=a8:i0,s0]*2 *7ec0ab16
C tex-a8-near 128x128 st=ok in=a8 h=e82ccbb286bc2325,e82ccbb286bc2325 hb=e82ccbb286bc2325 sent=0 p=00000000,000000fc,00000000,000000fc,00000080,00000040,000000c0,00000040,000000c0 g=00000008,00000018,00000028,00000038,00000048,00000058,00000068,00000078,00000088,00000098,000000a8,000000b8,000000c8,000000d8,000000e8,000000f8 t=0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee0000000e0000002e0000004e0000006e0000008e000000ae000000ce000000ee ms=2 *49a477d7
K tex-a8-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=a8:i0,s0]*2 *63ee9884
C tex-rgba16f-lin 128x128 st=ok in=rgba16f h=bb9f9a7d9949feed,bb9f9a7d9949feed hb=bb9f9a7d9949feed sent=0 p=000089ff,fe00e1ff,00fee1ff,fefe89ff,bbbbb1ff,8888b1ff,e088b1ff,88e0b1ff,e0e0b1ff g=2ebba5ff,55bba5ff,6dbbd0ff,80bbd0ff,90bba5ff,9ebba5ff,aabbd0ff,b6bbd0ff,c0bba5ff,cabba5ff,d3bbd0ff,dcbbd0ff,e5bba5ff,edbba5ff,f4bbd0ff,fbbbd0ff t=3c3c8cff753cdeff963c8effaf3cdeffc53c8effd73cdeffe83c8efff73cdfff3c75deff757590ff9675ddffaf7590ffc575ddffd77590ffe875ddfff7758eff3c968eff7596ddff969690ffaf96ddffc59690ffd796ddffe89690fff796deff3cafdeff75af90ff96afddffafaf90ffc5afddffd7af90ffe8afddfff7af8eff3cc58eff75c5ddff96c590ffafc5ddffc5c590ffd7c5ddffe8c590fff7c5deff3cd7deff75d790ff96d7ddffafd790ffc5d7ddffd7d790ffe8d7ddfff7d78eff3ce88eff75e8ddff96e890ffafe8ddffc5e890ffd7e8ddffe8e890fff7e8deff3cf7dfff75f78eff96f7deffaff78effc5f7deffd7f78effe8f7defff7f78cff ms=2 *885422d6
K tex-rgba16f-lin n=2 d=0 ci_affine_premul_linear_to_srgb_unpremul[0=bgra8:o,1=rgba16f:i0,s0]*2 *a3befd7f
C tex-rgba16f-near 128x128 st=ok in=rgba16f h=04809933e3a7288d,04809933e3a7288d hb=04809933e3a7288d sent=0 p=000089ff,fe00e1ff,00fee1ff,fefe89ff,bcbc89ff,898989ff,e18989ff,89e189ff,e1e189ff g=32bc89ff,56bc89ff,6ebce1ff,81bce1ff,91bc89ff,9fbc89ff,abbce1ff,b7bce1ff,c1bc89ff,cbbc89ff,d4bce1ff,ddbce1ff,e5bc89ff,edbc89ff,f5bce1ff,fcbce1ff t=3b3b89ff753be1ff963b89ffaf3be1ffc53b89ffd83be1ffe83b89fff73be1ff3b75e1ff757589ff9675e1ffaf7589ffc575e1ffd87589ffe875e1fff77589ff3b9689ff7596e1ff969689ffaf96e1ffc59689ffd896e1ffe89689fff796e1ff3bafe1ff75af89ff96afe1ffafaf89ffc5afe1ffd8af89ffe8afe1fff7af89ff3bc589ff75c5e1ff96c589ffafc5e1ffc5c589ffd8c5e1ffe8c589fff7c5e1ff3bd8e1ff75d889ff96d8e1ffafd889ffc5d8e1ffd8d889ffe8d8e1fff7d889ff3be889ff75e8e1ff96e889ffafe8e1ffc5e889ffd8e8e1ffe8e889fff7e8e1ff3bf7e1ff75f789ff96f7e1ffaff789ffc5f7e1ffd8f789ffe8f7e1fff7f789ff ms=2 *d2fae3b9
K tex-rgba16f-near n=2 d=0 ci_affine_nearest_premul_linear_to_srgb_[0=bgra8:o,1=rgba16f:i0,s0]*2 *5e72601b
C tex-rgba16u-lin 128x128 st=ok in=rgba16u h=c89ba91151c892b9,c89ba91151c892b9 hb=c89ba91151c892b9 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,7f7f70ff,3f3f70ff,bf3f70ff,3fbf70ff,bfbf70ff g=077f60ff,177f60ff,277fa0ff,377fa0ff,477f60ff,577f60ff,677fa0ff,777fa0ff,877f60ff,977f60ff,a77fa0ff,b77fa0ff,c77f60ff,d77f60ff,e77fa0ff,f77fa0ff t=0e0e44ff2e0ebaff4e0e46ff6e0ebaff8e0e46ffae0ebaffce0e46ffee0ebcff0e2ebaff2e2e48ff4e2eb8ff6e2e48ff8e2eb8ffae2e48ffce2eb8ffee2e46ff0e4e46ff2e4eb8ff4e4e48ff6e4eb8ff8e4e48ffae4eb8ffce4e48ffee4ebaff0e6ebaff2e6e48ff4e6eb8ff6e6e48ff8e6eb8ffae6e48ffce6eb8ffee6e46ff0e8e46ff2e8eb8ff4e8e48ff6e8eb8ff8e8e48ffae8eb8ffce8e48ffee8ebaff0eaebaff2eae48ff4eaeb8ff6eae48ff8eaeb8ffaeae48ffceaeb8ffeeae46ff0ece46ff2eceb8ff4ece48ff6eceb8ff8ece48ffaeceb8ffcece48ffeecebaff0eeebcff2eee46ff4eeebaff6eee46ff8eeebaffaeee46ffceeebaffeeee44ff ms=2 *4d970514
K tex-rgba16u-lin n=2 d=0 ci_affine[0=bgra8:o,1=rgba16u:i0,s0]*2 *7f98cc94
C tex-rgba16u-near 128x128 st=ok in=rgba16u h=93f58ae72abcc325,93f58ae72abcc325 hb=93f58ae72abcc325 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,808040ff,404040ff,c04040ff,40c040ff,c0c040ff g=088040ff,188040ff,2880c0ff,3880c0ff,488040ff,588040ff,6880c0ff,7880c0ff,888040ff,988040ff,a880c0ff,b880c0ff,c88040ff,d88040ff,e880c0ff,f880c0ff t=0e0e40ff2e0ec0ff4e0e40ff6e0ec0ff8e0e40ffae0ec0ffce0e40ffee0ec0ff0e2ec0ff2e2e40ff4e2ec0ff6e2e40ff8e2ec0ffae2e40ffce2ec0ffee2e40ff0e4e40ff2e4ec0ff4e4e40ff6e4ec0ff8e4e40ffae4ec0ffce4e40ffee4ec0ff0e6ec0ff2e6e40ff4e6ec0ff6e6e40ff8e6ec0ffae6e40ffce6ec0ffee6e40ff0e8e40ff2e8ec0ff4e8e40ff6e8ec0ff8e8e40ffae8ec0ffce8e40ffee8ec0ff0eaec0ff2eae40ff4eaec0ff6eae40ff8eaec0ffaeae40ffceaec0ffeeae40ff0ece40ff2ecec0ff4ece40ff6ecec0ff8ece40ffaecec0ffcece40ffeecec0ff0eeec0ff2eee40ff4eeec0ff6eee40ff8eeec0ffaeee40ffceeec0ffeeee40ff ms=2 *0a644c45
K tex-rgba16u-near n=2 d=0 ci_affine_nearest[0=bgra8:o,1=rgba16u:i0,s0]*2 *4f64f6c7
C tex-rgb10a2-lin 128x128 st=ok in=rgb10a2 h=c89ba91151c892b9,c89ba91151c892b9 hb=c89ba91151c892b9 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,7f7f70ff,3f3f70ff,bf3f70ff,3fbf70ff,bfbf70ff g=077f60ff,177f60ff,277fa0ff,377fa0ff,477f60ff,577f60ff,677fa0ff,777fa0ff,877f60ff,977f60ff,a77fa0ff,b77fa0ff,c77f60ff,d77f60ff,e77fa0ff,f77fa0ff t=0e0e44ff2e0ebaff4e0e46ff6e0ebaff8e0e46ffae0ebaffce0e46ffee0ebcff0e2ebaff2e2e48ff4e2eb8ff6e2e48ff8e2eb8ffae2e48ffce2eb8ffee2e46ff0e4e46ff2e4eb8ff4e4e48ff6e4eb8ff8e4e48ffae4eb8ffce4e48ffee4ebaff0e6ebaff2e6e48ff4e6eb8ff6e6e48ff8e6eb8ffae6e48ffce6eb8ffee6e46ff0e8e46ff2e8eb8ff4e8e48ff6e8eb8ff8e8e48ffae8eb8ffce8e48ffee8ebaff0eaebaff2eae48ff4eaeb8ff6eae48ff8eaeb8ffaeae48ffceaeb8ffeeae46ff0ece46ff2eceb8ff4ece48ff6eceb8ff8ece48ffaeceb8ffcece48ffeecebaff0eeebcff2eee46ff4eeebaff6eee46ff8eeebaffaeee46ffceeebaffeeee44ff ms=3 *d17e3ca5
K tex-rgb10a2-lin n=2 d=0 ci_affine_premul[0=bgra8:o,1=rgb10a2:i0,s0]*2 *c94be537
C tex-rgb10a2-near 128x128 st=ok in=rgb10a2 h=93f58ae72abcc325,93f58ae72abcc325 hb=93f58ae72abcc325 sent=0 p=000040ff,fc00c0ff,00fcc0ff,fcfc40ff,808040ff,404040ff,c04040ff,40c040ff,c0c040ff g=088040ff,188040ff,2880c0ff,3880c0ff,488040ff,588040ff,6880c0ff,7880c0ff,888040ff,988040ff,a880c0ff,b880c0ff,c88040ff,d88040ff,e880c0ff,f880c0ff t=0e0e40ff2e0ec0ff4e0e40ff6e0ec0ff8e0e40ffae0ec0ffce0e40ffee0ec0ff0e2ec0ff2e2e40ff4e2ec0ff6e2e40ff8e2ec0ffae2e40ffce2ec0ffee2e40ff0e4e40ff2e4ec0ff4e4e40ff6e4ec0ff8e4e40ffae4ec0ffce4e40ffee4ec0ff0e6ec0ff2e6e40ff4e6ec0ff6e6e40ff8e6ec0ffae6e40ffce6ec0ffee6e40ff0e8e40ff2e8ec0ff4e8e40ff6e8ec0ff8e8e40ffae8ec0ffce8e40ffee8ec0ff0eaec0ff2eae40ff4eaec0ff6eae40ff8eaec0ffaeae40ffceaec0ffeeae40ff0ece40ff2ecec0ff4ece40ff6ecec0ff8ece40ffaecec0ffcece40ffeecec0ff0eeec0ff2eee40ff4eeec0ff6eee40ff8eeec0ffaeee40ffceeec0ffeeee40ff ms=2 *4549d2a9
K tex-rgb10a2-near n=2 d=0 ci_affine_nearest_premul[0=bgra8:o,1=rgb10a2:i0,s0]*2 *34f8d0f8
C cg-rgba8 128x128 st=ok in=cg-rgba8 h=d6f0f1b08c4ed531,d6f0f1b08c4ed531 hb=d6f0f1b08c4ed531 sent=0 p=00fcc0ff,fcfc40ff,000040ff,fc00c0ff,7f7d90ff,3fbd90ff,bfbd90ff,3f3d90ff,bf3d90ff g=077da0ff,177da0ff,277d60ff,377d60ff,477da0ff,577da0ff,677d60ff,777d60ff,877da0ff,977da0ff,a77d60ff,b77d60ff,c77da0ff,d77da0ff,e77d60ff,f77d60ff t=0eeebcff2eee46ff4eeebaff6eee46ff8eeebaffaeee46ffceeebaffeeee44ff0ece46ff2eceb8ff4ece48ff6eceb8ff8ece48ffaeceb8ffcece48ffeecebaff0eaebaff2eae48ff4eaeb8ff6eae48ff8eaeb8ffaeae48ffceaeb8ffeeae46ff0e8e46ff2e8eb8ff4e8e48ff6e8eb8ff8e8e48ffae8eb8ffce8e48ffee8ebaff0e6ebaff2e6e48ff4e6eb8ff6e6e48ff8e6eb8ffae6e48ffce6eb8ffee6e46ff0e4e46ff2e4eb8ff4e4e48ff6e4eb8ff8e4e48ffae4eb8ffce4e48ffee4ebaff0e2ebaff2e2e48ff4e2eb8ff6e2e48ff8e2eb8ffae2e48ffce2eb8ffee2e46ff0e0e44ff2e0ebaff4e0e46ff6e0ebaff8e0e46ffae0ebaffce0e46ffee0ebcff ms=2 *2dc6981f
K cg-rgba8 n=2 d=0 ci_affine_clamp_to_alpha[0=bgra8:o,1=rgba8:x,s0]*2 *beb8dff7
C cg-ga8 128x128 st=ok in=cg-ga8 h=5f191369f1d1c7ea,5f191369f1d1c7ea hb=5f191369f1d1c7ea sent=0 p=00000081,7f7f7f81,000000ff,fcfcfcff,606060c0,272727a0,787878a0,373737e0,a8a8a8e0 g=050505c0,111111c0,1d1d1dc0,292929c0,353535c0,424242c0,4e4e4ec0,5a5a5ac0,666666c0,727272c0,7e7e7ec0,8a8a8ac0,969696c0,a2a2a2c0,afafafc0,bbbbbbc0 t=08080888191919882a2a2a883b3b3b884c4c4c885d5d5d886e6e6e887f7f7f88080808981b1b1b982e2e2e984242429855555598686868987b7b7b988e8e8e98090909a81e1e1ea8333333a8494949a85e5e5ea8737373a8888888a89d9d9da80a0a0ab8212121b8383838b84f4f4fb8676767b87d7d7db8959595b8acacacb80b0b0bc8242424c83d3d3dc8565656c86f6f6fc8888888c8a1a1a1c8bbbbbbc80c0c0cd8272727d8424242d85d5d5dd8787878d8939393d8aeaeaed8c9c9c9d80d0d0de82a2a2ae8474747e8646464e8818181e89e9e9ee8bbbbbbe8d8d8d8e80e0e0ef82d2d2df84c4c4cf86b6b6bf88a8a8af8a9a9a9f8c8c8c8f8e7e7e7f8 ms=2 *6be1b569
K cg-ga8 n=2 d=0 ci_affine_clamp_to_alpha_premul_linear_t[0=bgra8:o,1=rg8:x,s0]*2 *2f6000dc
C cg-ga16f 128x128 st=ok in=cg-ga16f h=b3d788b877ab5d13,b3d788b877ab5d13 hb=b3d788b877ab5d13 sent=0 p=00000081,80808081,000000ff,fefefeff,8d8d8dc0,565656a0,8d8d8da0,787878e0,c6c6c6e0 g=232323c0,404040c0,525252c0,606060c0,6c6c6cc0,777777c0,818181c0,898989c0,919191c0,999999c0,a0a0a0c0,a6a6a6c0,acacacc0,b2b2b2c0,b9b9b9c0,bebebec0 t=202020883e3e3e88505050885e5e5e8869696988737373887c7c7c88848484882323239846464698595959986868689875757598808080988a8a8a9893939398282828a84d4d4da8636363a8737373a8828282a88e8e8ea8999999a8a3a3a3a82b2b2bb8555555b86c6c6cb87f7f7fb88e8e8eb89b9b9bb8a7a7a7b8b3b3b3b82f2f2fc85c5c5cc8767676c88a8a8ac89a9a9ac8a9a9a9c8b6b6b6c8c2c2c2c8333333d8636363d87f7f7fd8959595d8a7a7a7d8b6b6b6d8c5c5c5d8d1d1d1d8373737e86b6b6be8888888e89f9f9fe8b3b3b3e8c4c4c4e8d3d3d3e8e1e1e1e83b3b3bf8727272f8929292f8aaaaaaf8bfbfbff8d1d1d1f8e2e2e2f8f0f0f0f8 ms=3 *423b4430
K cg-ga16f n=2 d=0 ci_affine_clamp_to_alpha_premul_linear_t[0=bgra8:o,1=rg16f:x,s0]*2 *6219902b
C cg-g8 128x128 st=ok in=cg-g8 h=e8026f5ff62d2f25,e8026f5ff62d2f25 hb=e8026f5ff62d2f25 sent=0 p=000000ff,fcfcfcff,000000ff,fcfcfcff,7f7f7fff,3f3f3fff,bfbfbfff,3f3f3fff,bfbfbfff g=070707ff,171717ff,272727ff,373737ff,474747ff,575757ff,676767ff,777777ff,878787ff,979797ff,a7a7a7ff,b7b7b7ff,c7c7c7ff,d7d7d7ff,e7e7e7ff,f7f7f7ff t=0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff0e0e0eff2e2e2eff4e4e4eff6e6e6eff8e8e8effaeaeaeffcececeffeeeeeeff ms=2 *629ba8a8
K cg-g8 n=2 d=0 ci_affine_swizzle_rrr1[0=bgra8:o,1=r8:x,s0]*2 *d93d598e
C lanczos-rgba8s 40x40 st=ok in=rgba8s h=4ba0f959b0d0cc3d,4ba0f959b0d0cc3d hb=4ba0f959b0d0cc3d sent=0 p=00000ce9,e0007be9,00e07be9,e0e00ce9,38382bff,0e0e2bff,880e2bff,0e882bff,88882bff g=013921ff,02381cff,053884ff,093884ff,10381cff,17381cff,233884ff,2d3884ff,3e381cff,4c381cff,633884ff,753884ff,93381cff,a9381cff,cd3884ff,eb3985ff t=010112fb070184fd140114fd280184fd450114fd6c0184fd9d0114fdd70184fb010784fd070717ff140785ff280717ff450785ff6c0717ff9e0785ffd90714fd011414fd071485ff141417ff281485ff451417ff6c1485ff9e1417ffd91484fd012884fd072817ff142885ff282817ff452885ff6c2817ff9e2885ffd92814fd014514fd074585ff144517ff284585ff454517ff6c4585ff9e4517ffd94584fd016c84fd076c17ff146c85ff286c17ff456c85ff6c6c17ff9e6c85ffd96c14fd019d14fd079e85ff149e17ff289e85ff459e17ff6c9e85ff9e9e17ffd99d84fd01d784fb07d914fd14d984fd28d914fd45d984fd6cd914fd9dd984fdd7d712fb ms=4 *3af626a4
K lanczos-rgba8s n=6 d=0 ci_premul_srgb_to_linear_unpremul[0=rgba16f:x,1=rgba8s:i0,s0]*2;ci_lanczosDownH[0=rgba16f:x,1=rgba16f:x,s0]*2;ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba16f:x,s0]*2 *48730eb7
C blur-rgba8 128x128 st=ok in=rgba8 h=fb07a0817d8fe8b1,fb07a0817d8fe8b1 hb=fb07a0817d8fe8b1 sent=0 p=010140ff,fb01c0ff,01fbc0ff,fbfb40ff,7f7f88ff,3f3f88ff,bf3f88ff,3fbf88ff,bfbf88ff g=077f82ff,177f82ff,277f97ff,377f96ff,477f82ff,577f82ff,677f97ff,777f96ff,877f82ff,977f82ff,a77f97ff,b77f96ff,c77f82ff,d77f82ff,e77f97ff,f77f98ff t=0e0e51ff2e0eb1ff4e0e59ff6e0eb1ff8e0e59ffae0eb1ffce0e59ffee0eb6ff0e2eb1ff2e2e60ff4e2eadff6e2e60ff8e2eadffae2e60ffce2eadffee2e59ff0e4e59ff2e4eadff4e4e60ff6e4eadff8e4e60ffae4eadffce4e60ffee4eb1ff0e6eb1ff2e6e60ff4e6eadff6e6e60ff8e6eadffae6e60ffce6eadffee6e59ff0e8e59ff2e8eadff4e8e60ff6e8eadff8e8e60ffae8eadffce8e60ffee8eb1ff0eaeb1ff2eae60ff4eaeadff6eae60ff8eaeadffaeae60ffceaeadffeeae59ff0ece59ff2eceadff4ece60ff6eceadff8ece60ffaeceadffcece60ffeeceb1ff0eeeb6ff2eee59ff4eeeb1ff6eee59ff8eeeb1ffaeee59ffceeeb1ffeeee51ff ms=9 *61f773cf
K blur-rgba8 n=8 d=0 ci_affine_clampRect_premul_srgb_to_linea[0=rgba16f:x,1=rgba8:i0,s0]*2;F13V[0=rgba16f:x,1=rgba16f:x,s0]*2;F13H[0=rgba16f:x,1=rgba16f:x,s0]*2;ci_affine_clamp_to_alpha_premul_linear_t[0=bgra8:o,1=rgba16f:x,s0]*2 *dc84b1a6
C fill 64x64 st=ok in=none h=df48cf684e74c325,df48cf684e74c325 hb=df48cf684e74c325 sent=0 p=4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff g=4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff,4080bfff t=4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff4080bfff ms=2 *55c0282d
K fill n=2 d=0 ci_fill[0=bgra8:o]*2 *26dc5965
C multi-2 96x96 st=ok in=rgba8s+rgba8s h=68f1e190bbbc8225,68f1e190bbbc8225 hb=68f1e190bbbc8225 sent=0 p=ca0505ff,05930cff,262804c0,262804c0,05930cff,ca0505ff,05930cff,262804c0,262804c0 g=ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff t=ca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cff262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0 s=ca0505ff,05930cff/262804c0,262804c0 ms=2 *bfd81aa1
K multi-2 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba8s:i1,2=rgba8s:i0,s0,s1]*2 *28cc071a
C multi-3 96x96 st=ok in=rgba8s+rgba8s+rgba8s h=10c471ae9b3e1325,10c471ae9b3e1325 hb=10c471ae9b3e1325 sent=0 p=ca0505ff,0814caff,1c1f3adf,1c1f3adf,05930cff,ca0505ff,0814caff,1c1f3adf,1c1f3adf g=ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,0814caff,0814caff,0814caff,0814caff,0814caff t=ca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caffca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caffca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caffca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caffca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caffca0505ffca0505ff883407ff05930cff05930cff073e8bff0814caff0814caff1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf1c1f3adf s=ca0505ff,05930cff,0814caff/1c1f3adf,1c1f3adf,1c1f3adf ms=2 *1fa05b3d
K multi-3 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba8s:i2,2=rgba8s:i1,3=rgba8s:i0,s0,s1,s2]*2 *56939a93
C multi-4 96x96 st=ok in=rgba8s+rgba8s+rgba8s+rgba8s h=45c0c22021566325,45c0c22021566325 hb=45c0c22021566325 sent=0 p=ca0505ff,de9303ff,46322aef,46322aef,0814caff,05930cff,de9303ff,46322aef,46322aef g=ca0505ff,ca0505ff,ca0505ff,ca0505ff,05930cff,05930cff,05930cff,05930cff,0814caff,0814caff,0814caff,0814caff,de9303ff,de9303ff,de9303ff,de9303ff t=ca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ffca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ffca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ffca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ffca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ffca0505ffca0505ff05930cff05930cff0814caff0814caffde9303ffde9303ff46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef46322aef s=ca0505ff,05930cff,0814caff,de9303ff/46322aef,46322aef,46322aef,46322aef ms=2 *d97e59b2
K multi-4 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba8s:i3,2=rgba8s:i2,3=rgba8s:i1,4=rgba8s:i0,s0,s1,s2,s3]*2 *0717400f
C multi-6 96x96 st=ok in=rgba8s+rgba8s+rgba8s+rgba8s+rgba8s+rgba8s h=48a2f683317e9b25,48a2f683317e9b25 hb=48a2f683317e9b25 sent=0 p=ca0505ff,03a4a4ff,2b3940fb,2b3940fb,de9303ff,05930cff,740c93ff,2b3940fb,2b3940fb g=ca0505ff,ca0505ff,ca0505ff,05930cff,05930cff,0814caff,0814caff,0814caff,de9303ff,de9303ff,de9303ff,740c93ff,740c93ff,03a4a4ff,03a4a4ff,03a4a4ff t=ca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ffca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ffca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ffca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ffca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ffca0505ff47640aff06694bff0814caffde9303ff973963ff4e3f99ff03a4a4ff2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb2b3940fb s=ca0505ff,05930cff,0814caff,de9303ff,740c93ff,03a4a4ff/2b3940fb,2b3940fb,2b3940fb,2b3940fb,2b3940fb,2b3940fb ms=4 *597c7046
K multi-6 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba8s:i5,2=rgba8s:i4,3=rgba8s:i3,4=rgba8s:i2,5=rgba8s:i1,6=rgba8s:i0,s0,s1,s2,s3,s4,s5]*2 *f5cc0dfb
C multi-6-bgra8 96x96 st=ok in=bgra8+bgra8+bgra8+bgra8+bgra8+bgra8 h=3cf3913f088aa325,3cf3913f088aa325 hb=3cf3913f088aa325 sent=0 p=e62828ff,1ed2d2ff,86adbdfb,86adbdfb,f0c81eff,28c83cff,b43cc8ff,86adbdfb,86adbdfb g=e62828ff,e62828ff,e62828ff,28c83cff,28c83cff,3250e6ff,3250e6ff,3250e6ff,f0c81eff,f0c81eff,f0c81eff,b43cc8ff,b43cc8ff,1ed2d2ff,1ed2d2ff,1ed2d2ff t=e62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ffe62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ffe62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ffe62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ffe62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ffe62828ff679335ff2ba075ff3250e6fff0c81effc86b8fff826ecbff1ed2d2ff86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb86adbdfb s=e62828ff,28c83cff,3250e6ff,f0c81eff,b43cc8ff,1ed2d2ff/86adbdfb,86adbdfb,86adbdfb,86adbdfb,86adbdfb,86adbdfb ms=3 *53eeecc5
K multi-6-bgra8 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=bgra8:i5,2=bgra8:i4,3=bgra8:i3,4=bgra8:i2,5=bgra8:i1,6=bgra8:i0,s0,s1,s2,s3,s4,s5]*2 *271cbc39
C filter-srcover-2 96x96 st=ok in=rgba8s+rgba8s h=68f1e190bbbc8225,68f1e190bbbc8225 hb=68f1e190bbbc8225 sent=0 p=ca0505ff,05930cff,262804c0,262804c0,05930cff,ca0505ff,05930cff,262804c0,262804c0 g=ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,ca0505ff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff t=ca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cffca0505ffca0505ffca0505ffca0505ff05930cff05930cff05930cff05930cff262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0262804c0 s=ca0505ff,05930cff/262804c0,262804c0 ms=2 *d9d8d287
K filter-srcover-2 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=rgba8s:i1,2=rgba8s:i0,s0,s1]*2 *96bcbc95
C mixed-3 96x96 st=ok in=cg-ga8+rgba8s+bgra8 h=c895b1e070a11b25,c895b1e070a11b25 hb=c895b1e070a11b25 sent=0 p=30303080,3250e6ff,3c51aeff,1e3890c0,05930cff,616161ff,3250e6ff,3c51aeff,1e3890c0 g=616161ff,616161ff,616161ff,616161ff,616161ff,05930cff,05930cff,05930cff,05930cff,05930cff,05930cff,3250e6ff,3250e6ff,3250e6ff,3250e6ff,3250e6ff t=3030308030303080225124aa05930cff05930cff23669dff3250e6ff3250e6ff3030308030303080225124aa05930cff05930cff23669dff3250e6ff3250e6ff616161ff616161ff427245ff05930cff05930cff23669dff3250e6ff3250e6ff616161ff616161ff427245ff05930cff05930cff23669dff3250e6ff3250e6ff616161ff616161ff427245ff05930cff05930cff23669dff3250e6ff3250e6ff616161ff616161ff427245ff05930cff05930cff23669dff3250e6ff3250e6ff3c51aeff3c51aeff3249a4ea1e3890c01e3890c01e3890c01e3890c01e3890c03c51aeff3c51aeff3249a4ea1e3890c01e3890c01e3890c01e3890c01e3890c0 s=515151d6,05930cff,3250e6ff/3c51aeff,1e3890c0,1e3890c0 ms=2 *fa87a856
K mixed-3 n=2 d=0 ci_clamp_to_alpha_premul_linear_to_srgb_[0=bgra8:o,1=bgra8:i2,2=rgba8s:i1,3=rg8:x,s0,s1,s2]*2 *b002e24e
D diag=/private/var/tmp/adreno-icon-pixels-2871.log absent *8c0545f2
END command=synth cases=38 errors=0 *0510e4e8
+233
View File
@@ -0,0 +1,233 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Run icon-pixels in the A16 guest from its root console shell, fetch its lines and compare them with this Mac's.
During a --console desktop boot, once the multi-user root shell answers, as the only console client (pass the run's
--owner). Writes nothing outside the guest's /private/var/tmp/icon-pixels (and, with --move-icon-caches, renames the
icon caches aside). Every console command's output stays under about 4 KB.
python3 tools/run-icon-pixels.py --device udp://enN --launch-record build/PREFIX-early-stop-launch.json \\
--owner HEX --tool build/icon-pixels/TOOL/icon-pixels --output NEW-DIR \\
[--reference-synth REF.txt] [--reference-icons REF.txt] [--steps push,version,synth,icons,diag] \\
[--move-icon-caches TAG] [--synth-timeout 1200]
Steps (--steps picks and orders them; each is recorded in OUTPUT/run.json):
push the tool as base64 in 512-character printf chunks (tools/deploy-guest-plugin.py's transfer: one numbered file
per chunk, a SHA-256 listing finds chunks not held intact and resends them), joined, decoded, its SHA-256
checked, then installed as /private/var/tmp/icon-pixels/icon-pixels (mode 755)
version `icon-pixels version`: the binary executes (an ad-hoc arm64e executable; if the guest's AMFI kills it, the
step stops and names the trust-cache row build-icon-pixels.py recorded) and finds the plug-in device
synth `icon-pixels synth --repeat 2` as root: CoreImage kernels on synthetic inputs, on the plug-in device
icons `icon-pixels icons --source gen` as root (IconServices' generator in-process, on the plug-in device), then,
as the console user in that user's bootstrap context (launchctl asuser UID sudo -u #UID),
`icon-pixels icons --source ws` (NSWorkspace, through iconservicesagent and its caches)
recache with --move-icon-caches TAG: rename the IconServices store, every user's com.apple.iconservices and
com.apple.iconservicesagent caches and the Dock's com.apple.dock.iconcache to NAME.TAG (reversible: mv back),
restart iconservicesd (launchctl kickstart -k), stop every iconservicesagent, the Dock and System Settings, so
the next icons drawn are rendered again with the installed plug-in. Put it before `icons`
diag the plug-in's diagnostics: per-process files /private/var/tmp/adreno-*.log (counts, distinct drop/refusal
reasons), and the unified log subsystem org.snapintosh.adreno for iconservicesagent and System Settings
Each output file is fetched 3 lines per command; every line carries a CRC, so console noise is dropped and a block that
came back short is fetched again (up to 4 times). Then tools/compare-icon-pixels.py against the references.
Offline (the console stand-in, tools/test-icon-pixels.py): --target 127.0.0.1:PORT --session HEX --guest-root DIR.
"""
import argparse
import base64
import hashlib
import importlib.util
import json
from pathlib import Path
import re
import shlex
import subprocess
import sys
import time
import zlib
ROOT = Path(__file__).resolve().parents[1]
REMOTE = '/private/var/tmp/icon-pixels'
BLOCK = 3
STEPS = 'push,version,synth,icons,diag'
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def valid(line):
body, sep, crc = line.strip().rpartition(' *')
return bool(sep) and re.fullmatch(r'[0-9a-f]{8}', crc or '') is not None and \
f'{zlib.crc32(body.encode()) & 0xffffffff:08x}' == crc
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
where = ap.add_mutually_exclusive_group(required=True)
where.add_argument('--device')
where.add_argument('--target', help='host:port of the console stand-in (offline test)')
ap.add_argument('--launch-record')
ap.add_argument('--session')
ap.add_argument('--owner', required=True, help='hex console owner of this run (one owner for every console step)')
ap.add_argument('--tool', type=Path, required=True)
ap.add_argument('--output', type=Path, required=True)
ap.add_argument('--steps', default=STEPS)
ap.add_argument('--reference-synth', type=Path)
ap.add_argument('--reference-icons', type=Path)
ap.add_argument('--move-icon-caches', metavar='TAG')
ap.add_argument('--synth-args', default='--repeat 2')
ap.add_argument('--icons-args', default='--size 20 --scale 2')
ap.add_argument('--synth-timeout', type=int, default=1200)
ap.add_argument('--icons-timeout', type=int, default=600)
ap.add_argument('--guest-root', default='/', help='prefix of every guest path (offline test)')
ap.add_argument('--console-user', help='uid for the ws icons (default: the owner of /dev/console)')
ap.add_argument('--poll-ms', type=int, default=50)
ap.add_argument('--wait-ms', type=int, default=150)
ap.add_argument('--attempts', type=int, default=30)
a = ap.parse_args(argv)
steps = [s for s in a.steps.split(',') if s]
unknown = set(steps) - {'push', 'version', 'synth', 'icons', 'recache', 'diag'}
if unknown or ('recache' in steps) != bool(a.move_icon_caches):
ap.error(f'unknown steps {sorted(unknown)}, or recache without --move-icon-caches TAG (or the reverse)')
# Offline the "guest" shell is this Mac's: never restart its processes, never ask its sudo for a password.
if a.target and 'recache' in steps:
ap.error('recache restarts the shell machine\'s icon services, Dock and System Settings: only with --device')
if a.target and 'icons' in steps and a.console_user is None:
ap.error('offline, pass --console-user (0 skips the NSWorkspace icons)')
if a.move_icon_caches and not re.fullmatch(r'[A-Za-z0-9._-]{1,48}', a.move_icon_caches):
ap.error('the cache tag is letters, digits, dot, dash and underscore')
out = a.output.resolve()
out.mkdir(parents=True, exist_ok=False)
deploy = load('deploy', ROOT / 'tools/deploy-guest-plugin.py')
tool = a.tool.read_bytes()
tool_sha = hashlib.sha256(tool).hexdigest()
build = a.tool.resolve().parent / 'build.json'
trust_row = json.loads(build.read_text()).get('trust_row') if build.is_file() else None
args = argparse.Namespace(session=a.session, launch_record=a.launch_record, device=a.device, target=a.target,
owner=a.owner, wait_ms=a.wait_ms, poll_ms=a.poll_ms, test_skip_chunk=[])
root = a.guest_root.rstrip('/')
remote = root + REMOTE
exe = f'{remote}/icon-pixels'
rec = dict(schema='snapintosh-icon-pixels-run-v1', started=time.time(), tool=str(a.tool), tool_sha256=tool_sha,
tool_bytes=len(tool), remote=remote, steps=steps, status='RUNNING')
shell = deploy.Shell(args)
def fetch(name, path):
"""The file's lines, BLOCK per command, checksummed; written to OUTPUT/NAME."""
count = shell.run(f'{name}-lines', f"awk 'END{{print NR}}' {shlex.quote(path)}", ok=(0, 1))
m = re.search(r'^(\d+)\s*$', count, re.M)
lines = int(m.group(1)) if m else 0
kept, refetched, t0 = [], 0, time.monotonic()
for first in range(1, lines + 1, BLOCK):
last = min(lines, first + BLOCK - 1)
want = last - first + 1
good = []
for _ in range(5):
text = shell.run('fetch', f"sed -n '{first},{last}p' {shlex.quote(path)}", timeout=60, ok=(0, 1))
good = [l.strip() for l in text.splitlines() if valid(l)]
if len(good) >= want:
break
refetched += 1
kept += good[:want]
(out / name).write_text('\n'.join(kept) + ('\n' if kept else ''))
rec[name] = dict(lines=lines, fetched=len(kept), refetched_blocks=refetched,
seconds=round(time.monotonic() - t0, 1))
return kept
def run_tool(name, command, timeout):
t0 = time.monotonic()
res = shell.run(name, f'cd {shlex.quote(remote)} && {command} > {name}.txt 2> {name}.err; echo rc=$?; '
f"awk 'END{{print NR}}' {name}.txt; tail -c 600 {name}.err", timeout=timeout, ok=(0, 1))
m = re.search(r'rc=(\d+)\s+(\d+)', res)
rec[f'{name}_rc'] = int(m.group(1)) if m else None
rec[f'{name}_seconds'] = round(time.monotonic() - t0, 1)
rec[f'{name}_stderr_tail'] = res[res.find('rc='):][-700:] if 'rc=' in res else res[-700:]
return fetch(f'{name}.txt', f'{remote}/{name}.txt')
try:
rec['shell_attempts'] = deploy.wait_for_shell(shell, a.attempts, 20)
for step in steps:
if step == 'push':
shell.run('work', f'mkdir -p {shlex.quote(remote)} && rm -rf {shlex.quote(remote)}/push')
t0 = time.monotonic()
f = dict(name='icon-pixels', text=base64.b64encode(tool).decode(), payload_sha256=tool_sha,
new_sha256=tool_sha, patch=False, path=None)
rec['push'] = deploy.transfer(shell, args, f'{remote}/push', f)
shell.run('install', f'cp {shlex.quote(remote)}/push/icon-pixels.new {shlex.quote(exe)}.new && '
f'chmod 755 {shlex.quote(exe)}.new && mv -f {shlex.quote(exe)}.new {shlex.quote(exe)} && '
f'rm -rf {shlex.quote(remote)}/push')
if shell.digest('installed', exe) != tool_sha:
raise deploy.Refused('the installed tool differs from the one pushed')
rec['push_seconds'] = round(time.monotonic() - t0, 1)
elif step == 'version':
res = shell.run('version', f'{shlex.quote(exe)} version 2>&1; echo rc=$?', timeout=120, ok=(0, 1))
m = re.search(r'rc=(\d+)', res)
rec['version_rc'] = int(m.group(1)) if m else None
rec['version'] = [l.strip() for l in res.splitlines() if valid(l)]
if rec['version_rc'] != 0 or not any(l.startswith('V ') for l in rec['version']):
hint = (f' If it was killed (rc 137, "Killed: 9"), the guest refuses the ad-hoc signature: add the row '
f'{json.dumps(trust_row)} to the next run\'s trust additions.' if trust_row else '')
raise deploy.Refused(f'icon-pixels does not run: {res[-300:]!r}.{hint}')
elif step == 'synth':
run_tool('synth', f'./icon-pixels synth {a.synth_args}', a.synth_timeout)
elif step == 'icons':
run_tool('icons-gen', f'./icon-pixels icons --source gen {a.icons_args}', a.icons_timeout)
uid = a.console_user or shell.run('console-user', 'stat -f %u /dev/console').strip().splitlines()[-1]
rec['console_user'] = uid
if uid.isdigit() and uid != '0':
run_tool('icons-ws', f'launchctl asuser {uid} sudo -u "#{uid}" ./icon-pixels icons --source ws '
f'{a.icons_args}', a.icons_timeout)
else:
rec['icons_ws'] = f'skipped: /dev/console belongs to {uid!r} (no logged-in user)'
elif step == 'recache':
tag = a.move_icon_caches
paths = (f'{root}/Library/Caches/com.apple.iconservices.store '
f'{root}/private/var/folders/*/*/C/com.apple.iconservices '
f'{root}/private/var/folders/*/*/C/com.apple.iconservicesagent '
f'{root}/private/var/folders/*/*/C/com.apple.dock.iconcache')
rec['recache'] = shell.run('recache', (
f'for p in {paths}; do if [ -e "$p" ]; then mv "$p" "$p.{tag}" && echo "moved $p" || echo "FAILED $p"; '
f'fi; done; launchctl kickstart -k system/com.apple.iconservices.iconservicesd 2>&1 | tail -1; '
f'killall iconservicesagent Dock "System Settings" 2>&1 | tail -3; echo recache-done'),
timeout=120, ok=(0, 1))[-3000:]
elif step == 'diag':
rec['diag_files'] = shell.run('diag-files', (
f'ls {root}/private/var/tmp/ | grep -c "^adreno-.*\\.log$"; '
f'ls -t {root}/private/var/tmp/ | grep "^adreno-.*\\.log$" | head -12'), ok=(0, 1))[-1500:]
rec['diag_reasons'] = shell.run('diag-reasons', (
f'cat {root}/private/var/tmp/adreno-*.log 2>/dev/null | grep -E "dropped:|refused:|zero texture" | '
f'sed -E "s/^.*(dropped:|refused:|zero texture:)/\\1/" | cut -c1-200 | sort | uniq -c | sort -rn | '
f'head -15'),
timeout=120, ok=(0, 1))[-3500:]
rec['diag_unified'] = shell.run('diag-unified', (
'log show --last 60m --style compact --predicate \'subsystem == "org.snapintosh.adreno" AND '
'(process == "iconservicesagent" OR process == "System Settings")\' 2>/dev/null | '
'grep -E "dropped|refused|not built|zero texture|failed" | cut -c1-220 | tail -15'),
timeout=600, ok=(0, 1))[-3500:]
rec['status'] = 'DONE'
except deploy.Refused as error:
rec['status'] = 'REFUSED'
rec['error'] = str(error)
finally:
rec['commands'] = [s for s in shell.steps if s['step'] not in ('chunk', 'fetch')]
rec['finished'] = time.time()
(out / 'run.json').write_text(json.dumps(rec, indent=1) + '\n')
compares = {}
for name, ref, cases in (('synth.txt', a.reference_synth, '.'), ('icons-gen.txt', a.reference_icons, '^gen-'),
('icons-ws.txt', a.reference_icons, '^ws-')):
if ref and (out / name).is_file():
r = subprocess.run([sys.executable, str(ROOT / 'tools/compare-icon-pixels.py'), str(ref), str(out / name),
'--cases', cases, '--json', str(out / f'{name}.compare.json')], capture_output=True, text=True)
(out / f'{name}.compare.txt').write_text(r.stdout + r.stderr)
compares[name] = r.stdout.strip().splitlines()[-1] if r.stdout.strip() else f'exit {r.returncode}'
print(r.stdout[-2500:])
summary = {k: v for k, v in rec.items() if k not in ('commands', 'push')}
summary['compare'] = compares
print(json.dumps(summary, indent=1)[-4000:])
return 0 if rec['status'] == 'DONE' else 1
if __name__ == '__main__':
sys.exit(main())
+306
View File
@@ -0,0 +1,306 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""icon-pixels offline: the tool on this Mac's GPU, the comparison's diagnoses, and the console procedure end to end.
python3 tools/test-icon-pixels.py --output NEW-DIR [--bundle ADRENO-BUNDLE] [--skip-console] [--noise-rate 40]
1. tools/build-icon-pixels.py builds the tool; it runs `synth` on this Mac's GPU twice (deterministic: identical C
lines) and against tools/icon-pixels-reference-synth.txt (the committed synthetic reference, made with --host-caps
on the deployed plug-in): every case must PASS, or the reference is stale for this Mac's macOS/CoreImage.
2. With --bundle (an ADRENO_SETTINGS_ICONS plug-in bundle with its compiler runtime): `synth --plugin BUNDLE
--cs-samplers 8` (the plug-in device, commits intercepted) must dispatch exactly the kernels and texture formats
of the reference (compare --census-only PASS) with nothing dropped or refused; without the CS sampler stand-in the
multi-input kernels must be dropped (the deployed-kext gate) and the comparison must say so.
3. Fault injection: guest outputs derived from the reference, each with one fault the comparison must name: red/blue
swapped, an extra or a missing sRGB decode, transparent output, a zeroed channel, an unwritten and a partially
written destination, differing readbacks, differing repeats, a missing or foreign input stripe (with its texture
index), a broken blend band, an empty/black/partial/recoloured icon, console noise; and a perturbation within
tolerance that must still PASS.
4. Unless --skip-console: tools/run-icon-pixels.py over the console stand-in (tools/test-console-link.py: q1n1's
console handler, the monitor's console link, /bin/sh on a pty, request/reply loss and mid-line console noise)
pushes the tool, runs `version`, a few synth cases and two generated icons on this Mac's GPU and the diag step,
fetches every line and compares them with a direct run of the same cases: identical C and K lines. (recache and
the NSWorkspace icons act on the shell's own machine, so the runner refuses them offline.)
"""
import argparse
import importlib.util
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import zlib
ROOT = Path(__file__).resolve().parents[1]
REFERENCE = ROOT / 'tools/icon-pixels-reference-synth.txt'
checks, failures = 0, []
def check(ok, what):
global checks
checks += 1
if not ok:
failures.append(what)
print('FAIL', what)
return ok
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def seal(body):
return f'{body} *{zlib.crc32(body.encode()) & 0xffffffff:08x}'
def body(line):
return line.rpartition(' *')[0]
def kind_lines(text, kinds):
return [l for l in text.splitlines() if l[:2] in kinds]
def edit(lines, case, fn, kind='C'):
"""A copy of lines with case's line rewritten by fn(fields dict in order) and resealed."""
out = []
for l in lines:
b = body(l)
words = b.split(' ')
if len(words) > 2 and words[0] == kind and words[1] == case:
head, fields = words[:3], {}
order = []
for w in words[3:]:
k, _, v = w.partition('=')
fields[k] = v
order.append(k)
fn(fields)
l = seal(' '.join(head + [f'{k}={fields[k]}' for k in order if k in fields]))
out.append(l)
return out
def map_pixels(fields, fn, keys=('p', 'g', 't', 's')):
"""Apply fn([r,g,b,a]) -> [r,g,b,a] to every RRGGBBAA value in the sampled fields."""
def one(h):
v = fn([int(h[i:i + 2], 16) for i in (0, 2, 4, 6)])
return ''.join(f'{max(0, min(255, int(round(x)))):02x}' for x in v)
for k in keys:
if k not in fields:
continue
if k == 't':
t = fields['t']
fields['t'] = ''.join(one(t[i:i + 8]) for i in range(0, len(t), 8))
elif k == 's':
top, _, band = fields['s'].partition('/')
fields['s'] = ','.join(one(h) for h in top.split(',')) + '/' + ','.join(one(h) for h in band.split(','))
else:
fields[k] = ','.join(one(h) for h in fields[k].split(','))
def run_compare(cmp_, ref_lines, guest_lines, out, name, *extra):
ref, guest = out / f'{name}.ref.txt', out / f'{name}.guest.txt'
ref.write_text('\n'.join(ref_lines) + '\n')
guest.write_text('\n'.join(guest_lines) + '\n')
r = subprocess.run([sys.executable, str(ROOT / 'tools/compare-icon-pixels.py'), str(ref), str(guest), *extra],
capture_output=True, text=True)
(out / f'{name}.compare.txt').write_text(r.stdout + r.stderr)
verdicts = {}
for l in r.stdout.splitlines():
m = re.match(r'(PASS|FAIL) (\S+?)(?:: (.*))?$', l)
if m and m.group(2) not in ('of',):
verdicts[m.group(2)] = (m.group(1), m.group(3) or '')
return r.returncode, verdicts, r.stdout
def fault_injection(out, ref_lines, icon_lines):
cmp_ = None
lines = ref_lines + icon_lines
rc, v, text = run_compare(cmp_, lines, lines, out, 'identical')
check(rc == 0 and all(x[0] == 'PASS' for x in v.values()), f'identical output must pass: {text[-300:]}')
def expect(name, guest, case, verdict_prefix, contains=None):
rc, v, text = run_compare(cmp_, lines, guest, out, name)
got = v.get(case, ('MISSING', ''))
ok = got[0] == 'FAIL' and got[1].startswith(verdict_prefix) and (contains is None or contains in got[1])
check(ok and rc == 1, f'{name}: {case} must FAIL with {verdict_prefix}{" ... " + contains if contains else ""}; '
f'got {got} rc {rc}')
others = [c for c, x in v.items() if c != case and x[0] == 'FAIL']
check(not others, f'{name}: only {case} may fail; also {others[:5]}')
from_srgb = lambda c: 255 * ((c / 255) / 12.92 if c / 255 <= 0.04045 else ((c / 255 + 0.055) / 1.055) ** 2.4)
to_srgb = lambda c: 255 * ((c / 255) * 12.92 if c / 255 <= 0.0031308 else 1.055 * (c / 255) ** (1 / 2.4) - 0.055)
expect('swap', edit(lines, 'tex-rgba8-lin', lambda f: map_pixels(f, lambda p: [p[2], p[1], p[0], p[3]])),
'tex-rgba8-lin', 'SWAP-RB')
expect('srgb-extra', edit(lines, 'tex-rgba8-lin', lambda f: map_pixels(f, lambda p: [from_srgb(p[0]), from_srgb(p[1]),
from_srgb(p[2]), p[3]])),
'tex-rgba8-lin', 'SRGB-EXTRA')
expect('srgb-missing', edit(lines, 'tex-rgba8s-lin', lambda f: map_pixels(f, lambda p: [to_srgb(p[0]), to_srgb(p[1]),
to_srgb(p[2]), p[3]])),
'tex-rgba8s-lin', 'SRGB-MISSING')
expect('transparent', edit(lines, 'tex-bgra8-near', lambda f: map_pixels(f, lambda p: [0, 0, 0, 0])),
'tex-bgra8-near', 'TRANSPARENT')
expect('green-zero', edit(lines, 'tex-rg8-lin', lambda f: map_pixels(f, lambda p: [p[0], 0, p[2], p[3]])),
'tex-rg8-lin', 'CHANNEL', 'G reads 0')
expect('unwritten', edit(lines, 'tex-rg16f-lin', lambda f: f.update(sent='16384')), 'tex-rg16f-lin', 'UNWRITTEN')
expect('partial', edit(lines, 'tex-r8-lin', lambda f: f.update(sent='512')), 'tex-r8-lin', 'PARTIAL')
expect('readback', edit(lines, 'tex-a8-lin', lambda f: f.update(hb='0123456789abcdef')), 'tex-a8-lin', 'READBACK')
expect('repeats', edit(lines, 'fill', lambda f: f.update(h=f['h'].split(',')[0] + ',0123456789abcdef')),
'fill', 'NONDETERMINISTIC')
def stripe(k, fn):
def apply(f):
top, _, band = f['s'].partition('/')
t = top.split(',')
t[k] = fn(t)
f['s'] = ','.join(t) + '/' + band
return apply
expect('input-missing', edit(lines, 'multi-4', stripe(2, lambda t: '00000000')), 'multi-4', 'INPUT-2', 'texture(2)')
expect('input-foreign', edit(lines, 'multi-6', stripe(5, lambda t: t[1])), 'multi-6', 'INPUT-5', "input 1's colour")
def band(f):
top, _, b = f['s'].partition('/')
f['s'] = top + '/' + ','.join('10203040' for _ in b.split(','))
expect('blend', edit(lines, 'mixed-3', band), 'mixed-3', 'BLEND')
expect('missing-case', [l for l in lines if not body(l).startswith('C cg-ga8 ')], 'cg-ga8', 'MISSING')
expect('error', edit(lines, 'cg-ga16f', lambda f: f.update(st='exception')), 'cg-ga16f', 'ERROR')
# Perturbation within tolerance (+-3 on every channel, alternating) must still pass.
flip = [1]
def jitter(p):
flip[0] = -flip[0]
return [x + 3 * flip[0] for x in p]
rc, v, text = run_compare(cmp_, lines, edit(lines, 'tex-rgba16f-lin', lambda f: map_pixels(f, jitter)), out, 'jitter')
check(v.get('tex-rgba16f-lin', ('?',))[0] == 'PASS', f'jitter within tolerance must pass: {v.get("tex-rgba16f-lin")}')
# Console noise and a torn line are dropped (counted), the rest still compares.
noisy = []
for i, l in enumerate(lines):
noisy.append(l)
if i % 5 == 0:
noisy.append('AdrenoDisplay: perf pace s=1 pps=59.8 late=0 gpu=1/4357')
torn = [l if not body(l).startswith('C fill ') else l[:-3] for l in noisy]
rc, v, text = run_compare(cmp_, lines, torn, out, 'noise')
check(v.get('fill', ('?',))[0] == 'FAIL' and 'MISSING' in v.get('fill', ('', ''))[1] and
sum(1 for x in v.values() if x[0] == 'FAIL') == 1 and 'noise dropped' in text,
f'noise: only the torn fill line is lost: {[c for c, x in v.items() if x[0] == "FAIL"]}')
if icon_lines:
icon = next(body(l).split(' ')[1] for l in icon_lines if body(l).startswith('I gen-folder '))
expect('icon-empty', edit(lines, icon, lambda f: f.update(cov='0', rgb='000000', a='00', ag='0' * 64, lg='8' * 64),
kind='I'), icon, 'EMPTY')
expect('icon-black', edit(lines, icon, lambda f: f.update(rgb='020202'), kind='I'), icon, 'BLACK')
expect('icon-partial', edit(lines, icon, lambda f: f.update(ag='0' * 32 + f['ag'][32:]), kind='I'), icon, 'PARTIAL')
expect('icon-colour', edit(lines, icon, lambda f: f.update(rgb=f['rgb'][4:6] + f['rgb'][2:4] + f['rgb'][0:2]),
kind='I'), icon, 'WRONG-COLOUR')
def console_end_to_end(out, tool, noise_rate):
cl = load('consolelink', ROOT / 'tools/test-console-link.py')
binary = cl.build(out)
cases = 'tex-rgba8s-lin|tex-rg8-near|multi-3|fill'
direct = subprocess.run([str(tool), 'synth', '--repeat', '1', '--cases', cases], capture_output=True, text=True,
timeout=600)
(out / 'console-direct.txt').write_text(direct.stdout)
noise = out / 'noise.txt'
noise.write_text(''.join(f'AdrenoDisplay: perf pace s={i} pps=59.8 late=0 gpu=1/4357\n' for i in range(200)))
guest = out / 'guest'
(guest / 'private/var/tmp').mkdir(parents=True)
h = cl.Harness(binary, out, 'console', '--duration', 1500, '--shell', "PS1='sh-3.2# ' exec /bin/sh -i",
'--loss', 0.03, '--reply-loss', 0.03, '--seed', 47, '--poll-ms', 20,
'--noise', noise, '--noise-rate', noise_rate)
try:
r = subprocess.run([sys.executable, str(ROOT / 'tools/run-icon-pixels.py'), '--target', f'127.0.0.1:{h.port}',
'--session', f'{cl.SESSION:x}', '--owner', '5eedc0de51', '--guest-root', str(guest),
'--tool', str(tool), '--output', str(out / 'console-run'),
'--steps', 'push,version,synth,icons,diag', '--console-user', '0',
'--synth-args', f"--repeat 1 --cases '{cases}'",
'--icons-args', "--cases 'gen-(home|folder)'", '--poll-ms', '20', '--wait-ms', '60',
'--attempts', '5'],
capture_output=True, text=True, timeout=1500, env=dict(os.environ, PYTHONDONTWRITEBYTECODE='1'))
(out / 'console-run.log').write_text(r.stdout + r.stderr)
finally:
h.process.terminate()
h.process.wait(timeout=30)
check(r.returncode == 0, f'run-icon-pixels exit {r.returncode}: {r.stdout[-300:]} {r.stderr[-300:]}')
rec = json.loads((out / 'console-run/run.json').read_text()) if (out / 'console-run/run.json').is_file() else {}
check(rec.get('status') == 'DONE' and rec.get('synth_rc') == 0 and rec.get('version_rc') == 0,
f'console run: status {rec.get("status")} {rec.get("error")} synth rc {rec.get("synth_rc")}')
got = (out / 'console-run/synth.txt').read_text() if (out / 'console-run/synth.txt').is_file() else ''
# Identical but for the elapsed-time field.
same = lambda text: [re.sub(r' ms=\d+$', '', body(l)) for l in kind_lines(text, ('C ', 'K '))]
want = same(direct.stdout)
check(same(got) == want and len(want) == 8,
f'fetched C/K lines differ from a direct run ({len(same(got))} vs {len(want)} lines)')
icons = (out / 'console-run/icons-gen.txt').read_text() if (out / 'console-run/icons-gen.txt').is_file() else ''
check(len(kind_lines(icons, ('I ',))) == 2 and all(' st=ok ' in l for l in kind_lines(icons, ('I ',))),
f'icons step: {kind_lines(icons, ("I ",))}')
check('skipped' in str(rec.get('icons_ws')) and 'diag_reasons' in rec and 'diag_unified' in rec,
f'icons-ws skipped offline and the diag step ran: {rec.get("icons_ws")}')
return dict(push_seconds=rec.get('push_seconds'), synth=rec.get('synth.txt'), chunks=(rec.get('push') or {}).get('chunks'))
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--output', type=Path, required=True)
ap.add_argument('--bundle', type=Path, help='an ADRENO_SETTINGS_ICONS AdrenoMetal.bundle with its compiler runtime')
ap.add_argument('--skip-console', action='store_true')
ap.add_argument('--noise-rate', type=int, default=40)
a = ap.parse_args()
out = a.output.resolve()
out.mkdir(parents=True, exist_ok=False)
if a.bundle:
a.bundle = a.bundle.resolve()
r = subprocess.run([sys.executable, str(ROOT / 'tools/build-icon-pixels.py'), '--output', str(out / 'tool')],
capture_output=True, text=True)
if not check(r.returncode == 0, f'build: {r.stdout[-300:]} {r.stderr[-300:]}'):
return 1
tool = out / 'tool/icon-pixels'
runs = []
for n in range(2):
r = subprocess.run([str(tool), 'synth'] + (['--host-caps', str(a.bundle)] if a.bundle else []),
capture_output=True, text=True, timeout=600)
(out / f'host-synth-{n}.txt').write_text(r.stdout)
check(r.returncode == 0, f'host synth run {n} exit {r.returncode}: {r.stderr[-300:]}')
runs.append(r.stdout)
pixels = lambda text: [re.sub(r' ms=\d+$', '', body(l)) for l in kind_lines(text, ('C ',))]
check(pixels(runs[0]) and pixels(runs[0]) == pixels(runs[1]), 'two runs on this Mac\'s GPU must print the same pixels')
rc = subprocess.run([sys.executable, str(ROOT / 'tools/compare-icon-pixels.py'), str(REFERENCE), str(out / 'host-synth-0.txt'),
'--tolerance', '0'], capture_output=True, text=True)
(out / 'reference-check.txt').write_text(rc.stdout)
check(rc.returncode == 0, f'this Mac no longer reproduces {REFERENCE.name} exactly: {rc.stdout.strip().splitlines()[-1:]}')
if a.bundle:
for units, label in (('8', 'cs8'), (None, 'cs1')):
argv = [str(tool), 'synth', '--plugin', str(a.bundle)] + (['--cs-samplers', units] if units else [])
p = subprocess.run(argv, capture_output=True, text=True, timeout=900)
(out / f'plugin-{label}.txt').write_text(p.stdout)
check(p.returncode == 0, f'plug-in synth ({label}) exit {p.returncode}: {p.stderr[-300:]}')
c = subprocess.run([sys.executable, str(ROOT / 'tools/compare-icon-pixels.py'), str(REFERENCE),
str(out / f'plugin-{label}.txt'), '--census-only'], capture_output=True, text=True)
(out / f'plugin-{label}.compare.txt').write_text(c.stdout)
if units:
check(c.returncode == 0, f'plug-in census (8 CS sampler units) differs: {c.stdout.strip().splitlines()[-1:]}')
else:
dropped = re.search(r'dropped=(\d+)', p.stdout)
check(c.returncode == 1 and dropped and int(dropped.group(1)) > 0 and 'plugin-diagnostics' in c.stdout,
'without AdrenoIBComputeSamplers the multi-input kernels must be dropped and reported')
ref_lines = [l for l in REFERENCE.read_text().splitlines() if l.strip()]
icons = subprocess.run([str(tool), 'icons', '--size', '20'], capture_output=True, text=True, timeout=600)
icon_lines = [l for l in icons.stdout.splitlines() if l.startswith(('I ', 'K gen-', 'K ws-'))]
check(icons.returncode == 0 and len([l for l in icon_lines if l.startswith('I ')]) >= 10,
f'icons on this Mac: exit {icons.returncode}, {len(icon_lines)} lines')
(out / 'faults').mkdir()
fault_injection(out / 'faults', ref_lines, icon_lines)
console = None
if not a.skip_console:
console = console_end_to_end(out, tool, a.noise_rate)
result = dict(checks=checks, failures=failures, console=console, tool=json.loads((out / 'tool/build.json').read_text()))
(out / 'result.json').write_text(json.dumps(result, indent=1) + '\n')
print(f'{"PASS" if not failures else "FAIL"}: {checks} checks ({len(failures)} failed); {out / "result.json"}')
return 0 if not failures else 1
if __name__ == '__main__':
sys.exit(main())