mirror of
https://github.com/elwrcl/intel-hard-lab.git
synced 2026-10-31 07:48:02 +03:00
first commit
This commit is contained in:
54 files changed
+156785
No files matched your search
+18
@@ -0,0 +1,18 @@
|
||||
/.icd-vendors
|
||||
/ivb-test/result/
|
||||
/cts/cts-run-*
|
||||
/media/*
|
||||
!/media/.gitkeep
|
||||
*.aub
|
||||
*.qpa
|
||||
*.h264
|
||||
*.y4m
|
||||
*.mp4
|
||||
*.o
|
||||
*.log
|
||||
__pycache__/
|
||||
*.pyc
|
||||
/cts-run-*
|
||||
/hangs/
|
||||
/.gcroots/
|
||||
/cts-chunked/
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def meminfo():
|
||||
d = {}
|
||||
with open("/proc/meminfo") as f:
|
||||
for line in f:
|
||||
k, v = line.split(":", 1)
|
||||
d[k.strip()] = int(v.split()[0])
|
||||
return d
|
||||
|
||||
|
||||
def main():
|
||||
outdir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd() / "hangs"
|
||||
poll = int(sys.argv[2]) if len(sys.argv) > 2 else 10
|
||||
|
||||
err = ""
|
||||
for c in sorted(glob.glob("/sys/class/drm/card*/error")):
|
||||
err = c
|
||||
break
|
||||
|
||||
if not err:
|
||||
print("no /sys/class/drm/card*/error — is i915 loaded?", file=sys.stderr)
|
||||
elif not os.access(err, os.R_OK):
|
||||
print(f"cannot read {err} — rerun with sudo for GPU hang capture", file=sys.stderr)
|
||||
err = ""
|
||||
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
hangs_txt = outdir / "hangs.txt"
|
||||
mem_log = outdir / "mem.log"
|
||||
|
||||
print(f"watching {err or '(memory only)'} every {poll}s -> {outdir}")
|
||||
with open(hangs_txt, "a") as f:
|
||||
f.write(f"{now_iso()} watch started\n")
|
||||
|
||||
n = 0
|
||||
while True:
|
||||
if err and os.path.getsize(err) > 0:
|
||||
n += 1
|
||||
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
hang_f = outdir / f"hang-{stamp}.txt"
|
||||
data = open(err, "rb").read()
|
||||
hang_f.write_bytes(data)
|
||||
|
||||
with open(hangs_txt, "a") as hf:
|
||||
for line in data.decode(errors="replace").splitlines():
|
||||
if line.startswith(("GPU HANG", "Kernel", "Time")):
|
||||
hf.write(line + "\n")
|
||||
break
|
||||
msg = f"{now_iso()} hang #{n} -> {hang_f} ({hang_f.stat().st_size} B)"
|
||||
hf.write(msg + "\n")
|
||||
print(msg)
|
||||
|
||||
dmesg = subprocess.run(["dmesg"], capture_output=True, text=True).stdout
|
||||
matched = [l for l in dmesg.splitlines()
|
||||
if any(k.lower() in l.lower() for k in ("GPU HANG", "reset", "hangcheck"))]
|
||||
(outdir / f"dmesg-{stamp}.txt").write_text("\n".join(matched[-20:]) + ("\n" if matched else ""))
|
||||
|
||||
with open(err, "w") as ef:
|
||||
ef.write("1")
|
||||
|
||||
mi = meminfo()
|
||||
avail = mi.get("MemAvailable", 0)
|
||||
swtot = mi.get("SwapTotal", 0)
|
||||
swfree = mi.get("SwapFree", 0)
|
||||
sunrecl = mi.get("SUnreclaim", 0)
|
||||
ptbl = mi.get("PageTables", 0)
|
||||
|
||||
ps = subprocess.run(["ps", "-eo", "rss,comm", "--sort=-rss", "--no-headers"],
|
||||
capture_output=True, text=True).stdout
|
||||
top3 = []
|
||||
for line in ps.splitlines()[:3]:
|
||||
parts = line.split(None, 1)
|
||||
if len(parts) == 2:
|
||||
rss, comm = parts
|
||||
top3.append(f"{comm}:{int(rss) // 1024}M")
|
||||
top3_str = " ".join(top3) + (" " if top3 else "")
|
||||
|
||||
line = (f"{now_iso()} avail={avail // 1024}M swapused={(swtot - swfree) // 1024}M "
|
||||
f"sunreclaim={sunrecl // 1024}M pagetables={ptbl // 1024}M {top3_str}\n")
|
||||
with open(mem_log, "a") as mf:
|
||||
mf.write(line)
|
||||
try:
|
||||
subprocess.run(["sync", "-d", str(mem_log)], check=True,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessError:
|
||||
subprocess.run(["sync"])
|
||||
|
||||
time.sleep(poll)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def mem_available_mb():
|
||||
with open("/proc/meminfo") as f:
|
||||
for line in f:
|
||||
if line.startswith("MemAvailable:"):
|
||||
return int(line.split()[1]) // 1024
|
||||
return None
|
||||
|
||||
|
||||
def proc_alive(name):
|
||||
return subprocess.run(["pgrep", "-x", name],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||
|
||||
|
||||
def main():
|
||||
threshold = int(sys.argv[1]) if len(sys.argv) > 1 else 1500
|
||||
proc = sys.argv[2] if len(sys.argv) > 2 else "deqp-vk"
|
||||
poll = int(sys.argv[3]) if len(sys.argv) > 3 else 3
|
||||
|
||||
print(f"guarding: kill {proc} when MemAvailable < {threshold}M (poll {poll}s)")
|
||||
|
||||
while True:
|
||||
avail = mem_available_mb()
|
||||
if avail is not None and avail < threshold:
|
||||
print(f"{now_iso()} avail={avail}M below {threshold}M — terminating {proc}")
|
||||
subprocess.run(["pkill", "-TERM", "-x", proc])
|
||||
time.sleep(10)
|
||||
if proc_alive(proc):
|
||||
print("still alive, SIGKILL")
|
||||
subprocess.run(["pkill", "-KILL", "-x", proc])
|
||||
|
||||
outdir = os.environ.get("OUTDIR", ".")
|
||||
with open(os.path.join(outdir, "mem-guard.log"), "a") as f:
|
||||
f.write(f"{now_iso()} killed {proc} at avail={avail}M\n")
|
||||
subprocess.run(["sync"])
|
||||
sys.exit(1)
|
||||
|
||||
time.sleep(poll)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
import collections
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def wc_l(path):
|
||||
if not os.path.exists(path):
|
||||
return 0
|
||||
with open(path, "rb") as f:
|
||||
return sum(1 for _ in f)
|
||||
|
||||
|
||||
def read_lines(path):
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
with open(path) as f:
|
||||
return [l.rstrip("\n") for l in f if l.strip()]
|
||||
|
||||
|
||||
def write_lines(path, lines):
|
||||
with open(path, "w") as f:
|
||||
for l in lines:
|
||||
f.write(l + "\n")
|
||||
|
||||
|
||||
def begin_test_case_names(qpa_path):
|
||||
names = set()
|
||||
if not os.path.exists(qpa_path):
|
||||
return names
|
||||
txt = open(qpa_path, errors="replace").read()
|
||||
for line in txt.splitlines():
|
||||
if line.startswith("#beginTestCaseResult "):
|
||||
names.add(line[len("#beginTestCaseResult "):].strip())
|
||||
return names
|
||||
|
||||
|
||||
def main():
|
||||
lab_root = Path(os.environ.get("LAB_ROOT", Path(__file__).resolve().parent.parent))
|
||||
drivers_root = Path(os.environ.get("DRIVERS_ROOT", lab_root.parent))
|
||||
mesa_src = Path(os.environ.get("MESA_SRC", drivers_root / "intel-hard-graphics"))
|
||||
mesa_build = Path(os.environ.get("MESA_BUILD", mesa_src / "build"))
|
||||
cts_dir = Path(os.environ.get("CTS_DIR", mesa_src / "VK-GL-CTS/build-cts/external/vulkancts/modules/vulkan"))
|
||||
cts_mustpass = Path(os.environ.get("CTS_MUSTPASS", mesa_src / "VK-GL-CTS/external/vulkancts/mustpass/main/vk-default"))
|
||||
icd = Path(os.environ.get("VK_DRIVER_FILES", mesa_build / "src/intel/vulkan_hasvk/intel_hasvk13_devenv_icd.x86_64.json"))
|
||||
exclude = Path(os.environ.get("EXCLUDE", lab_root / "cts/cts-exclude-gen7.txt"))
|
||||
|
||||
scope = sys.argv[1] if len(sys.argv) > 1 else "mustpass"
|
||||
chunk_size = int(sys.argv[2]) if len(sys.argv) > 2 else 100000
|
||||
pause = int(sys.argv[3]) if len(sys.argv) > 3 else 30
|
||||
outdir = Path(os.environ.get("OUTDIR", Path.cwd() / "cts-chunked"))
|
||||
|
||||
stop_on_hang = os.environ.get("STOP_ON_HANG", "0") == "1"
|
||||
max_hangs = int(os.environ.get("MAX_HANGS", "3"))
|
||||
max_chunks = int(os.environ.get("MAX_CHUNKS", "3"))
|
||||
max_subruns = int(os.environ.get("MAX_SUBRUNS", "40"))
|
||||
hangs = 0
|
||||
|
||||
deqp = cts_dir / "deqp-vk"
|
||||
for p in (deqp, icd):
|
||||
if not p.exists():
|
||||
print(f"missing: {p}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
os.chdir(cts_dir)
|
||||
|
||||
src_txt = outdir / "src.txt"
|
||||
cases_txt = outdir / "cases.txt"
|
||||
if not (src_txt.exists() and src_txt.stat().st_size > 0):
|
||||
if scope == "all":
|
||||
out = subprocess.run([str(deqp), "--deqp-runmode=stdout-caselist"],
|
||||
capture_output=True, text=True).stdout
|
||||
lines = [l[len("TEST: "):] for l in out.splitlines() if l.startswith("TEST: ")]
|
||||
write_lines(src_txt, lines)
|
||||
else:
|
||||
lines = []
|
||||
for f in sorted(glob.glob(str(cts_mustpass / "*.txt"))):
|
||||
lines.extend(l for l in read_lines(f) if l.startswith("dEQP-VK"))
|
||||
write_lines(src_txt, lines)
|
||||
|
||||
if exclude.exists() and exclude.stat().st_size > 0:
|
||||
excl = set(read_lines(exclude))
|
||||
write_lines(cases_txt, [l for l in read_lines(src_txt) if l not in excl])
|
||||
else:
|
||||
write_lines(cases_txt, read_lines(src_txt))
|
||||
|
||||
todo_txt = outdir / "todo.txt"
|
||||
prev = sorted(glob.glob(str(outdir / "chunk-*.qpa")))
|
||||
if prev:
|
||||
done = set()
|
||||
for f in prev:
|
||||
done |= begin_test_case_names(f)
|
||||
done_txt = outdir / "done.txt"
|
||||
write_lines(done_txt, sorted(done))
|
||||
cases = read_lines(cases_txt)
|
||||
todo = [l for l in cases if l not in done]
|
||||
write_lines(todo_txt, todo)
|
||||
print(f"resume: {len(done)} recorded, {len(todo)} left")
|
||||
else:
|
||||
cases = read_lines(cases_txt)
|
||||
write_lines(todo_txt, cases)
|
||||
print(f"{len(cases)} cases to run")
|
||||
|
||||
if not (todo_txt.exists() and todo_txt.stat().st_size > 0):
|
||||
print("nothing left to run — summarising")
|
||||
|
||||
todo = read_lines(todo_txt)
|
||||
total = len(todo)
|
||||
n = 0
|
||||
start = 1
|
||||
ran = 0
|
||||
rc = 0
|
||||
sub = 0
|
||||
|
||||
while todo and start <= total:
|
||||
n += 1
|
||||
while glob.glob(str(outdir / f"chunk-{n}.*.qpa")):
|
||||
n += 1
|
||||
|
||||
list_path = outdir / f"chunk-{n}.txt"
|
||||
chunk_lines = todo[start - 1: start - 1 + chunk_size]
|
||||
write_lines(list_path, chunk_lines)
|
||||
if not chunk_lines:
|
||||
break
|
||||
|
||||
print(f"chunk {n}: {len(chunk_lines)} cases (from line {start} of {total})")
|
||||
want = len(chunk_lines)
|
||||
work_path = outdir / f"chunk-{n}.work"
|
||||
write_lines(work_path, chunk_lines)
|
||||
sub = 0
|
||||
|
||||
while work_path.exists() and work_path.stat().st_size > 0:
|
||||
sub += 1
|
||||
if sub > max_subruns:
|
||||
print(f" gave up after {max_subruns} subruns")
|
||||
break
|
||||
|
||||
qpa = outdir / f"chunk-{n}.{sub}.qpa"
|
||||
log = outdir / f"chunk-{n}.{sub}.log"
|
||||
env = dict(os.environ, VK_DRIVER_FILES=str(icd))
|
||||
with open(log, "w") as lf:
|
||||
proc = subprocess.run(
|
||||
[str(deqp), f"--deqp-caselist-file={work_path}",
|
||||
f"--deqp-log-filename={qpa}",
|
||||
"--deqp-log-images=disable", "--deqp-log-shader-sources=disable"],
|
||||
stdout=lf, stderr=subprocess.STDOUT, env=env)
|
||||
rc = proc.returncode
|
||||
|
||||
work_lines = read_lines(work_path)
|
||||
left = len(work_lines)
|
||||
got = sum(1 for l in open(qpa, errors="replace").read().splitlines()
|
||||
if l.startswith("#beginTestCaseResult")) if qpa.exists() else 0
|
||||
if got >= left:
|
||||
break
|
||||
|
||||
done_now = begin_test_case_names(qpa)
|
||||
next_lines = [l for l in work_lines if l not in done_now]
|
||||
if len(next_lines) == left:
|
||||
culprit = work_lines[0] if work_lines else ""
|
||||
with open(outdir / "crashers.txt", "a") as cf:
|
||||
cf.write(f"{now_iso()} crashed before recording: {culprit}\n")
|
||||
next_lines = work_lines[1:]
|
||||
write_lines(work_path, next_lines)
|
||||
print(f" subrun {sub}: rc={rc}, {got} done, {len(next_lines)} left — restarting")
|
||||
time.sleep(pause)
|
||||
|
||||
for suffix in (".work", ".work.done", ".work.next"):
|
||||
p = outdir / f"chunk-{n}{suffix}"
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
|
||||
done_n = 0
|
||||
bad_log = False
|
||||
for f in sorted(glob.glob(str(outdir / f"chunk-{n}.*.qpa"))):
|
||||
done_n += sum(1 for l in open(f, errors="replace").read().splitlines()
|
||||
if l.startswith("#beginTestCaseResult"))
|
||||
for f in sorted(glob.glob(str(outdir / f"chunk-{n}.*.log"))):
|
||||
if "DeviceLost" in open(f, errors="replace").read():
|
||||
bad_log = True
|
||||
print(f" rc={rc}, {done_n}/{want} results ({sub} subrun)")
|
||||
|
||||
bad = done_n < want or bad_log
|
||||
wait_s = pause
|
||||
if bad:
|
||||
hangs += 1
|
||||
with open(outdir / "hangs.txt", "a") as hf:
|
||||
hf.write(f"{now_iso()} chunk {n} ended badly (rc={rc})\n")
|
||||
if stop_on_hang:
|
||||
print(" chunk ended badly — stopping (STOP_ON_HANG=1)")
|
||||
print(" rerun the same command to continue from here")
|
||||
break
|
||||
wait_s = pause * 4
|
||||
print(f" chunk ended badly (#{hangs}) — cooling down {wait_s}s")
|
||||
if max_hangs > 0 and hangs >= max_hangs:
|
||||
print(f" {hangs} bad chunks — stopping (MAX_HANGS={max_hangs})")
|
||||
break
|
||||
|
||||
start += chunk_size
|
||||
ran += 1
|
||||
if max_chunks > 0 and ran >= max_chunks and start <= total:
|
||||
print()
|
||||
print(f"{ran} chunks done this boot (MAX_CHUNKS={max_chunks}) — stopping here.")
|
||||
print(f"REBOOT, then rerun the same command to continue from case {start}.")
|
||||
break
|
||||
if start <= total:
|
||||
time.sleep(wait_s)
|
||||
|
||||
res = {}
|
||||
for f in sorted(glob.glob(str(outdir / "chunk-*.qpa"))):
|
||||
txt = open(f, errors="replace").read()
|
||||
for block in re.split(r"#beginTestCaseResult ", txt)[1:]:
|
||||
name = block.split("\n", 1)[0].strip()
|
||||
m = re.search(r'<Result StatusCode="(\w+)">', block)
|
||||
res[name] = m.group(1) if m else "Crash"
|
||||
|
||||
counts = collections.Counter(res.values())
|
||||
print(f"\n{sum(counts.values())} results")
|
||||
for k, v in counts.most_common():
|
||||
print(f" {k:16} {v}")
|
||||
|
||||
with open(outdir / "failures.txt", "w") as fh:
|
||||
for name, status in sorted(res.items()):
|
||||
if status not in ("Pass", "NotSupported"):
|
||||
fh.write(f"{status}\t{name}\n")
|
||||
print(f"\nfailures -> {outdir}/failures.txt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
import collections
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def read_lines(path):
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
with open(path) as f:
|
||||
return [l.rstrip("\n") for l in f if l.strip()]
|
||||
|
||||
|
||||
def write_lines(path, lines):
|
||||
with open(path, "w") as f:
|
||||
for l in lines:
|
||||
f.write(l + "\n")
|
||||
|
||||
|
||||
def begin_test_case_names(qpa_path):
|
||||
names = set()
|
||||
if not os.path.exists(qpa_path):
|
||||
return names
|
||||
txt = open(qpa_path, errors="replace").read()
|
||||
for line in txt.splitlines():
|
||||
if line.startswith("#beginTestCaseResult "):
|
||||
names.add(line[len("#beginTestCaseResult "):].strip())
|
||||
return names
|
||||
|
||||
|
||||
def last_begin_test_case_name(qpa_path):
|
||||
last = None
|
||||
if not os.path.exists(qpa_path):
|
||||
return last
|
||||
for line in open(qpa_path, errors="replace"):
|
||||
if line.startswith("#beginTestCaseResult "):
|
||||
last = line[len("#beginTestCaseResult "):].strip()
|
||||
return last
|
||||
|
||||
|
||||
def nproc():
|
||||
return os.cpu_count() or 1
|
||||
|
||||
|
||||
def main():
|
||||
lab_root = Path(os.environ.get("LAB_ROOT", Path(__file__).resolve().parent.parent))
|
||||
drivers_root = Path(os.environ.get("DRIVERS_ROOT", lab_root.parent))
|
||||
mesa_src = Path(os.environ.get("MESA_SRC", drivers_root / "intel-hard-graphics"))
|
||||
mesa_build = Path(os.environ.get("MESA_BUILD", mesa_src / "build"))
|
||||
cts_dir = Path(os.environ.get("CTS_DIR", mesa_src / "VK-GL-CTS/build-cts/external/vulkancts/modules/vulkan"))
|
||||
cts_mustpass = Path(os.environ.get("CTS_MUSTPASS", mesa_src / "VK-GL-CTS/external/vulkancts/mustpass/main/vk-default"))
|
||||
icd = Path(os.environ.get("VK_DRIVER_FILES", mesa_build / "src/intel/vulkan_hasvk/intel_hasvk13_devenv_icd.x86_64.json"))
|
||||
exclude = lab_root / "cts/cts-exclude-gen7.txt"
|
||||
|
||||
scope = sys.argv[1] if len(sys.argv) > 1 else "mustpass"
|
||||
shards = int(sys.argv[2]) if len(sys.argv) > 2 else nproc()
|
||||
cooldown = int(os.environ.get("COOLDOWN", "60"))
|
||||
outdir = Path(os.environ.get(
|
||||
"OUTDIR", Path.cwd() / f"cts-run-{datetime.now().strftime('%Y%m%d-%H%M%S')}"))
|
||||
|
||||
deqp = cts_dir / "deqp-vk"
|
||||
for p in (deqp, icd):
|
||||
if not p.exists():
|
||||
print(f"missing: {p}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
os.chdir(cts_dir)
|
||||
|
||||
cases_txt = outdir / "cases.txt"
|
||||
if scope == "all":
|
||||
out = subprocess.run([str(deqp), "--deqp-runmode=stdout-caselist"],
|
||||
capture_output=True, text=True).stdout
|
||||
lines = [l[len("TEST: "):] for l in out.splitlines() if l.startswith("TEST: ")]
|
||||
src_txt = outdir / "all.txt"
|
||||
write_lines(src_txt, lines)
|
||||
else:
|
||||
lines = []
|
||||
for f in sorted(glob.glob(str(cts_mustpass / "*.txt"))):
|
||||
lines.extend(l for l in read_lines(f) if l.startswith("dEQP-VK"))
|
||||
src_txt = outdir / "mustpass.txt"
|
||||
write_lines(src_txt, lines)
|
||||
|
||||
src_lines = read_lines(src_txt)
|
||||
if exclude.exists() and exclude.stat().st_size > 0:
|
||||
excl = set(read_lines(exclude))
|
||||
case_lines = [l for l in src_lines if l not in excl]
|
||||
else:
|
||||
case_lines = list(src_lines)
|
||||
write_lines(cases_txt, case_lines)
|
||||
print(f"{len(src_lines)} cases, {len(src_lines) - len(case_lines)} excluded, {len(case_lines)} to run")
|
||||
|
||||
prev = sorted(glob.glob(str(outdir / "*.qpa"))) + sorted(glob.glob(str(outdir / "attempt-*/*.qpa")))
|
||||
if prev:
|
||||
done = set()
|
||||
for f in prev:
|
||||
done |= begin_test_case_names(f)
|
||||
done_txt = outdir / "done.txt"
|
||||
write_lines(done_txt, sorted(done))
|
||||
if done:
|
||||
case_lines = [l for l in read_lines(cases_txt) if l not in done]
|
||||
write_lines(cases_txt, case_lines)
|
||||
print(f"resume: {len(done)} already recorded, {len(case_lines)} left")
|
||||
|
||||
n = 1
|
||||
while (outdir / f"attempt-{n}").exists():
|
||||
n += 1
|
||||
attempt_dir = outdir / f"attempt-{n}"
|
||||
attempt_dir.mkdir(parents=True, exist_ok=True)
|
||||
for f in list(glob.glob(str(outdir / "shard*.qpa"))) + list(glob.glob(str(outdir / "shard*.log"))):
|
||||
os.rename(f, attempt_dir / os.path.basename(f))
|
||||
print(f"previous attempt parked in attempt-{n}/")
|
||||
|
||||
case_lines = read_lines(cases_txt)
|
||||
if not case_lines:
|
||||
print("nothing left to run")
|
||||
sys.exit(0)
|
||||
|
||||
def run_shard(i):
|
||||
list_path = outdir / f"shard{i}.txt"
|
||||
shard_lines = [l for j, l in enumerate(case_lines) if (j + 1) % shards == i]
|
||||
write_lines(list_path, shard_lines)
|
||||
pass_n = 1
|
||||
|
||||
while list_path.exists() and list_path.stat().st_size > 0:
|
||||
qpa = outdir / f"shard{i}.pass{pass_n}.qpa"
|
||||
log = outdir / f"shard{i}.pass{pass_n}.log"
|
||||
env = dict(os.environ, VK_DRIVER_FILES=str(icd))
|
||||
with open(log, "w") as lf:
|
||||
subprocess.run(
|
||||
[str(deqp), f"--deqp-caselist-file={list_path}",
|
||||
f"--deqp-log-filename={qpa}",
|
||||
"--deqp-log-images=disable", "--deqp-log-shader-sources=disable"],
|
||||
stdout=lf, stderr=subprocess.STDOUT, env=env)
|
||||
|
||||
log_text = open(log, errors="replace").read() if log.exists() else ""
|
||||
if "ABORTED" not in log_text:
|
||||
break
|
||||
|
||||
last = last_begin_test_case_name(qpa)
|
||||
if not last:
|
||||
break
|
||||
|
||||
print(f"shard {i} aborted at {last} -- cooling down {cooldown}s", file=sys.stderr)
|
||||
with open(outdir / "aborted.txt", "a") as af:
|
||||
af.write(last + "\n")
|
||||
time.sleep(cooldown)
|
||||
|
||||
shard_lines = read_lines(list_path)
|
||||
seen = False
|
||||
remaining = []
|
||||
for l in shard_lines:
|
||||
if seen:
|
||||
remaining.append(l)
|
||||
if l == last:
|
||||
seen = True
|
||||
write_lines(list_path, remaining)
|
||||
pass_n += 1
|
||||
|
||||
threads = [threading.Thread(target=run_shard, args=(i,)) for i in range(shards)]
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
res = {}
|
||||
for f in sorted(glob.glob(str(outdir / "shard*.qpa"))) + sorted(glob.glob(str(outdir / "attempt-*/shard*.qpa"))):
|
||||
txt = open(f, errors="replace").read()
|
||||
for block in re.split(r"#beginTestCaseResult ", txt)[1:]:
|
||||
name = block.split("\n", 1)[0].strip()
|
||||
m = re.search(r'<Result StatusCode="(\w+)">', block)
|
||||
if m:
|
||||
res[name] = m.group(1)
|
||||
|
||||
counts = collections.Counter(res.values())
|
||||
print(f"\n{sum(counts.values())} results")
|
||||
for k, v in counts.most_common():
|
||||
print(f" {k:16} {v}")
|
||||
|
||||
with open(outdir / "failures.txt", "w") as fh:
|
||||
for name, status in sorted(res.items()):
|
||||
if status not in ("Pass", "NotSupported"):
|
||||
fh.write(f"{status}\t{name}\n")
|
||||
print(f"\nfailures -> {outdir}/failures.txt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
||||
2026-08-20T16:08:40+03:00 chunk 1 ended badly (rc=1)
|
||||
2026-08-20T16:13:52+03:00 chunk 2 ended badly (rc=1)
|
||||
2026-08-20T16:24:23+03:00 chunk 3 ended badly (rc=1)
|
||||
2026-08-20T18:03:06+03:00 chunk 6 ended badly (rc=1)
|
||||
2026-08-20T18:48:18+03:00 chunk 10 ended badly (rc=139)
|
||||
2026-08-20T18:54:08+03:00 chunk 11 ended badly (rc=139)
|
||||
2026-08-20T20:28:44+03:00 chunk 9 ended badly (rc=0)
|
||||
2026-08-20T21:15:33+03:00 chunk 13 ended badly (rc=1)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Symlink
+1
@@ -0,0 +1 @@
|
||||
/nix/store/jr8wwd19z6yvsfxykva6brazipm06r9b-pocl-7.1/etc/OpenCL/vendors/pocl.icd
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,964 @@
|
||||
/* vulkan H.264 encode test for hasvk.
|
||||
* ./hasvk_encode_test # flat-gray 176x144, dumps /tmp/hasvk_enc_out.h264
|
||||
* HASVK_ENC_GRADIENT=1 ./hasvk_encode_test # luma gradient (real residuals)
|
||||
* HASVK_ENC_GRADIENT=1 HASVK_ENC_CHROMA=1 ./... # + chroma gradient
|
||||
* validate the output with ffmpeg:
|
||||
* ffmpeg -i /tmp/hasvk_enc_out.h264 -f rawvideo -pix_fmt yuv420p out.yuv
|
||||
* The stream prepends the driver's own SPS/PPS
|
||||
* (vkGetEncodedVideoSessionParametersKHR) so it decodes standalone.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vk_video/vulkan_video_codec_h264std.h>
|
||||
#include <vk_video/vulkan_video_codec_h264std_encode.h>
|
||||
|
||||
#define CHECK(x) do { \
|
||||
VkResult _r = (x); \
|
||||
if (_r != VK_SUCCESS) { \
|
||||
fprintf(stderr, "FAIL %s:%d: %s -> %d\n", __FILE__, __LINE__, #x, _r); \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define WIDTH 176
|
||||
#define HEIGHT 144
|
||||
#define WIDTH_MBS (WIDTH / 16)
|
||||
#define HEIGHT_MBS (HEIGHT / 16)
|
||||
#define LUMA_SZ (WIDTH * HEIGHT)
|
||||
#define CHROMA_SZ ((WIDTH / 2) * (HEIGHT / 2) * 2)
|
||||
#define FRAME_SZ (LUMA_SZ + CHROMA_SZ)
|
||||
|
||||
static int
|
||||
load_frame(const char *path, int idx, int i420, uint8_t *dst)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) { fprintf(stderr, "cannot open '%s'\n", path); return 0; }
|
||||
if (fseek(f, (long)idx * FRAME_SZ, SEEK_SET) != 0) {
|
||||
fprintf(stderr, "'%s': cannot seek to frame %d\n", path, idx);
|
||||
fclose(f); return 0;
|
||||
}
|
||||
uint8_t buf[FRAME_SZ];
|
||||
size_t n = fread(buf, 1, FRAME_SZ, f);
|
||||
fclose(f);
|
||||
if (n != FRAME_SZ) {
|
||||
fprintf(stderr, "'%s': frame %d short read (%zu of %d)\n", path, idx, n, FRAME_SZ);
|
||||
return 0;
|
||||
}
|
||||
memcpy(dst, buf, LUMA_SZ);
|
||||
if (i420) {
|
||||
const uint8_t *cb = buf + LUMA_SZ, *cr = cb + CHROMA_SZ / 2;
|
||||
for (int i = 0; i < CHROMA_SZ / 2; i++) {
|
||||
dst[LUMA_SZ + i * 2 + 0] = cb[i];
|
||||
dst[LUMA_SZ + i * 2 + 1] = cr[i];
|
||||
}
|
||||
} else {
|
||||
memcpy(dst + LUMA_SZ, buf + LUMA_SZ, CHROMA_SZ);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
shift_frame(const uint8_t *src, uint8_t *dst, int dx, int dy)
|
||||
{
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
int sy = y - dy; sy = sy < 0 ? 0 : (sy >= HEIGHT ? HEIGHT - 1 : sy);
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
int sx = x - dx; sx = sx < 0 ? 0 : (sx >= WIDTH ? WIDTH - 1 : sx);
|
||||
dst[y * WIDTH + x] = src[sy * WIDTH + sx];
|
||||
}
|
||||
}
|
||||
int cw = WIDTH / 2, ch = HEIGHT / 2, cdx = dx / 2, cdy = dy / 2;
|
||||
for (int y = 0; y < ch; y++) {
|
||||
int sy = y - cdy; sy = sy < 0 ? 0 : (sy >= ch ? ch - 1 : sy);
|
||||
for (int x = 0; x < cw; x++) {
|
||||
int sx = x - cdx; sx = sx < 0 ? 0 : (sx >= cw ? cw - 1 : sx);
|
||||
dst[LUMA_SZ + (y * cw + x) * 2 + 0] = src[LUMA_SZ + (sy * cw + sx) * 2 + 0];
|
||||
dst[LUMA_SZ + (y * cw + x) * 2 + 1] = src[LUMA_SZ + (sy * cw + sx) * 2 + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR pGetPhysicalDeviceVideoCapabilitiesKHR;
|
||||
static PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR pGetPhysicalDeviceVideoFormatPropertiesKHR;
|
||||
static PFN_vkCreateVideoSessionKHR pCreateVideoSessionKHR;
|
||||
static PFN_vkDestroyVideoSessionKHR pDestroyVideoSessionKHR;
|
||||
static PFN_vkGetVideoSessionMemoryRequirementsKHR pGetVideoSessionMemoryRequirementsKHR;
|
||||
static PFN_vkBindVideoSessionMemoryKHR pBindVideoSessionMemoryKHR;
|
||||
static PFN_vkCreateVideoSessionParametersKHR pCreateVideoSessionParametersKHR;
|
||||
static PFN_vkDestroyVideoSessionParametersKHR pDestroyVideoSessionParametersKHR;
|
||||
static PFN_vkGetEncodedVideoSessionParametersKHR pGetEncodedVideoSessionParametersKHR;
|
||||
static PFN_vkCmdBeginVideoCodingKHR pCmdBeginVideoCodingKHR;
|
||||
static PFN_vkCmdEndVideoCodingKHR pCmdEndVideoCodingKHR;
|
||||
static PFN_vkCmdEncodeVideoKHR pCmdEncodeVideoKHR;
|
||||
|
||||
static uint32_t
|
||||
find_memory_type(VkPhysicalDeviceMemoryProperties *mp, uint32_t type_bits, VkMemoryPropertyFlags want)
|
||||
{
|
||||
for (uint32_t i = 0; i < mp->memoryTypeCount; i++) {
|
||||
if ((type_bits & (1u << i)) &&
|
||||
(mp->memoryTypes[i].propertyFlags & want) == want)
|
||||
return i;
|
||||
}
|
||||
/* fall back: any type in the mask */
|
||||
for (uint32_t i = 0; i < mp->memoryTypeCount; i++) {
|
||||
if (type_bits & (1u << i))
|
||||
return i;
|
||||
}
|
||||
fprintf(stderr, "FAIL: no matching memory type for bits 0x%x\n", type_bits);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static VkDeviceMemory
|
||||
alloc_and_bind_buffer(VkDevice dev, VkPhysicalDeviceMemoryProperties *mp,
|
||||
VkBuffer buf, VkMemoryPropertyFlags want)
|
||||
{
|
||||
VkMemoryRequirements req;
|
||||
vkGetBufferMemoryRequirements(dev, buf, &req);
|
||||
VkMemoryAllocateInfo ai = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = req.size,
|
||||
.memoryTypeIndex = find_memory_type(mp, req.memoryTypeBits, want),
|
||||
};
|
||||
VkDeviceMemory mem;
|
||||
CHECK(vkAllocateMemory(dev, &ai, NULL, &mem));
|
||||
CHECK(vkBindBufferMemory(dev, buf, mem, 0));
|
||||
return mem;
|
||||
}
|
||||
|
||||
static VkDeviceMemory
|
||||
alloc_and_bind_image(VkDevice dev, VkPhysicalDeviceMemoryProperties *mp, VkImage img)
|
||||
{
|
||||
VkMemoryRequirements req;
|
||||
vkGetImageMemoryRequirements(dev, img, &req);
|
||||
VkMemoryAllocateInfo ai = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = req.size,
|
||||
.memoryTypeIndex = find_memory_type(mp, req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
|
||||
};
|
||||
VkDeviceMemory mem;
|
||||
CHECK(vkAllocateMemory(dev, &ai, NULL, &mem));
|
||||
CHECK(vkBindImageMemory(dev, img, mem, 0));
|
||||
return mem;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
/* instance */
|
||||
VkApplicationInfo app = {
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.apiVersion = VK_API_VERSION_1_3,
|
||||
};
|
||||
VkInstanceCreateInfo ici = {
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
.pApplicationInfo = &app,
|
||||
};
|
||||
VkInstance instance;
|
||||
CHECK(vkCreateInstance(&ici, NULL, &instance));
|
||||
|
||||
uint32_t pdev_count = 0;
|
||||
CHECK(vkEnumeratePhysicalDevices(instance, &pdev_count, NULL));
|
||||
if (pdev_count == 0) { fprintf(stderr, "no physical devices\n"); return 1; }
|
||||
VkPhysicalDevice *pdevs = calloc(pdev_count, sizeof(*pdevs));
|
||||
CHECK(vkEnumeratePhysicalDevices(instance, &pdev_count, pdevs));
|
||||
VkPhysicalDevice pdev = pdevs[0];
|
||||
|
||||
VkPhysicalDeviceProperties pprops;
|
||||
vkGetPhysicalDeviceProperties(pdev, &pprops);
|
||||
printf("Device: %s\n", pprops.deviceName);
|
||||
|
||||
VkPhysicalDeviceMemoryProperties mem_props;
|
||||
vkGetPhysicalDeviceMemoryProperties(pdev, &mem_props);
|
||||
|
||||
/* find encode queue family */
|
||||
uint32_t qf_count = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties2(pdev, &qf_count, NULL);
|
||||
VkQueueFamilyVideoPropertiesKHR *video_props = calloc(qf_count, sizeof(*video_props));
|
||||
VkQueueFamilyProperties2 *qfs = calloc(qf_count, sizeof(*qfs));
|
||||
for (uint32_t i = 0; i < qf_count; i++) {
|
||||
video_props[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR;
|
||||
qfs[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
|
||||
qfs[i].pNext = &video_props[i];
|
||||
}
|
||||
vkGetPhysicalDeviceQueueFamilyProperties2(pdev, &qf_count, qfs);
|
||||
|
||||
int32_t encode_qf = -1;
|
||||
int32_t transfer_qf = -1;
|
||||
for (uint32_t i = 0; i < qf_count; i++) {
|
||||
if ((qfs[i].queueFamilyProperties.queueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR) &&
|
||||
(video_props[i].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR)) {
|
||||
encode_qf = (int32_t)i;
|
||||
}
|
||||
if ((qfs[i].queueFamilyProperties.queueFlags & VK_QUEUE_TRANSFER_BIT) && transfer_qf < 0)
|
||||
transfer_qf = (int32_t)i;
|
||||
}
|
||||
if (encode_qf < 0) { fprintf(stderr, "no H.264 encode queue family found\n"); return 1; }
|
||||
if (transfer_qf < 0) { fprintf(stderr, "no transfer-capable queue family found\n"); return 1; }
|
||||
printf("encode queue family = %d, transfer queue family = %d\n", encode_qf, transfer_qf);
|
||||
|
||||
/* ---- Device ---- */
|
||||
const char *dev_exts[] = {
|
||||
"VK_KHR_video_queue",
|
||||
"VK_KHR_video_encode_queue",
|
||||
"VK_KHR_video_encode_h264",
|
||||
"VK_KHR_synchronization2",
|
||||
};
|
||||
float qprio = 1.0f;
|
||||
VkDeviceQueueCreateInfo qcis[2] = {
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = (uint32_t)transfer_qf,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &qprio,
|
||||
},
|
||||
{
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = (uint32_t)encode_qf,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &qprio,
|
||||
},
|
||||
};
|
||||
VkDeviceCreateInfo dci = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = (encode_qf == transfer_qf) ? 1 : 2,
|
||||
.pQueueCreateInfos = qcis,
|
||||
.enabledExtensionCount = sizeof(dev_exts) / sizeof(dev_exts[0]),
|
||||
.ppEnabledExtensionNames = dev_exts,
|
||||
};
|
||||
VkDevice dev;
|
||||
CHECK(vkCreateDevice(pdev, &dci, NULL, &dev));
|
||||
|
||||
#define LOAD(name) p##name = (PFN_vk##name)vkGetDeviceProcAddr(dev, "vk" #name)
|
||||
LOAD(GetPhysicalDeviceVideoCapabilitiesKHR);
|
||||
LOAD(GetPhysicalDeviceVideoFormatPropertiesKHR);
|
||||
LOAD(CreateVideoSessionKHR);
|
||||
LOAD(DestroyVideoSessionKHR);
|
||||
LOAD(GetVideoSessionMemoryRequirementsKHR);
|
||||
LOAD(BindVideoSessionMemoryKHR);
|
||||
LOAD(CreateVideoSessionParametersKHR);
|
||||
LOAD(DestroyVideoSessionParametersKHR);
|
||||
LOAD(GetEncodedVideoSessionParametersKHR);
|
||||
LOAD(CmdBeginVideoCodingKHR);
|
||||
LOAD(CmdEndVideoCodingKHR);
|
||||
LOAD(CmdEncodeVideoKHR);
|
||||
pGetPhysicalDeviceVideoCapabilitiesKHR =
|
||||
(PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceVideoCapabilitiesKHR");
|
||||
pGetPhysicalDeviceVideoFormatPropertiesKHR =
|
||||
(PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR)vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceVideoFormatPropertiesKHR");
|
||||
#undef LOAD
|
||||
|
||||
VkQueue queue;
|
||||
vkGetDeviceQueue(dev, (uint32_t)encode_qf, 0, &queue);
|
||||
VkQueue transfer_queue;
|
||||
vkGetDeviceQueue(dev, (uint32_t)transfer_qf, 0, &transfer_queue);
|
||||
|
||||
int use_cabac = getenv("HASVK_ENC_CABAC") ? 1 : 0;
|
||||
int use_pframe = getenv("HASVK_ENC_PFRAME") ? 1 : 0;
|
||||
|
||||
/* H.264 encode, 4:2:0, 8-bit */
|
||||
VkVideoEncodeH264ProfileInfoKHR h264_profile = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_INFO_KHR,
|
||||
.stdProfileIdc = use_cabac ? STD_VIDEO_H264_PROFILE_IDC_MAIN
|
||||
: STD_VIDEO_H264_PROFILE_IDC_BASELINE,
|
||||
};
|
||||
VkVideoProfileInfoKHR profile = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR,
|
||||
.pNext = &h264_profile,
|
||||
.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR,
|
||||
.chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR,
|
||||
.lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR,
|
||||
.chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR,
|
||||
};
|
||||
VkVideoProfileListInfoKHR profile_list = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR,
|
||||
.profileCount = 1,
|
||||
.pProfiles = &profile,
|
||||
};
|
||||
|
||||
/* capability */
|
||||
VkVideoEncodeH264CapabilitiesKHR h264_caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_KHR };
|
||||
VkVideoEncodeCapabilitiesKHR enc_caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_CAPABILITIES_KHR, .pNext = &h264_caps };
|
||||
VkVideoCapabilitiesKHR caps = { .sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR, .pNext = &enc_caps };
|
||||
CHECK(pGetPhysicalDeviceVideoCapabilitiesKHR(pdev, &profile, &caps));
|
||||
printf("caps: minBitstreamBufferOffsetAlignment=%llu minBitstreamBufferSizeAlignment=%llu maxDpbSlots=%u\n",
|
||||
(unsigned long long)caps.minBitstreamBufferOffsetAlignment,
|
||||
(unsigned long long)caps.minBitstreamBufferSizeAlignment,
|
||||
caps.maxDpbSlots);
|
||||
printf("enc caps: supportedEncodeFeedbackFlags=0x%x rateControlModes=0x%x\n",
|
||||
enc_caps.supportedEncodeFeedbackFlags, enc_caps.rateControlModes);
|
||||
|
||||
/* src/dpb images */
|
||||
VkPhysicalDeviceVideoFormatInfoKHR fmt_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR,
|
||||
.pNext = &profile_list,
|
||||
.imageUsage = VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR | VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR,
|
||||
};
|
||||
uint32_t fmt_count = 0;
|
||||
CHECK(pGetPhysicalDeviceVideoFormatPropertiesKHR(pdev, &fmt_info, &fmt_count, NULL));
|
||||
if (fmt_count == 0) { fprintf(stderr, "no video formats reported\n"); return 1; }
|
||||
VkVideoFormatPropertiesKHR *fmts = calloc(fmt_count, sizeof(*fmts));
|
||||
for (uint32_t i = 0; i < fmt_count; i++)
|
||||
fmts[i].sType = VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR;
|
||||
CHECK(pGetPhysicalDeviceVideoFormatPropertiesKHR(pdev, &fmt_info, &fmt_count, fmts));
|
||||
VkFormat pic_format = fmts[0].format;
|
||||
printf("picture format = %d\n", pic_format);
|
||||
|
||||
/* video sessions */
|
||||
VkExtensionProperties std_hdr = {
|
||||
.extensionName = VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_EXTENSION_NAME,
|
||||
.specVersion = VK_STD_VULKAN_VIDEO_CODEC_H264_ENCODE_SPEC_VERSION,
|
||||
};
|
||||
VkVideoSessionCreateInfoKHR sess_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR,
|
||||
.queueFamilyIndex = (uint32_t)encode_qf,
|
||||
.pVideoProfile = &profile,
|
||||
.pictureFormat = pic_format,
|
||||
.maxCodedExtent = { WIDTH, HEIGHT },
|
||||
.referencePictureFormat = pic_format,
|
||||
.maxDpbSlots = 1,
|
||||
.maxActiveReferencePictures = 0,
|
||||
.pStdHeaderVersion = &std_hdr,
|
||||
};
|
||||
VkVideoSessionKHR session;
|
||||
CHECK(pCreateVideoSessionKHR(dev, &sess_ci, NULL, &session));
|
||||
|
||||
uint32_t req_count = 0;
|
||||
CHECK(pGetVideoSessionMemoryRequirementsKHR(dev, session, &req_count, NULL));
|
||||
printf("video session memory requirement count = %u\n", req_count);
|
||||
VkVideoSessionMemoryRequirementsKHR *reqs = calloc(req_count, sizeof(*reqs));
|
||||
for (uint32_t i = 0; i < req_count; i++)
|
||||
reqs[i].sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_MEMORY_REQUIREMENTS_KHR;
|
||||
CHECK(pGetVideoSessionMemoryRequirementsKHR(dev, session, &req_count, reqs));
|
||||
|
||||
VkDeviceMemory *sess_mems = calloc(req_count, sizeof(*sess_mems));
|
||||
VkBindVideoSessionMemoryInfoKHR *binds = calloc(req_count, sizeof(*binds));
|
||||
for (uint32_t i = 0; i < req_count; i++) {
|
||||
VkMemoryAllocateInfo ai = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.allocationSize = reqs[i].memoryRequirements.size,
|
||||
.memoryTypeIndex = find_memory_type(&mem_props, reqs[i].memoryRequirements.memoryTypeBits,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
|
||||
};
|
||||
CHECK(vkAllocateMemory(dev, &ai, NULL, &sess_mems[i]));
|
||||
binds[i] = (VkBindVideoSessionMemoryInfoKHR) {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_VIDEO_SESSION_MEMORY_INFO_KHR,
|
||||
.memoryBindIndex = reqs[i].memoryBindIndex,
|
||||
.memory = sess_mems[i],
|
||||
.memoryOffset = 0,
|
||||
.memorySize = reqs[i].memoryRequirements.size,
|
||||
};
|
||||
printf(" bindIndex=%u size=%llu\n", reqs[i].memoryBindIndex,
|
||||
(unsigned long long)reqs[i].memoryRequirements.size);
|
||||
}
|
||||
CHECK(pBindVideoSessionMemoryKHR(dev, session, req_count, binds));
|
||||
|
||||
/* SPS / PPS */
|
||||
int crop_r = getenv("HASVK_ENC_CROP_R") ? atoi(getenv("HASVK_ENC_CROP_R")) : 0;
|
||||
int crop_b = getenv("HASVK_ENC_CROP_B") ? atoi(getenv("HASVK_ENC_CROP_B")) : 0;
|
||||
crop_r &= ~1; crop_b &= ~1;
|
||||
|
||||
StdVideoH264SequenceParameterSet sps = {
|
||||
.profile_idc = use_cabac ? STD_VIDEO_H264_PROFILE_IDC_MAIN
|
||||
: STD_VIDEO_H264_PROFILE_IDC_BASELINE,
|
||||
.level_idc = getenv("HASVK_ENC_LEVEL0") ? STD_VIDEO_H264_LEVEL_IDC_1_0
|
||||
: STD_VIDEO_H264_LEVEL_IDC_3_0,
|
||||
.chroma_format_idc = STD_VIDEO_H264_CHROMA_FORMAT_IDC_420,
|
||||
.seq_parameter_set_id = 0,
|
||||
.log2_max_frame_num_minus4 = getenv("HASVK_ENC_LOG2MAXFN")
|
||||
? atoi(getenv("HASVK_ENC_LOG2MAXFN")) : 0,
|
||||
.pic_order_cnt_type = getenv("HASVK_ENC_POC2") ? STD_VIDEO_H264_POC_TYPE_2
|
||||
: STD_VIDEO_H264_POC_TYPE_0,
|
||||
.log2_max_pic_order_cnt_lsb_minus4 = 0,
|
||||
.max_num_ref_frames = 0,
|
||||
.pic_width_in_mbs_minus1 = WIDTH_MBS - 1,
|
||||
.pic_height_in_map_units_minus1 = HEIGHT_MBS - 1,
|
||||
.frame_crop_right_offset = (uint32_t)(crop_r / 2),
|
||||
.frame_crop_bottom_offset = (uint32_t)(crop_b / 2),
|
||||
};
|
||||
sps.flags.frame_mbs_only_flag = 1;
|
||||
sps.flags.direct_8x8_inference_flag = 1;
|
||||
sps.flags.frame_cropping_flag = (crop_r || crop_b) ? 1 : 0;
|
||||
|
||||
StdVideoH264PictureParameterSet pps = {
|
||||
.seq_parameter_set_id = 0,
|
||||
.pic_parameter_set_id = 0,
|
||||
.num_ref_idx_l0_default_active_minus1 = 0,
|
||||
.num_ref_idx_l1_default_active_minus1 = 0,
|
||||
.weighted_bipred_idc = STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_DEFAULT,
|
||||
.pic_init_qp_minus26 = 0, /* pic_init_qp = 26 */
|
||||
.pic_init_qs_minus26 = 0,
|
||||
.chroma_qp_index_offset = 0,
|
||||
.second_chroma_qp_index_offset = 0,
|
||||
};
|
||||
pps.flags.deblocking_filter_control_present_flag =
|
||||
getenv("HASVK_ENC_NODEBLOCKCTRL") ? 0 : 1;
|
||||
pps.flags.entropy_coding_mode_flag = use_cabac ? 1 : 0;
|
||||
|
||||
VkVideoEncodeH264SessionParametersAddInfoKHR add_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_ADD_INFO_KHR,
|
||||
.stdSPSCount = 1,
|
||||
.pStdSPSs = &sps,
|
||||
.stdPPSCount = 1,
|
||||
.pStdPPSs = &pps,
|
||||
};
|
||||
VkVideoEncodeH264SessionParametersCreateInfoKHR h264_params_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_CREATE_INFO_KHR,
|
||||
.maxStdSPSCount = 1,
|
||||
.maxStdPPSCount = 1,
|
||||
.pParametersAddInfo = &add_info,
|
||||
};
|
||||
VkVideoSessionParametersCreateInfoKHR params_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR,
|
||||
.pNext = &h264_params_ci,
|
||||
.videoSession = session,
|
||||
};
|
||||
VkVideoSessionParametersKHR params;
|
||||
CHECK(pCreateVideoSessionParametersKHR(dev, ¶ms_ci, NULL, ¶ms));
|
||||
|
||||
static uint8_t hdr_buf[512];
|
||||
size_t hdr_size = 0;
|
||||
{
|
||||
VkVideoEncodeH264SessionParametersGetInfoKHR h264_get = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_GET_INFO_KHR,
|
||||
.writeStdSPS = VK_TRUE,
|
||||
.stdSPSId = 0,
|
||||
.writeStdPPS = VK_TRUE,
|
||||
.stdPPSId = 0,
|
||||
};
|
||||
VkVideoEncodeSessionParametersGetInfoKHR get_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_SESSION_PARAMETERS_GET_INFO_KHR,
|
||||
.pNext = &h264_get,
|
||||
.videoSessionParameters = params,
|
||||
};
|
||||
size_t data_size = 0;
|
||||
VkResult gr = pGetEncodedVideoSessionParametersKHR(dev, &get_info, NULL,
|
||||
&data_size, NULL);
|
||||
printf("GetEncoded SPS+PPS: result=%d size=%zu\n", gr, data_size);
|
||||
if (gr == VK_SUCCESS && data_size > 0 && data_size <= sizeof(hdr_buf)) {
|
||||
gr = pGetEncodedVideoSessionParametersKHR(dev, &get_info, NULL,
|
||||
&data_size, hdr_buf);
|
||||
if (gr == VK_SUCCESS) hdr_size = data_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* source + recon/DPB */
|
||||
VkImageCreateInfo img_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = &profile_list,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = pic_format,
|
||||
.extent = { WIDTH, HEIGHT, 1 },
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
};
|
||||
VkImage src_image;
|
||||
CHECK(vkCreateImage(dev, &img_ci, NULL, &src_image));
|
||||
VkDeviceMemory src_image_mem = alloc_and_bind_image(dev, &mem_props, src_image);
|
||||
|
||||
VkImageCreateInfo dpb_ci = img_ci;
|
||||
dpb_ci.usage = VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR;
|
||||
VkImage dpb_image;
|
||||
CHECK(vkCreateImage(dev, &dpb_ci, NULL, &dpb_image));
|
||||
VkDeviceMemory dpb_image_mem = alloc_and_bind_image(dev, &mem_props, dpb_image);
|
||||
|
||||
VkImageViewCreateInfo src_view_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = src_image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = pic_format,
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
|
||||
};
|
||||
VkImageView src_view;
|
||||
CHECK(vkCreateImageView(dev, &src_view_ci, NULL, &src_view));
|
||||
|
||||
VkImageViewCreateInfo dpb_view_ci = src_view_ci;
|
||||
dpb_view_ci.image = dpb_image;
|
||||
VkImageView dpb_view;
|
||||
CHECK(vkCreateImageView(dev, &dpb_view_ci, NULL, &dpb_view));
|
||||
|
||||
VkImage dpb_image2 = VK_NULL_HANDLE;
|
||||
VkImageView dpb_view2 = VK_NULL_HANDLE;
|
||||
if (use_pframe) {
|
||||
CHECK(vkCreateImage(dev, &dpb_ci, NULL, &dpb_image2));
|
||||
alloc_and_bind_image(dev, &mem_props, dpb_image2);
|
||||
VkImageViewCreateInfo v = dpb_view_ci;
|
||||
v.image = dpb_image2;
|
||||
CHECK(vkCreateImageView(dev, &v, NULL, &dpb_view2));
|
||||
}
|
||||
|
||||
int shift_px = getenv("HASVK_ENC_SHIFT") ? atoi(getenv("HASVK_ENC_SHIFT")) : 0;
|
||||
int src_step = getenv("HASVK_ENC_SRC_STEP") ? atoi(getenv("HASVK_ENC_SRC_STEP")) : 0;
|
||||
int two_src = use_pframe && (shift_px || src_step);
|
||||
VkImage src_image2 = VK_NULL_HANDLE;
|
||||
VkImageView src_view2 = VK_NULL_HANDLE;
|
||||
if (two_src) {
|
||||
CHECK(vkCreateImage(dev, &img_ci, NULL, &src_image2));
|
||||
alloc_and_bind_image(dev, &mem_props, src_image2);
|
||||
VkImageViewCreateInfo v = src_view_ci;
|
||||
v.image = src_image2;
|
||||
CHECK(vkCreateImageView(dev, &v, NULL, &src_view2));
|
||||
}
|
||||
|
||||
/* fill source image with flat gray via staging buffer */
|
||||
VkDeviceSize luma_size = (VkDeviceSize)WIDTH * HEIGHT;
|
||||
VkDeviceSize chroma_size = (VkDeviceSize)(WIDTH / 2) * (HEIGHT / 2) * 2;
|
||||
VkDeviceSize frame_size = luma_size + chroma_size;
|
||||
VkDeviceSize staging_size = frame_size * (two_src ? 2 : 1);
|
||||
VkBufferCreateInfo staging_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = staging_size,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
VkBuffer staging_buf;
|
||||
CHECK(vkCreateBuffer(dev, &staging_ci, NULL, &staging_buf));
|
||||
VkDeviceMemory staging_mem = alloc_and_bind_buffer(dev, &mem_props, staging_buf,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
void *staging_map;
|
||||
CHECK(vkMapMemory(dev, staging_mem, 0, staging_size, 0, &staging_map));
|
||||
const char *src_in = getenv("HASVK_ENC_SRC_IN");
|
||||
int src_i420 = getenv("HASVK_ENC_SRC_I420") ? 1 : 0;
|
||||
int src_frame = getenv("HASVK_ENC_SRC_FRAME") ? atoi(getenv("HASVK_ENC_SRC_FRAME")) : 0;
|
||||
if (src_in) {
|
||||
if (!load_frame(src_in, src_frame, src_i420, (uint8_t *)staging_map))
|
||||
return 1;
|
||||
} else if (getenv("HASVK_ENC_GRADIENT")) {
|
||||
uint8_t *sm = (uint8_t *)staging_map;
|
||||
for (uint32_t yy = 0; yy < HEIGHT; yy++)
|
||||
for (uint32_t xx = 0; xx < WIDTH; xx++)
|
||||
sm[yy * WIDTH + xx] = (uint8_t)((xx * 255) / (WIDTH - 1));
|
||||
if (getenv("HASVK_ENC_CHROMA")) {
|
||||
uint8_t *ch = sm + luma_size;
|
||||
uint32_t cw = WIDTH / 2, chh = HEIGHT / 2;
|
||||
for (uint32_t cy = 0; cy < chh; cy++)
|
||||
for (uint32_t cx = 0; cx < cw; cx++) {
|
||||
ch[(cy * cw + cx) * 2 + 0] = (uint8_t)((cx * 255) / (cw - 1));
|
||||
ch[(cy * cw + cx) * 2 + 1] = (uint8_t)((cy * 255) / (chh - 1));
|
||||
}
|
||||
} else {
|
||||
memset(sm + luma_size, 128, (size_t)chroma_size);
|
||||
}
|
||||
} else {
|
||||
memset(staging_map, 128, (size_t)frame_size);
|
||||
}
|
||||
|
||||
if (two_src) {
|
||||
uint8_t *f0 = (uint8_t *)staging_map, *f1 = f0 + frame_size;
|
||||
if (shift_px) {
|
||||
shift_frame(f0, f1, shift_px, 0);
|
||||
} else if (!src_in) {
|
||||
fprintf(stderr, "HASVK_ENC_SRC_STEP needs HASVK_ENC_SRC_IN\n");
|
||||
return 1;
|
||||
} else if (!load_frame(src_in, src_frame + src_step, src_i420, f1)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const char *src_out = getenv("HASVK_ENC_SRC_OUT");
|
||||
if (src_out) {
|
||||
const uint8_t *sm = (const uint8_t *)staging_map;
|
||||
const uint8_t *ch = sm + luma_size;
|
||||
uint32_t cw = WIDTH / 2, chh = HEIGHT / 2;
|
||||
FILE *sf = fopen(src_out, "wb");
|
||||
if (sf) {
|
||||
fwrite(sm, 1, (size_t)luma_size, sf);
|
||||
for (uint32_t i = 0; i < cw * chh; i++)
|
||||
fputc(ch[i * 2 + 0], sf);
|
||||
for (uint32_t i = 0; i < cw * chh; i++)
|
||||
fputc(ch[i * 2 + 1], sf);
|
||||
fclose(sf);
|
||||
}
|
||||
}
|
||||
vkUnmapMemory(dev, staging_mem);
|
||||
|
||||
VkDeviceSize dst_align = caps.minBitstreamBufferSizeAlignment ? caps.minBitstreamBufferSizeAlignment : 4096;
|
||||
VkDeviceSize dst_size = ((VkDeviceSize)1 << 20);
|
||||
dst_size = (dst_size + dst_align - 1) & ~(dst_align - 1);
|
||||
VkBufferCreateInfo dst_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.pNext = &profile_list,
|
||||
.size = dst_size,
|
||||
.usage = VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
};
|
||||
VkBuffer dst_buf;
|
||||
CHECK(vkCreateBuffer(dev, &dst_ci, NULL, &dst_buf));
|
||||
VkDeviceMemory dst_mem = alloc_and_bind_buffer(dev, &mem_props, dst_buf,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
void *dst_map;
|
||||
CHECK(vkMapMemory(dev, dst_mem, 0, dst_size, 0, &dst_map));
|
||||
memset(dst_map, 0xCD, (size_t)dst_size);
|
||||
|
||||
VkCommandPoolCreateInfo xfer_pool_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = (uint32_t)transfer_qf,
|
||||
};
|
||||
VkCommandPool xfer_pool;
|
||||
CHECK(vkCreateCommandPool(dev, &xfer_pool_ci, NULL, &xfer_pool));
|
||||
VkCommandBufferAllocateInfo xfer_cb_ai = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = xfer_pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer xfer_cmd;
|
||||
CHECK(vkAllocateCommandBuffers(dev, &xfer_cb_ai, &xfer_cmd));
|
||||
|
||||
VkCommandBufferBeginInfo xfer_begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
||||
CHECK(vkBeginCommandBuffer(xfer_cmd, &xfer_begin_info));
|
||||
|
||||
VkImageMemoryBarrier to_xfer = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = 0,
|
||||
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = src_image,
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
|
||||
};
|
||||
VkImageMemoryBarrier to_xfer_both[2] = { to_xfer, to_xfer };
|
||||
to_xfer_both[1].image = src_image2;
|
||||
vkCmdPipelineBarrier(xfer_cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 0, NULL, 0, NULL, two_src ? 2 : 1, to_xfer_both);
|
||||
|
||||
VkBufferImageCopy regions[2] = {
|
||||
{ /* luma plane */
|
||||
.bufferOffset = 0,
|
||||
.imageSubresource = { VK_IMAGE_ASPECT_PLANE_0_BIT, 0, 0, 1 },
|
||||
.imageExtent = { WIDTH, HEIGHT, 1 },
|
||||
},
|
||||
{ /* chroma plane (interleaved UV, half res) */
|
||||
.bufferOffset = luma_size,
|
||||
.imageSubresource = { VK_IMAGE_ASPECT_PLANE_1_BIT, 0, 0, 1 },
|
||||
.imageExtent = { WIDTH / 2, HEIGHT / 2, 1 },
|
||||
},
|
||||
};
|
||||
vkCmdCopyBufferToImage(xfer_cmd, staging_buf, src_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, regions);
|
||||
if (two_src) {
|
||||
VkBufferImageCopy r2[2] = { regions[0], regions[1] };
|
||||
r2[0].bufferOffset = frame_size;
|
||||
r2[1].bufferOffset = frame_size + luma_size;
|
||||
vkCmdCopyBufferToImage(xfer_cmd, staging_buf, src_image2, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, r2);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier to_encode_src = to_xfer;
|
||||
to_encode_src.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
to_encode_src.dstAccessMask = 0;
|
||||
to_encode_src.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
to_encode_src.newLayout = VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR;
|
||||
VkImageMemoryBarrier to_encode_dpb = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = dpb_image,
|
||||
.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 },
|
||||
};
|
||||
VkImageMemoryBarrier barriers2[4] = { to_encode_src, to_encode_dpb };
|
||||
uint32_t barrier2_count = 2;
|
||||
if (use_pframe) {
|
||||
VkImageMemoryBarrier to_encode_dpb2 = to_encode_dpb;
|
||||
to_encode_dpb2.image = dpb_image2;
|
||||
barriers2[barrier2_count++] = to_encode_dpb2;
|
||||
}
|
||||
if (two_src) {
|
||||
VkImageMemoryBarrier to_encode_src2 = to_encode_src;
|
||||
to_encode_src2.image = src_image2;
|
||||
barriers2[barrier2_count++] = to_encode_src2;
|
||||
}
|
||||
vkCmdPipelineBarrier(xfer_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0, 0, NULL, 0, NULL, barrier2_count, barriers2);
|
||||
|
||||
CHECK(vkEndCommandBuffer(xfer_cmd));
|
||||
|
||||
/* HASVK_ENC_SEMSYNC */
|
||||
int use_semsync = getenv("HASVK_ENC_SEMSYNC") ? 1 : 0;
|
||||
VkSemaphore xfer_sem = VK_NULL_HANDLE;
|
||||
if (use_semsync) {
|
||||
VkSemaphoreCreateInfo sci = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||
CHECK(vkCreateSemaphore(dev, &sci, NULL, &xfer_sem));
|
||||
}
|
||||
|
||||
VkFenceCreateInfo xfer_fence_ci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
VkFence xfer_fence;
|
||||
CHECK(vkCreateFence(dev, &xfer_fence_ci, NULL, &xfer_fence));
|
||||
VkSubmitInfo xfer_submit = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &xfer_cmd,
|
||||
.signalSemaphoreCount = use_semsync ? 1u : 0u,
|
||||
.pSignalSemaphores = use_semsync ? &xfer_sem : NULL,
|
||||
};
|
||||
printf("submitting transfer prep...\n");
|
||||
fflush(stdout);
|
||||
CHECK(vkQueueSubmit(transfer_queue, 1, &xfer_submit, xfer_fence));
|
||||
if (!use_semsync) {
|
||||
VkResult xfer_wait = vkWaitForFences(dev, 1, &xfer_fence, VK_TRUE, 10ull * 1000 * 1000 * 1000);
|
||||
printf("transfer wait result = %d\n", xfer_wait);
|
||||
if (xfer_wait != VK_SUCCESS) { fprintf(stderr, "transfer prep failed/hung\n"); return 3; }
|
||||
} else {
|
||||
printf("transfer submitted (semaphore sync, no CPU wait)\n");
|
||||
}
|
||||
|
||||
VkCommandPoolCreateInfo pool_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = (uint32_t)encode_qf,
|
||||
};
|
||||
VkCommandPool pool;
|
||||
CHECK(vkCreateCommandPool(dev, &pool_ci, NULL, &pool));
|
||||
VkCommandBufferAllocateInfo cb_ai = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer cmd;
|
||||
CHECK(vkAllocateCommandBuffers(dev, &cb_ai, &cmd));
|
||||
|
||||
VkQueryPoolVideoEncodeFeedbackCreateInfoKHR fb_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_VIDEO_ENCODE_FEEDBACK_CREATE_INFO_KHR,
|
||||
.pNext = &profile,
|
||||
.encodeFeedbackFlags =
|
||||
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BUFFER_OFFSET_BIT_KHR |
|
||||
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BYTES_WRITTEN_BIT_KHR,
|
||||
};
|
||||
VkQueryPoolCreateInfo qp_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.pNext = &fb_ci,
|
||||
.queryType = VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR,
|
||||
.queryCount = 2,
|
||||
};
|
||||
VkQueryPool qpool;
|
||||
CHECK(vkCreateQueryPool(dev, &qp_ci, NULL, &qpool));
|
||||
|
||||
VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
|
||||
CHECK(vkBeginCommandBuffer(cmd, &begin_info));
|
||||
|
||||
vkCmdResetQueryPool(cmd, qpool, 0, 2);
|
||||
|
||||
if (getenv("HASVK_BISECT_TRULY_EMPTY")) {
|
||||
CHECK(vkEndCommandBuffer(cmd));
|
||||
VkFenceCreateInfo fci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
VkFence f;
|
||||
CHECK(vkCreateFence(dev, &fci, NULL, &f));
|
||||
VkSubmitInfo si = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd };
|
||||
printf("submitting TRULY EMPTY (no video calls at all)...\n");
|
||||
fflush(stdout);
|
||||
CHECK(vkQueueSubmit(queue, 1, &si, f));
|
||||
VkResult r = vkWaitForFences(dev, 1, &f, VK_TRUE, 15ull * 1000 * 1000 * 1000);
|
||||
printf("truly-empty wait result = %d\n", r);
|
||||
return r == VK_SUCCESS ? 0 : 2;
|
||||
}
|
||||
|
||||
int qp = getenv("HASVK_ENC_QP") ? atoi(getenv("HASVK_ENC_QP")) : 26;
|
||||
uint64_t bitrate = getenv("HASVK_ENC_BITRATE")
|
||||
? strtoull(getenv("HASVK_ENC_BITRATE"), NULL, 10) : 0;
|
||||
if (qp < 0) qp = 0;
|
||||
if (qp > 51) qp = 51;
|
||||
|
||||
int num_slices = getenv("HASVK_ENC_SLICES") ? atoi(getenv("HASVK_ENC_SLICES")) : 1;
|
||||
if (num_slices < 1) num_slices = 1;
|
||||
if (num_slices > HEIGHT_MBS) num_slices = HEIGHT_MBS;
|
||||
|
||||
VkVideoPictureResourceInfoKHR src_res = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
||||
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = src_view,
|
||||
};
|
||||
VkVideoPictureResourceInfoKHR src_res2 = src_res;
|
||||
src_res2.imageViewBinding = two_src ? src_view2 : src_view;
|
||||
VkVideoPictureResourceInfoKHR dpb_res0 = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
||||
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = dpb_view,
|
||||
};
|
||||
VkVideoPictureResourceInfoKHR dpb_res1 = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
||||
.codedExtent = { WIDTH, HEIGHT }, .baseArrayLayer = 0, .imageViewBinding = dpb_view2,
|
||||
};
|
||||
|
||||
int num_frames = use_pframe ? 2 : 1;
|
||||
VkDeviceSize base_off = getenv("HASVK_ENC_DSTOFF")
|
||||
? (VkDeviceSize)atoi(getenv("HASVK_ENC_DSTOFF")) : 0;
|
||||
VkDeviceSize frame_dst_offset[2] = { base_off, dst_size / 2 };
|
||||
|
||||
for (int frame = 0; frame < num_frames; frame++) {
|
||||
bool is_p_frame = (frame == 1) && !getenv("HASVK_ENC_FORCE_I");
|
||||
|
||||
VkVideoReferenceSlotInfoKHR begin_slots[2];
|
||||
uint32_t begin_slot_count = 0;
|
||||
if (is_p_frame) {
|
||||
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
||||
.slotIndex = 0, .pPictureResource = &dpb_res0 };
|
||||
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
||||
.slotIndex = 1, .pPictureResource = &dpb_res1 };
|
||||
} else {
|
||||
begin_slots[begin_slot_count++] = (VkVideoReferenceSlotInfoKHR){
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
||||
.slotIndex = 0, .pPictureResource = &dpb_res0 };
|
||||
}
|
||||
VkVideoEncodeH264RateControlLayerInfoKHR h264_rc_layer = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_RATE_CONTROL_LAYER_INFO_KHR,
|
||||
};
|
||||
VkVideoEncodeRateControlLayerInfoKHR rc_layer = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_LAYER_INFO_KHR,
|
||||
.pNext = &h264_rc_layer,
|
||||
.averageBitrate = bitrate,
|
||||
.maxBitrate = bitrate,
|
||||
.frameRateNumerator = 30,
|
||||
.frameRateDenominator = 1,
|
||||
};
|
||||
VkVideoEncodeRateControlInfoKHR rc_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_INFO_KHR,
|
||||
.rateControlMode = getenv("HASVK_ENC_VBR")
|
||||
? VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR
|
||||
: VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR,
|
||||
.layerCount = 1,
|
||||
.pLayers = &rc_layer,
|
||||
};
|
||||
VkVideoBeginCodingInfoKHR begin_coding = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR,
|
||||
.pNext = bitrate ? &rc_info : NULL,
|
||||
.videoSession = session,
|
||||
.videoSessionParameters = params,
|
||||
.referenceSlotCount = begin_slot_count,
|
||||
.pReferenceSlots = begin_slots,
|
||||
};
|
||||
pCmdBeginVideoCodingKHR(cmd, &begin_coding);
|
||||
|
||||
StdVideoEncodeH264PictureInfo std_pic_info = {
|
||||
.seq_parameter_set_id = 0,
|
||||
.pic_parameter_set_id = 0,
|
||||
.idr_pic_id = 0,
|
||||
.primary_pic_type = is_p_frame ? STD_VIDEO_H264_PICTURE_TYPE_P
|
||||
: STD_VIDEO_H264_PICTURE_TYPE_IDR,
|
||||
.frame_num = (uint16_t)frame,
|
||||
.PicOrderCnt = (int32_t)(frame * 2),
|
||||
};
|
||||
std_pic_info.flags.IdrPicFlag = is_p_frame ? 0 : 1;
|
||||
std_pic_info.flags.is_reference = 1;
|
||||
|
||||
StdVideoEncodeH264SliceHeader std_slice_hdr[HEIGHT_MBS];
|
||||
VkVideoEncodeH264NaluSliceInfoKHR nalu_slice[HEIGHT_MBS];
|
||||
for (int s = 0; s < num_slices; s++) {
|
||||
uint32_t start_row = (uint32_t)s * HEIGHT_MBS / num_slices;
|
||||
std_slice_hdr[s] = (StdVideoEncodeH264SliceHeader){
|
||||
.first_mb_in_slice = start_row * WIDTH_MBS,
|
||||
.slice_type = is_p_frame ? STD_VIDEO_H264_SLICE_TYPE_P
|
||||
: STD_VIDEO_H264_SLICE_TYPE_I,
|
||||
.slice_qp_delta = qp - 26,
|
||||
.cabac_init_idc = STD_VIDEO_H264_CABAC_INIT_IDC_0,
|
||||
/* value 1 ("_ENABLED") == H.264 disable_deblocking_filter_idc==1. */
|
||||
.disable_deblocking_filter_idc = STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_ENABLED,
|
||||
};
|
||||
nalu_slice[s] = (VkVideoEncodeH264NaluSliceInfoKHR){
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_NALU_SLICE_INFO_KHR,
|
||||
.constantQp = qp,
|
||||
.pStdSliceHeader = &std_slice_hdr[s],
|
||||
};
|
||||
}
|
||||
|
||||
VkVideoEncodeH264PictureInfoKHR h264_pic_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PICTURE_INFO_KHR,
|
||||
.naluSliceEntryCount = (uint32_t)num_slices,
|
||||
.pNaluSliceEntries = nalu_slice,
|
||||
.pStdPictureInfo = &std_pic_info,
|
||||
};
|
||||
|
||||
VkVideoReferenceSlotInfoKHR setup_slot = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
||||
.slotIndex = is_p_frame ? 1 : 0,
|
||||
.pPictureResource = is_p_frame ? &dpb_res1 : &dpb_res0,
|
||||
};
|
||||
VkVideoReferenceSlotInfoKHR ref_slot = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
||||
.slotIndex = 0,
|
||||
.pPictureResource = &dpb_res0,
|
||||
};
|
||||
|
||||
VkVideoEncodeInfoKHR encode_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_INFO_KHR,
|
||||
.pNext = &h264_pic_info,
|
||||
.dstBuffer = dst_buf,
|
||||
.dstBufferOffset = frame_dst_offset[frame],
|
||||
.dstBufferRange = dst_size / 2,
|
||||
.srcPictureResource = is_p_frame ? src_res2 : src_res,
|
||||
.pSetupReferenceSlot = &setup_slot,
|
||||
.referenceSlotCount = is_p_frame ? 1 : 0,
|
||||
.pReferenceSlots = is_p_frame ? &ref_slot : NULL,
|
||||
};
|
||||
vkCmdBeginQuery(cmd, qpool, frame, 0);
|
||||
pCmdEncodeVideoKHR(cmd, &encode_info);
|
||||
vkCmdEndQuery(cmd, qpool, frame);
|
||||
|
||||
VkVideoEndCodingInfoKHR end_coding = { .sType = VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR };
|
||||
pCmdEndVideoCodingKHR(cmd, &end_coding);
|
||||
}
|
||||
|
||||
CHECK(vkEndCommandBuffer(cmd));
|
||||
|
||||
VkFenceCreateInfo fence_ci = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
VkFence fence;
|
||||
CHECK(vkCreateFence(dev, &fence_ci, NULL, &fence));
|
||||
|
||||
VkPipelineStageFlags enc_wait_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkSubmitInfo submit = {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
.waitSemaphoreCount = use_semsync ? 1u : 0u,
|
||||
.pWaitSemaphores = use_semsync ? &xfer_sem : NULL,
|
||||
.pWaitDstStageMask = use_semsync ? &enc_wait_stage : NULL,
|
||||
};
|
||||
printf("submitting...\n");
|
||||
fflush(stdout);
|
||||
CHECK(vkQueueSubmit(queue, 1, &submit, fence));
|
||||
|
||||
VkResult wait_result = vkWaitForFences(dev, 1, &fence, VK_TRUE, 15ull * 1000 * 1000 * 1000);
|
||||
printf("wait result = %d (%s)\n", wait_result,
|
||||
wait_result == VK_SUCCESS ? "SUCCESS" :
|
||||
wait_result == VK_TIMEOUT ? "TIMEOUT" :
|
||||
wait_result == VK_ERROR_DEVICE_LOST ? "DEVICE_LOST" : "OTHER");
|
||||
|
||||
if (wait_result == VK_SUCCESS) {
|
||||
const uint8_t *bytes = (const uint8_t *)dst_map;
|
||||
const char *out_path = getenv("HASVK_ENC_OUT");
|
||||
if (!out_path) out_path = "/tmp/hasvk_enc_out.h264";
|
||||
FILE *of = fopen(out_path, "wb");
|
||||
if (of && hdr_size > 0)
|
||||
fwrite(hdr_buf, 1, hdr_size, of);
|
||||
|
||||
for (int f = 0; f < num_frames; f++) {
|
||||
struct { uint32_t offset; uint32_t bytesWritten; } fb = { 0, 0 };
|
||||
VkResult qr = vkGetQueryPoolResults(dev, qpool, f, 1, sizeof(fb), &fb,
|
||||
sizeof(fb), VK_QUERY_RESULT_WAIT_BIT);
|
||||
printf("frame %d (%s): bytesWritten=%u (query result %d)\n",
|
||||
f, f == 0 ? "I" : "P", fb.bytesWritten, qr);
|
||||
if (of && qr == VK_SUCCESS)
|
||||
fwrite(bytes + frame_dst_offset[f], 1, fb.bytesWritten, of);
|
||||
}
|
||||
if (of) {
|
||||
fclose(of);
|
||||
printf("wrote %zu (SPS/PPS) + %d frame(s) to %s\n",
|
||||
hdr_size, num_frames, out_path);
|
||||
}
|
||||
}
|
||||
|
||||
vkUnmapMemory(dev, dst_mem);
|
||||
printf("done.\n");
|
||||
return wait_result == VK_SUCCESS ? 0 : 2;
|
||||
}
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1786106723,
|
||||
"narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
description = "gen7-lab — Ivy Bridge (HD 4000) driver test lab";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
pciId = "8086:0166";
|
||||
|
||||
packages = with pkgs; [
|
||||
# CL
|
||||
clinfo
|
||||
clpeak
|
||||
ocl-icd
|
||||
opencl-headers
|
||||
pocl
|
||||
|
||||
# VK
|
||||
vulkan-tools
|
||||
vulkan-headers
|
||||
vulkan-loader
|
||||
vulkan-validation-layers
|
||||
shaderc
|
||||
glslang
|
||||
spirv-tools
|
||||
|
||||
# build
|
||||
gcc
|
||||
pkg-config
|
||||
cmake
|
||||
(python3.withPackages (
|
||||
ps: with ps; [
|
||||
pyopencl
|
||||
numpy
|
||||
opencv4
|
||||
]
|
||||
))
|
||||
# media
|
||||
ffmpeg
|
||||
mpv
|
||||
|
||||
# debug
|
||||
gdb
|
||||
strace
|
||||
jq
|
||||
patchelf
|
||||
binutils
|
||||
libdrm
|
||||
];
|
||||
|
||||
runtimeLibs = with pkgs; [
|
||||
libxshmfence
|
||||
xorg.xcbutilkeysyms
|
||||
llvmPackages_21.llvm.lib
|
||||
llvmPackages_21.libclang.lib
|
||||
spirv-llvm-translator
|
||||
vulkan-loader
|
||||
libdrm
|
||||
expat
|
||||
zlib
|
||||
zstd
|
||||
elfutils
|
||||
];
|
||||
|
||||
paths = ''
|
||||
export LAB_ROOT="''${LAB_ROOT:-$PWD}"
|
||||
if [ ! -f "$LAB_ROOT/ivb-test/runner.py" ]; then
|
||||
echo "warning: LAB_ROOT=$LAB_ROOT does not look like the gen7-lab checkout"
|
||||
fi
|
||||
export DRIVERS_ROOT="''${DRIVERS_ROOT:-$(dirname "$LAB_ROOT")}"
|
||||
|
||||
export MESA_SRC="''${MESA_SRC:-$DRIVERS_ROOT/intel-hard-graphics}"
|
||||
export MESA_BUILD="''${MESA_BUILD:-$MESA_SRC/build}"
|
||||
export CLVK_SRC="''${CLVK_SRC:-$DRIVERS_ROOT/clvk}"
|
||||
export CLVK_BUILD="''${CLVK_BUILD:-$CLVK_SRC/build}"
|
||||
export CTS_DIR="''${CTS_DIR:-$MESA_SRC/VK-GL-CTS/build-cts/external/vulkancts/modules/vulkan}"
|
||||
export CTS_MUSTPASS="''${CTS_MUSTPASS:-$MESA_SRC/VK-GL-CTS/external/vulkancts/mustpass/main/vk-default}"
|
||||
export ICD_VENDORS="$LAB_ROOT/.icd-vendors"
|
||||
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibs}:''${LD_LIBRARY_PATH:-}"
|
||||
'';
|
||||
|
||||
vulkanEnv = ''
|
||||
export HASVK_ICD="$MESA_BUILD/src/intel/vulkan_hasvk/intel_hasvk13_devenv_icd.x86_64.json"
|
||||
if [ -f "$HASVK_ICD" ]; then
|
||||
export VK_DRIVER_FILES="$HASVK_ICD"
|
||||
export VK_ICD_FILENAMES="$HASVK_ICD"
|
||||
else
|
||||
echo "warning: hasvk ICD not found: $HASVK_ICD"
|
||||
unset HASVK_ICD
|
||||
fi
|
||||
export MESA_VK_DEVICE_SELECT="${pciId}"
|
||||
'';
|
||||
|
||||
openclEnv = ''
|
||||
mkdir -p "$ICD_VENDORS/lib"
|
||||
ln -sf ${pkgs.pocl}/etc/OpenCL/vendors/pocl.icd "$ICD_VENDORS/pocl.icd"
|
||||
|
||||
if [ -f "$MESA_BUILD/src/gallium/targets/rusticl/rusticl.icd" ]; then
|
||||
ln -sf "$MESA_BUILD/src/gallium/targets/rusticl/rusticl.icd" "$ICD_VENDORS/rusticl.icd"
|
||||
export LD_LIBRARY_PATH="$MESA_BUILD/src/gallium/targets/rusticl:''${LD_LIBRARY_PATH:-}"
|
||||
else
|
||||
rm -f "$ICD_VENDORS/rusticl.icd"
|
||||
echo "warning: rusticl.icd not found under $MESA_BUILD"
|
||||
fi
|
||||
|
||||
if [ -f "$CLVK_BUILD/libOpenCL.so.1" ]; then
|
||||
_clvk_lib="$ICD_VENDORS/lib/libclvk.so.1"
|
||||
if [ "$CLVK_BUILD/libOpenCL.so.1" -nt "$_clvk_lib" ]; then
|
||||
cp -L "$CLVK_BUILD/libOpenCL.so.1" "$_clvk_lib.tmp"
|
||||
chmod u+w "$_clvk_lib.tmp"
|
||||
patchelf --set-soname libclvk.so.1 "$_clvk_lib.tmp"
|
||||
mv "$_clvk_lib.tmp" "$_clvk_lib"
|
||||
fi
|
||||
echo "$_clvk_lib" > "$ICD_VENDORS/clvk.icd"
|
||||
else
|
||||
rm -f "$ICD_VENDORS/clvk.icd"
|
||||
echo "warning: clvk libOpenCL.so.1 not found under $CLVK_BUILD"
|
||||
fi
|
||||
|
||||
export OCL_ICD_VENDORS="$ICD_VENDORS"
|
||||
export OCL_ICD_ASSUME_ICD_EXTENSION=1
|
||||
export RUSTICL_ENABLE=crocus
|
||||
'';
|
||||
|
||||
runner = ''
|
||||
ivb() { python3 "$LAB_ROOT/ivb-test/runner.py" "$@"; }
|
||||
export -f ivb
|
||||
'';
|
||||
|
||||
banner = name: ocl: ''
|
||||
echo "=== ${name} ==="
|
||||
printf '%-18s %s\n' \
|
||||
"MESA_BUILD" "$MESA_BUILD" \
|
||||
"CLVK_BUILD" "$CLVK_BUILD" \
|
||||
"CTS_DIR" "$CTS_DIR" \
|
||||
"VK_DRIVER_FILES" "''${VK_DRIVER_FILES:-(none)}" \
|
||||
"OpenCL" "${ocl}"
|
||||
echo
|
||||
echo "ivb list | ivb run | ivb doctor"
|
||||
echo
|
||||
'';
|
||||
in
|
||||
{
|
||||
devShells.${system} = {
|
||||
default = pkgs.mkShell {
|
||||
inherit packages;
|
||||
shellHook = ''
|
||||
${paths}
|
||||
${vulkanEnv}
|
||||
${openclEnv}
|
||||
${runner}
|
||||
${banner "gen7-lab" "$(ls \"$ICD_VENDORS\"/*.icd 2>/dev/null | xargs -rn1 basename | sed 's/\\.icd//' | tr '\\n' ' ')"}
|
||||
'';
|
||||
};
|
||||
|
||||
clvk = pkgs.mkShell {
|
||||
inherit packages;
|
||||
shellHook = ''
|
||||
${paths}
|
||||
${vulkanEnv}
|
||||
${runner}
|
||||
export LD_LIBRARY_PATH="$CLVK_BUILD:''${LD_LIBRARY_PATH:-}"
|
||||
export CLVK_MAX_COMPUTE_UNITS="''${CLVK_MAX_COMPUTE_UNITS:-16}"
|
||||
unset OCL_ICD_VENDORS
|
||||
${banner "gen7-lab (clvk only)" "clvk (libOpenCL.so.1 shadows the loader)"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/nix/store/jr8wwd19z6yvsfxykva6brazipm06r9b-pocl-7.1/etc/OpenCL/vendors/pocl.icd
|
||||
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"conditional_rendering": {
|
||||
"pass": 233,
|
||||
"fail": 8,
|
||||
"skip": 789,
|
||||
"aborted": false,
|
||||
"rc": 1,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_conditional_rendering.c \u2014 genX_conditional_rendering()"
|
||||
},
|
||||
"host_image_copy_linear": {
|
||||
"pass": 223,
|
||||
"fail": 0,
|
||||
"skip": 1053,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_host_image_copy.c \u2014 isl linear_to_tiled / bounce buffer"
|
||||
},
|
||||
"host_image_copy_optimal": {
|
||||
"pass": 223,
|
||||
"fail": 0,
|
||||
"skip": 1053,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_host_image_copy.c \u2014 isl tiled_to_tiled / bounce buffer"
|
||||
},
|
||||
"border_color_swizzle": {
|
||||
"pass": 32,
|
||||
"fail": 0,
|
||||
"skip": 32,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_sampler.c \u2014 border color swizzle"
|
||||
},
|
||||
"map_memory2": {
|
||||
"pass": 4466,
|
||||
"fail": 0,
|
||||
"skip": 0,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_memory.c \u2014 genX_map_memory2()"
|
||||
},
|
||||
"load_store_op_none": {
|
||||
"pass": 209,
|
||||
"fail": 0,
|
||||
"skip": 64,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_render_pass.c \u2014 genX_load_store_op_none()"
|
||||
},
|
||||
"feature_structs": {
|
||||
"pass": 181,
|
||||
"fail": 0,
|
||||
"skip": 1,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "anv_api_info.c \u2014 genX_get_physical_device_properties2()"
|
||||
},
|
||||
"line_stipple": {
|
||||
"pass": 8,
|
||||
"fail": 0,
|
||||
"skip": 40,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "3DSTATE_WM LineStippleEnable (Gen7)"
|
||||
},
|
||||
"line_rasterization": {
|
||||
"pass": 41,
|
||||
"fail": 0,
|
||||
"skip": 67,
|
||||
"aborted": false,
|
||||
"rc": 0,
|
||||
"group": "vulkan-ext",
|
||||
"touched": "3DSTATE_SF / line rendering rules (Gen7)"
|
||||
},
|
||||
"h264_decode": {
|
||||
"frames": 488,
|
||||
"errors": 0,
|
||||
"rc": 0,
|
||||
"group": "video",
|
||||
"touched": "anv_video_decode.c + vme_kernels.h \u2014 VME dispatch, binding table, IDRT"
|
||||
},
|
||||
"h264_encode": {
|
||||
"psnr": 54.826539,
|
||||
"min_psnr": 38.0,
|
||||
"rc": 0,
|
||||
"group": "video",
|
||||
"touched": "anv_video_encode.c + vme_kernels.h \u2014 VME dispatch, binding table, IDRT"
|
||||
},
|
||||
"h264_roundtrip": {
|
||||
"psnr": null,
|
||||
"min_psnr": 36.0,
|
||||
"rc": -6,
|
||||
"err": "Assertion slot_index >= 0 failed at libavcodec/vulkan_encode.c:216",
|
||||
"group": "video",
|
||||
"touched": "anv_video_encode.c + anv_video_decode.c \u2014 encode then decode back"
|
||||
},
|
||||
"opencl_parity": {
|
||||
"backends": {
|
||||
"rusticl": {
|
||||
"ok": true,
|
||||
"device": "Mesa Intel(R) HD Graphics 4000 (IVB GT2)"
|
||||
},
|
||||
"clvk": {
|
||||
"ok": true,
|
||||
"device": "Intel(R) HD Graphics 4000 (IVB GT2)"
|
||||
},
|
||||
"pocl": {
|
||||
"ok": true,
|
||||
"device": "cpu-ivybridge-Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz"
|
||||
}
|
||||
},
|
||||
"ok_count": 3,
|
||||
"total": 3,
|
||||
"rc": 0,
|
||||
"group": "opencl",
|
||||
"touched": "rusticl (crocus) vs clvk (hasvk) vs pocl (cpu reference)"
|
||||
},
|
||||
"cl_workgroup_limits": {
|
||||
"ok": true,
|
||||
"rc": 0,
|
||||
"group": "opencl",
|
||||
"touched": "crocus compute limits \u2014 max workgroup size"
|
||||
},
|
||||
"cl_image_formats": {
|
||||
"ok": true,
|
||||
"rc": 0,
|
||||
"group": "opencl",
|
||||
"touched": "crocus supported image formats"
|
||||
},
|
||||
"cl_atomics": {
|
||||
"ok": true,
|
||||
"rc": 0,
|
||||
"group": "opencl",
|
||||
"touched": "crocus atomic ops across multiple workgroups"
|
||||
},
|
||||
"enc_standalone_i": {
|
||||
"ok": true,
|
||||
"rc": 0,
|
||||
"group": "video",
|
||||
"touched": "anv_video_mfc.c \u2014 I frame PAK objects, intra DC prediction"
|
||||
},
|
||||
"enc_standalone_ip": {
|
||||
"ok": true,
|
||||
"rc": 0,
|
||||
"group": "video",
|
||||
"touched": "anv_video_mfc.c \u2014 P_Skip path, DPB slot 0/1, reference picture address"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
# ivb-test manifest
|
||||
#
|
||||
# ${VAR} is expanded from the environment; `nix develop` exports all of them.
|
||||
# Override any of them on the command line to test a different tree.
|
||||
|
||||
[config]
|
||||
mesa_build = "${MESA_BUILD}"
|
||||
clvk_build = "${CLVK_BUILD}"
|
||||
vk_driver_files = "${MESA_BUILD}/src/intel/vulkan_hasvk/intel_hasvk13_devenv_icd.x86_64.json"
|
||||
icd_vendors = "${OCL_ICD_VENDORS}"
|
||||
rusticl_enable = "crocus"
|
||||
|
||||
cts_dir = "${CTS_DIR}"
|
||||
cts_cmd = "./deqp-vk --deqp-case={filter} --deqp-log-filename={log} --deqp-surface-type=fbo"
|
||||
cts_timeout = 3600
|
||||
|
||||
vulkan_device = "vk:0"
|
||||
asset_width = 640
|
||||
asset_height = 480
|
||||
asset_frames = 488
|
||||
|
||||
|
||||
# Low-level capture profiles. INTEL_DEBUG flag names differ between mesa
|
||||
# versions — check yours if a profile comes back empty.
|
||||
|
||||
[capture.bat]
|
||||
desc = "batchbuffer dump"
|
||||
env = { INTEL_DEBUG = "bat" }
|
||||
|
||||
[capture.submit]
|
||||
desc = "batch submit + sync + stall"
|
||||
env = { INTEL_DEBUG = "submit,sync,stall" }
|
||||
|
||||
[capture.shaders]
|
||||
desc = "shader disassembly (VS/FS/CS)"
|
||||
env = { INTEL_DEBUG = "vs,fs,cs" }
|
||||
|
||||
[capture.aub]
|
||||
desc = "AUB dump"
|
||||
wrapper = "intel_dump_gpu -o {out}/dump.aub"
|
||||
|
||||
[capture.validate]
|
||||
desc = "Vulkan validation layer"
|
||||
env = { VK_INSTANCE_LAYERS = "VK_LAYER_KHRONOS_validation", MESA_VK_ABORT_ON_DEVICE_LOSS = "1" }
|
||||
|
||||
[capture.nocache]
|
||||
desc = "disable the shader cache"
|
||||
env = { MESA_SHADER_CACHE_DISABLE = "1" }
|
||||
|
||||
|
||||
# ------------------------------------------------------------ vulkan (CTS)
|
||||
|
||||
[[test]]
|
||||
name = "conditional_rendering"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_conditional_rendering.c — genX_conditional_rendering()"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.conditional_rendering.*"
|
||||
|
||||
[[test]]
|
||||
name = "host_image_copy_linear"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_host_image_copy.c — isl linear_to_tiled / bounce buffer"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.image.host_image_copy.simple.*.linear.*"
|
||||
|
||||
[[test]]
|
||||
name = "host_image_copy_optimal"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_host_image_copy.c — isl tiled_to_tiled / bounce buffer"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.image.host_image_copy.simple.*.optimal.*"
|
||||
|
||||
[[test]]
|
||||
name = "border_color_swizzle"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_sampler.c — border color swizzle"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.pipeline.monolithic.sampler.border_swizzle.r8g8b8a8_unorm.*.custom.no_gather.*"
|
||||
|
||||
[[test]]
|
||||
name = "map_memory2"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_memory.c — genX_map_memory2()"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.memory.mapping.*"
|
||||
|
||||
[[test]]
|
||||
name = "load_store_op_none"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_render_pass.c — genX_load_store_op_none()"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.renderpasses.*.load_store_op_none.*"
|
||||
|
||||
[[test]]
|
||||
name = "feature_structs"
|
||||
group = "vulkan-ext"
|
||||
touched = "anv_api_info.c — genX_get_physical_device_properties2()"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.api.info.get_physical_device_properties2.features.*"
|
||||
|
||||
[[test]]
|
||||
name = "line_stipple"
|
||||
group = "vulkan-ext"
|
||||
touched = "3DSTATE_WM LineStippleEnable (Gen7)"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.pipeline.monolithic.extended_dynamic_state.*.line_stipple_*"
|
||||
|
||||
[[test]]
|
||||
name = "line_rasterization"
|
||||
group = "vulkan-ext"
|
||||
touched = "3DSTATE_SF / line rendering rules (Gen7)"
|
||||
kind = "cts"
|
||||
filter = "dEQP-VK.rasterization.primitives.*lines*"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- video
|
||||
|
||||
[[test]]
|
||||
name = "h264_decode"
|
||||
group = "video"
|
||||
touched = "anv_video_decode.c + vme_kernels.h — VME dispatch, binding table, IDRT"
|
||||
kind = "video"
|
||||
op = "decode"
|
||||
|
||||
[[test]]
|
||||
name = "h264_encode"
|
||||
group = "video"
|
||||
touched = "anv_video_encode.c + vme_kernels.h — VME dispatch, binding table, IDRT"
|
||||
kind = "video"
|
||||
op = "encode"
|
||||
min_psnr = 38.0
|
||||
|
||||
[[test]]
|
||||
name = "h264_roundtrip"
|
||||
group = "video"
|
||||
touched = "anv_video_encode.c + anv_video_decode.c — encode then decode back"
|
||||
kind = "video"
|
||||
op = "roundtrip"
|
||||
min_psnr = 36.0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ opencl
|
||||
|
||||
[[test]]
|
||||
name = "opencl_parity"
|
||||
group = "opencl"
|
||||
touched = "rusticl (crocus) vs clvk (hasvk) vs pocl (cpu reference)"
|
||||
kind = "opencl"
|
||||
backends = ["rusticl", "clvk", "pocl"]
|
||||
|
||||
[[test]]
|
||||
name = "cl_workgroup_limits"
|
||||
group = "opencl"
|
||||
touched = "crocus compute limits — max workgroup size"
|
||||
kind = "cmd"
|
||||
cwd = "opencl_tests/16x16"
|
||||
build = "gcc -O2 wg_size_probe.c -o {out}/probe -lOpenCL"
|
||||
cmd = "{out}/probe"
|
||||
|
||||
[[test]]
|
||||
name = "cl_image_formats"
|
||||
group = "opencl"
|
||||
touched = "crocus supported image formats"
|
||||
kind = "cmd"
|
||||
cwd = "opencl_tests/16x16"
|
||||
build = "gcc -O2 cl_image_format_probe.c -o {out}/probe -lOpenCL"
|
||||
cmd = "{out}/probe"
|
||||
|
||||
[[test]]
|
||||
name = "cl_atomics"
|
||||
group = "opencl"
|
||||
touched = "crocus atomic ops across multiple workgroups"
|
||||
kind = "cmd"
|
||||
cwd = "opencl_tests/atomic"
|
||||
build = "gcc -O2 multi_atomic_test.c -o {out}/probe -lOpenCL"
|
||||
cmd = "{out}/probe"
|
||||
|
||||
[[test]]
|
||||
name = "enc_standalone_i"
|
||||
group = "video"
|
||||
touched = "anv_video_mfc.c — I frame PAK objects, intra DC prediction"
|
||||
kind = "cmd"
|
||||
cwd = "encode"
|
||||
build = "gcc -O1 -g hasvk_encode_test.c -o {out}/enctest -lvulkan"
|
||||
cmd = "{out}/enctest"
|
||||
match = "frame 0 \\(I\\): bytesWritten=[1-9]"
|
||||
|
||||
[[test]]
|
||||
name = "enc_standalone_ip"
|
||||
group = "video"
|
||||
touched = "anv_video_mfc.c — P_Skip path, DPB slot 0/1, reference picture address"
|
||||
kind = "cmd"
|
||||
cwd = "encode"
|
||||
build = "gcc -O1 -g hasvk_encode_test.c -o {out}/enctest -lvulkan"
|
||||
cmd = "env HASVK_ENC_PFRAME=1 {out}/enctest"
|
||||
match = "frame 1 \\(P\\): bytesWritten=[1-9]"
|
||||
Executable
+605
@@ -0,0 +1,605 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ivy Bridge (Gen7) driver test runner.
|
||||
|
||||
Reads manifest.toml, runs the tests against the driver trees the nix shell
|
||||
points at, and diffs the result against baseline.json.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
LAB = ROOT.parent
|
||||
MANIFEST = ROOT / "manifest.toml"
|
||||
BASELINE = ROOT / "baseline.json"
|
||||
RESULT = ROOT / "result"
|
||||
|
||||
C_OK, C_BAD, C_DIM, C_OFF = "\033[32m", "\033[31m", "\033[90m", "\033[0m"
|
||||
if not sys.stdout.isatty():
|
||||
C_OK = C_BAD = C_DIM = C_OFF = ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- manifest
|
||||
|
||||
def expand(v: Any) -> Any:
|
||||
if isinstance(v, str):
|
||||
return os.path.expandvars(os.path.expanduser(v))
|
||||
if isinstance(v, dict):
|
||||
return {k: expand(x) for k, x in v.items()}
|
||||
if isinstance(v, list):
|
||||
return [expand(x) for x in v]
|
||||
return v
|
||||
|
||||
|
||||
def load_manifest():
|
||||
with open(MANIFEST, "rb") as f:
|
||||
return expand(tomllib.load(f))
|
||||
|
||||
|
||||
def select(man, only=None, group=None):
|
||||
tests = man["test"]
|
||||
if only:
|
||||
tests = [t for t in tests if t["name"] in only]
|
||||
if group:
|
||||
tests = [t for t in tests if t.get("group") in group]
|
||||
return tests
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- process
|
||||
|
||||
def run(cmd, outdir, tag, env=None, timeout=3600, cwd=None):
|
||||
full_env = dict(os.environ)
|
||||
if env:
|
||||
full_env.update({k: str(v) for k, v in env.items()})
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
p = subprocess.run(
|
||||
cmd if isinstance(cmd, list) else shlex.split(cmd),
|
||||
capture_output=True, text=True, env=full_env, timeout=timeout,
|
||||
cwd=cwd or None,
|
||||
)
|
||||
text, rc = p.stdout + p.stderr, p.returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
text, rc = "TIMEOUT\n", 124
|
||||
except FileNotFoundError as e:
|
||||
text, rc = f"NOT FOUND: {e}\n", 127
|
||||
(outdir / f"{tag}.log").write_text(text)
|
||||
return rc, text
|
||||
|
||||
|
||||
def driver_env(cfg):
|
||||
icd = cfg.get("vk_driver_files", "")
|
||||
if not icd:
|
||||
return {}
|
||||
return {"VK_DRIVER_FILES": icd, "VK_ICD_FILENAMES": icd}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ checks
|
||||
|
||||
def probe_env(cfg):
|
||||
"""Everything the runner depends on, and whether it is actually there."""
|
||||
icd_dir = Path(cfg.get("icd_vendors", ""))
|
||||
vendors = sorted(p.stem for p in icd_dir.glob("*.icd")) if icd_dir.is_dir() else []
|
||||
cts = Path(cfg.get("cts_dir", ""))
|
||||
return [
|
||||
("mesa build", cfg.get("mesa_build", ""), Path(cfg.get("mesa_build", "/x")).is_dir()),
|
||||
("hasvk ICD", cfg.get("vk_driver_files", ""), Path(cfg.get("vk_driver_files", "/x")).is_file()),
|
||||
("clvk build", cfg.get("clvk_build", ""), Path(cfg.get("clvk_build", "/x")).is_dir()),
|
||||
("cts dir", cfg.get("cts_dir", ""), cts.is_dir()),
|
||||
("deqp-vk", str(cts / "deqp-vk"), (cts / "deqp-vk").is_file()),
|
||||
("icd vendors", f"{icd_dir} [{' '.join(vendors) or 'empty'}]", bool(vendors)),
|
||||
("ffmpeg", shutil.which("ffmpeg") or "-", bool(shutil.which("ffmpeg"))),
|
||||
]
|
||||
|
||||
|
||||
def check_config(cfg):
|
||||
"""The Vulkan loader silently falls back to the system ICD when the file
|
||||
it is pointed at is missing, which quietly tests the wrong driver."""
|
||||
icd = cfg.get("vk_driver_files", "")
|
||||
if icd and not Path(icd).is_file():
|
||||
sys.exit(f"vk_driver_files missing: {icd}\n"
|
||||
f"the loader would fall back to the system ICD — refusing to run")
|
||||
cts = cfg.get("cts_dir", "")
|
||||
if cts and not Path(cts).is_dir():
|
||||
sys.exit(f"cts_dir missing: {cts}")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ assets
|
||||
|
||||
def make_assets(cfg, outdir):
|
||||
w, h, n = cfg["asset_width"], cfg["asset_height"], cfg["asset_frames"]
|
||||
assets = outdir / "assets"
|
||||
assets.mkdir(parents=True, exist_ok=True)
|
||||
ref, src = assets / "ref.y4m", assets / "src.h264"
|
||||
|
||||
if not ref.exists():
|
||||
rc, _ = run(f"ffmpeg -y -f lavfi -i testsrc2=size={w}x{h}:rate=30 "
|
||||
f"-frames:v {n} -pix_fmt yuv420p {ref}", assets, "gen_ref")
|
||||
if rc != 0 or not ref.exists():
|
||||
sys.exit(f"could not generate ref.y4m — {assets}/gen_ref.log")
|
||||
|
||||
if not src.exists():
|
||||
# B-frames on purpose: the decoder has to handle reordering
|
||||
rc, _ = run(f"ffmpeg -y -i {ref} -c:v libx264 -preset medium "
|
||||
f"-bf 3 -b_strategy 2 -refs 4 -g 30 -crf 20 {src}",
|
||||
assets, "gen_src")
|
||||
if rc != 0 or not src.exists():
|
||||
sys.exit(f"could not generate src.h264 — {assets}/gen_src.log")
|
||||
|
||||
return ref, src
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- runners
|
||||
|
||||
def run_cts(t, cfg, outdir):
|
||||
qpa = outdir / "TestResults.qpa"
|
||||
cmd = cfg["cts_cmd"].format(filter=t["filter"], log=qpa)
|
||||
rc, text = run(cmd, outdir, "cts", env=driver_env(cfg), cwd=cfg.get("cts_dir"),
|
||||
timeout=t.get("timeout", cfg.get("cts_timeout", 3600)))
|
||||
|
||||
blob = text + (qpa.read_text(errors="ignore") if qpa.exists() else "")
|
||||
count = lambda w: len(re.findall(rf'StatusCode="{w}"', blob))
|
||||
|
||||
passed = count("Pass")
|
||||
failed = count("Fail") + count("QualityWarning") + count("InternalError")
|
||||
skipped = count("NotSupported")
|
||||
|
||||
if passed == 0 and failed == 0: # some wrappers only print the summary
|
||||
m = re.search(r"Passed:\s*(\d+).*?Failed:\s*(\d+).*?Not supported:\s*(\d+)",
|
||||
blob, re.S)
|
||||
if m:
|
||||
passed, failed, skipped = (int(x) for x in m.groups())
|
||||
|
||||
aborted = "ABORTED" in blob
|
||||
return {"pass": passed, "fail": failed, "skip": skipped,
|
||||
"aborted": aborted, "rc": rc}
|
||||
|
||||
|
||||
def last_error(text):
|
||||
"""The line worth putting in the summary when ffmpeg gives up."""
|
||||
for line in reversed(text.strip().splitlines()):
|
||||
if re.search(r"assert|error|failed|invalid|not implemented|no such",
|
||||
line, re.I) and "Task finished" not in line:
|
||||
return line.strip()[:120]
|
||||
return ""
|
||||
|
||||
|
||||
def parse_psnr(text):
|
||||
m = re.search(r"average:([\d.]+)", text) or \
|
||||
re.search(r"PSNR.*?average:\s*([\d.]+)", text)
|
||||
return float(m.group(1)) if m else None
|
||||
|
||||
|
||||
def run_video(t, cfg, outdir, assets):
|
||||
ref, src = assets
|
||||
env = driver_env(cfg)
|
||||
op = t["op"]
|
||||
|
||||
# h264_vulkan takes Vulkan frames, so the software frames have to be
|
||||
# uploaded explicitly — without this ffmpeg dies in the filter graph and
|
||||
# the encoder is never reached.
|
||||
dev = cfg.get("vulkan_device", "vk:0")
|
||||
hw = f"-init_hw_device vulkan={dev} -filter_hw_device vk"
|
||||
upload = "-vf format=nv12,hwupload"
|
||||
|
||||
if op == "decode":
|
||||
rc, text = run(f"ffmpeg -v info -hwaccel vulkan -i {src} -f null -",
|
||||
outdir, "decode", env=env)
|
||||
hits = re.findall(r"frame=\s*(\d+)", text)
|
||||
return {"frames": max((int(x) for x in hits), default=0),
|
||||
"errors": len(re.findall(r"\[h264.*?\] (error|Error|corrupt)", text)),
|
||||
"rc": rc}
|
||||
|
||||
if op == "encode":
|
||||
out = outdir / "enc.h264"
|
||||
rc, text = run(f"ffmpeg -y {hw} -i {ref} {upload} "
|
||||
f"-c:v h264_vulkan -qp 18 -g 1 {out}",
|
||||
outdir, "encode", env=env)
|
||||
if rc != 0 or not out.exists() or out.stat().st_size == 0:
|
||||
return {"psnr": None, "min_psnr": t.get("min_psnr"), "rc": rc,
|
||||
"err": last_error(text)}
|
||||
_, text = run(f"ffmpeg -i {out} -i {ref} -lavfi psnr -f null -", outdir, "psnr")
|
||||
return {"psnr": parse_psnr(text), "min_psnr": t.get("min_psnr"), "rc": rc}
|
||||
|
||||
if op == "roundtrip":
|
||||
enc, dec = outdir / "rt.h264", outdir / "rt.y4m"
|
||||
rc, text = run(f"ffmpeg -y {hw} -i {ref} {upload} "
|
||||
f"-c:v h264_vulkan -qp 18 {enc}",
|
||||
outdir, "rt_encode", env=env)
|
||||
if rc != 0 or not enc.exists() or enc.stat().st_size == 0:
|
||||
return {"psnr": None, "min_psnr": t.get("min_psnr"), "rc": rc,
|
||||
"err": last_error(text)}
|
||||
rc, text = run(f"ffmpeg -y -hwaccel vulkan -i {enc} {dec}",
|
||||
outdir, "rt_decode", env=env)
|
||||
if rc != 0 or not dec.exists():
|
||||
return {"psnr": None, "min_psnr": t.get("min_psnr"), "rc": rc,
|
||||
"err": last_error(text)}
|
||||
_, text = run(f"ffmpeg -i {dec} -i {ref} -lavfi psnr -f null -", outdir, "rt_psnr")
|
||||
return {"psnr": parse_psnr(text), "min_psnr": t.get("min_psnr"), "rc": 0}
|
||||
|
||||
return {"rc": 1, "err": f"unknown video op: {op}"}
|
||||
|
||||
|
||||
CL_PROBE = r'''
|
||||
import json, sys
|
||||
import numpy as np, pyopencl as cl
|
||||
|
||||
out = []
|
||||
for p in cl.get_platforms():
|
||||
for d in p.get_devices():
|
||||
r = {"platform": p.name.strip(), "device": d.name.strip(), "ok": False}
|
||||
try:
|
||||
ctx = cl.Context([d])
|
||||
q = cl.CommandQueue(ctx)
|
||||
prg = cl.Program(ctx, open(sys.argv[1]).read()).build()
|
||||
a = np.arange(4096, dtype=np.float32)
|
||||
b = a * 2.0
|
||||
mf = cl.mem_flags
|
||||
ba = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a)
|
||||
bb = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b)
|
||||
bc = cl.Buffer(ctx, mf.WRITE_ONLY, a.nbytes)
|
||||
prg.vec_add(q, a.shape, None, ba, bb, bc)
|
||||
got = np.empty_like(a)
|
||||
cl.enqueue_copy(q, got, bc)
|
||||
q.finish()
|
||||
r["ok"] = bool(np.allclose(got, a + b))
|
||||
if not r["ok"]:
|
||||
r["err"] = "result mismatch"
|
||||
except Exception as e:
|
||||
r["err"] = f"{type(e).__name__}: {e}"[:200]
|
||||
out.append(r)
|
||||
print("__CLPROBE__" + json.dumps(out))
|
||||
'''
|
||||
|
||||
CL_KERNEL = """__kernel void vec_add(__global const float *a, __global const float *b,
|
||||
__global float *c) {
|
||||
int i = get_global_id(0);
|
||||
c[i] = a[i] + b[i];
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def run_opencl(t, cfg, outdir):
|
||||
"""Each backend runs on its own, with a vendor dir holding only its ICD,
|
||||
so a backend that fails to load cannot be masked by another one."""
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
kernel = outdir / "vec_add.cl"
|
||||
kernel.write_text(CL_KERNEL)
|
||||
probe = outdir / "probe.py"
|
||||
probe.write_text(CL_PROBE)
|
||||
|
||||
icd_dir = Path(cfg.get("icd_vendors", ""))
|
||||
backends = t.get("backends", ["rusticl", "clvk", "pocl"])
|
||||
results = {}
|
||||
|
||||
for name in backends:
|
||||
icd = icd_dir / f"{name}.icd"
|
||||
if not icd.exists():
|
||||
results[name] = {"ok": False, "err": f"no {name}.icd in {icd_dir}"}
|
||||
continue
|
||||
vend = outdir / "vendors" / name
|
||||
vend.mkdir(parents=True, exist_ok=True)
|
||||
target = vend / f"{name}.icd"
|
||||
if target.is_symlink() or target.exists():
|
||||
target.unlink()
|
||||
target.symlink_to(icd.resolve())
|
||||
|
||||
env = dict(driver_env(cfg))
|
||||
env.update({"OCL_ICD_VENDORS": str(vend),
|
||||
"OCL_ICD_ASSUME_ICD_EXTENSION": "1",
|
||||
"PYOPENCL_COMPILER_OUTPUT": "1"})
|
||||
if name == "rusticl":
|
||||
env["RUSTICL_ENABLE"] = cfg.get("rusticl_enable", "crocus")
|
||||
|
||||
rc, text = run([sys.executable, str(probe), str(kernel)], outdir,
|
||||
f"cl_{name}", env=env, timeout=t.get("timeout", 600))
|
||||
m = re.search(r"__CLPROBE__(.*)", text)
|
||||
if rc != 0 or not m:
|
||||
results[name] = {"ok": False, "err": f"probe failed (rc={rc})"}
|
||||
continue
|
||||
devs = json.loads(m.group(1))
|
||||
if not devs:
|
||||
results[name] = {"ok": False, "err": "no device"}
|
||||
continue
|
||||
d = devs[0]
|
||||
results[name] = {"ok": d["ok"], "device": d["device"],
|
||||
**({"err": d["err"]} if d.get("err") else {})}
|
||||
|
||||
return {"backends": results,
|
||||
"ok_count": sum(1 for r in results.values() if r["ok"]),
|
||||
"total": len(backends),
|
||||
"rc": 0 if all(r["ok"] for r in results.values()) else 1}
|
||||
|
||||
|
||||
def run_cmd(t, cfg, outdir):
|
||||
"""Generic escape hatch: build (optionally) and run a probe from the lab
|
||||
tree. Probes are compiled per run — checked-in binaries go stale as soon
|
||||
as the nix store is garbage collected."""
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
cwd = t.get("cwd")
|
||||
cwd = str(LAB / cwd) if cwd and not os.path.isabs(cwd) else cwd
|
||||
fmt = lambda s: s.format(lab=LAB, out=outdir)
|
||||
|
||||
if t.get("build"):
|
||||
rc, _ = run(fmt(t["build"]), outdir, "build", cwd=cwd, timeout=600)
|
||||
if rc != 0:
|
||||
return {"ok": False, "rc": rc, "err": "build failed"}
|
||||
|
||||
rc, text = run(fmt(t["cmd"]), outdir, "cmd", env=driver_env(cfg),
|
||||
cwd=cwd, timeout=t.get("timeout", 600))
|
||||
ok = rc == t.get("expect_rc", 0)
|
||||
if ok and t.get("match"):
|
||||
ok = bool(re.search(t["match"], text, re.S))
|
||||
return {"ok": ok, "rc": rc}
|
||||
|
||||
|
||||
KINDS = {"cts": run_cts, "video": run_video, "opencl": run_opencl, "cmd": run_cmd}
|
||||
|
||||
|
||||
# -------------------------------------------------------------- formatting
|
||||
|
||||
def fmt_one(r):
|
||||
if "pass" in r:
|
||||
s = f"{r['pass']}/{r['fail']}"
|
||||
return s + (f" {C_BAD}aborted{C_OFF}" if r.get("aborted") else "")
|
||||
if "frames" in r:
|
||||
return f"{r['frames']} frames, {r['errors']} errors"
|
||||
if "psnr" in r:
|
||||
if not r["psnr"]:
|
||||
why = f" — {r['err']}" if r.get("err") else ""
|
||||
return f"{C_BAD}failed{C_OFF}{C_DIM}{why}{C_OFF}"
|
||||
lo = r.get("min_psnr")
|
||||
flag = f" {C_BAD}<{lo}{C_OFF}" if lo and r["psnr"] < lo else ""
|
||||
return f"psnr {r['psnr']}{flag}"
|
||||
if "ok_count" in r:
|
||||
up = [n for n, b in r["backends"].items() if b["ok"]]
|
||||
down = [n for n, b in r["backends"].items() if not b["ok"]]
|
||||
s = "+".join(up) if up else "none"
|
||||
return f"{s} ok" + (f", {C_BAD}{'+'.join(down)} down{C_OFF}" if down else "")
|
||||
if "both_up" in r: # pre-0.2 baselines
|
||||
return "both up" if r["both_up"] else f"{C_BAD}missing{C_OFF}"
|
||||
if "ok" in r:
|
||||
return "ok" if r["ok"] else f"{C_BAD}fail (rc={r.get('rc')}){C_OFF}"
|
||||
return "?"
|
||||
|
||||
|
||||
def score(r):
|
||||
if "pass" in r:
|
||||
return r["pass"] - r["fail"] * 1000
|
||||
if "frames" in r:
|
||||
return r["frames"] - r["errors"] * 1000
|
||||
if "psnr" in r:
|
||||
return r["psnr"] or -1
|
||||
if "ok_count" in r:
|
||||
return r["ok_count"]
|
||||
if "both_up" in r:
|
||||
return 2 if r["both_up"] else 0
|
||||
if "ok" in r:
|
||||
return 1 if r["ok"] else 0
|
||||
return -1
|
||||
|
||||
|
||||
def comparable(a, b):
|
||||
"""Result shapes must match, otherwise the scores mean different things."""
|
||||
keys = lambda r: next((k for k in ("pass", "frames", "psnr", "ok_count",
|
||||
"both_up", "ok") if k in r), None)
|
||||
ka, kb = keys(a), keys(b)
|
||||
return ka == kb or {ka, kb} == {"ok_count", "both_up"}
|
||||
|
||||
|
||||
def below_floor(r):
|
||||
lo = r.get("min_psnr")
|
||||
return bool(lo) and (r.get("psnr") or 0) < lo
|
||||
|
||||
|
||||
def has_regression(base, cur):
|
||||
for n, v in cur.items():
|
||||
if below_floor(v):
|
||||
return True
|
||||
old = base.get(n)
|
||||
if old and comparable(old, v) and score(v) < score(old):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def row(name, r, old):
|
||||
val = fmt_one(r)
|
||||
if old is None or not comparable(old, r):
|
||||
return f" {C_DIM}new{C_OFF} {name:<28} {val}"
|
||||
cur_s, old_s = score(r), score(old)
|
||||
if cur_s > old_s:
|
||||
return f" {C_OK}^{C_OFF} {name:<28} {val} (was {fmt_one(old)})"
|
||||
if cur_s < old_s:
|
||||
return f" {C_BAD}v{C_OFF} {name:<28} {val} (was {fmt_one(old)})"
|
||||
return f" = {name:<28} {val}"
|
||||
|
||||
|
||||
def diff_report(base, cur, group_by="group"):
|
||||
if group_by != "group":
|
||||
return "\n".join(row(n, r, base.get(n)) for n, r in cur.items())
|
||||
groups = {}
|
||||
for n, r in cur.items():
|
||||
groups.setdefault(r.get("group", "other"), []).append((n, r))
|
||||
lines = []
|
||||
for g in sorted(groups):
|
||||
lines.append(f"\n{C_DIM}[{g}]{C_OFF}")
|
||||
lines += [row(n, r, base.get(n)) for n, r in groups[g]]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def strip_color(s):
|
||||
return re.sub(r"\033\[[0-9;]*m", "", s)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- commands
|
||||
|
||||
def new_result_dir():
|
||||
d = RESULT / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
latest = RESULT / "latest"
|
||||
if latest.is_symlink() or latest.exists():
|
||||
latest.unlink() if latest.is_symlink() else shutil.rmtree(latest)
|
||||
latest.symlink_to(d.name)
|
||||
return d
|
||||
|
||||
|
||||
def do_run(args):
|
||||
man = load_manifest()
|
||||
cfg = man["config"]
|
||||
check_config(cfg)
|
||||
|
||||
tests = select(man, args.only, args.group)
|
||||
if not tests:
|
||||
sys.exit("no test matches")
|
||||
|
||||
rundir = new_result_dir()
|
||||
assets = (make_assets(cfg, RESULT)
|
||||
if any(t["kind"] == "video" for t in tests) else (None, None))
|
||||
|
||||
results = {}
|
||||
for t in tests:
|
||||
name = t["name"]
|
||||
print(f"{C_DIM}..{C_OFF} {name} ", end="", flush=True)
|
||||
fn = KINDS.get(t["kind"])
|
||||
if not fn:
|
||||
r = {"rc": 1, "err": f"unknown kind: {t['kind']}"}
|
||||
elif t["kind"] == "video":
|
||||
r = fn(t, cfg, rundir / name, assets)
|
||||
else:
|
||||
r = fn(t, cfg, rundir / name)
|
||||
r["group"] = t.get("group", "other")
|
||||
r["touched"] = t.get("touched", "")
|
||||
results[name] = r
|
||||
print(fmt_one(r))
|
||||
|
||||
(rundir / "results.json").write_text(json.dumps(results, indent=2))
|
||||
|
||||
base = json.loads(BASELINE.read_text()) if BASELINE.exists() else {}
|
||||
report = diff_report(base, results, args.group_by)
|
||||
(rundir / "summary.txt").write_text(strip_color(report))
|
||||
print("\n" + report)
|
||||
print(f"{C_DIM}-> {rundir}{C_OFF}")
|
||||
|
||||
if args.update_baseline:
|
||||
BASELINE.write_text(json.dumps(results if not (args.only or args.group)
|
||||
else {**base, **results}, indent=2))
|
||||
print(f"{C_DIM}baseline updated{C_OFF}")
|
||||
|
||||
return 1 if has_regression(base, results) else 0
|
||||
|
||||
|
||||
def do_capture(args):
|
||||
man = load_manifest()
|
||||
cfg = man["config"]
|
||||
check_config(cfg)
|
||||
|
||||
prof = man.get("capture", {}).get(args.profile)
|
||||
if not prof:
|
||||
sys.exit(f"profile not found: {args.profile}\n"
|
||||
f"available: {', '.join(man.get('capture', {}))}")
|
||||
t = next((x for x in man["test"] if x["name"] == args.name), None)
|
||||
if not t:
|
||||
sys.exit(f"test not found: {args.name}")
|
||||
|
||||
outdir = new_result_dir() / args.name / "capture"
|
||||
outdir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
env = dict(driver_env(cfg))
|
||||
env.update(prof.get("env", {}))
|
||||
print(f"{C_DIM}profile: {args.profile} — {prof.get('desc', '')}{C_OFF}")
|
||||
for k, v in env.items():
|
||||
print(f"{C_DIM} {k}={v}{C_OFF}")
|
||||
|
||||
if t["kind"] == "cts":
|
||||
cmd = cfg["cts_cmd"].format(filter=t["filter"], log=outdir / "TestResults.qpa")
|
||||
cwd = cfg.get("cts_dir")
|
||||
elif t["kind"] == "video":
|
||||
ref, src = make_assets(cfg, RESULT)
|
||||
dev = cfg.get("vulkan_device", "vk:0")
|
||||
cmd = (f"ffmpeg -v verbose -hwaccel vulkan -i {src} -f null -"
|
||||
if t.get("op") == "decode" else
|
||||
f"ffmpeg -y -v verbose -init_hw_device vulkan={dev} "
|
||||
f"-filter_hw_device vk -i {ref} -vf format=nv12,hwupload "
|
||||
f"-c:v h264_vulkan -qp 18 -g 1 {outdir}/enc.h264")
|
||||
cwd = None
|
||||
else:
|
||||
sys.exit(f"capture does not support kind={t['kind']}")
|
||||
|
||||
wrapper = prof.get("wrapper", "").format(out=outdir)
|
||||
rc, _ = run(f"{wrapper} {cmd}".strip(), outdir, "capture", env=env, cwd=cwd)
|
||||
|
||||
print(f"rc={rc} -> {outdir}")
|
||||
for f in sorted(outdir.iterdir()):
|
||||
print(f" {f.name} {f.stat().st_size} B")
|
||||
return rc
|
||||
|
||||
|
||||
def do_doctor(args):
|
||||
cfg = load_manifest()["config"]
|
||||
bad = 0
|
||||
for label, value, ok in probe_env(cfg):
|
||||
mark = f"{C_OK}ok {C_OFF}" if ok else f"{C_BAD}MISSING{C_OFF}"
|
||||
bad += not ok
|
||||
print(f" {mark:<12} {label:<14} {value}")
|
||||
if bad:
|
||||
print(f"\n{C_DIM}enter the lab shell with 'nix develop' so the paths "
|
||||
f"get exported, or override them in the environment{C_OFF}")
|
||||
return 1 if bad else 0
|
||||
|
||||
|
||||
def do_list(args):
|
||||
man = load_manifest()
|
||||
groups = {}
|
||||
for t in man["test"]:
|
||||
groups.setdefault(t.get("group", "other"), []).append(t)
|
||||
for g in sorted(groups):
|
||||
print(f"\n{C_DIM}[{g}]{C_OFF}")
|
||||
for t in groups[g]:
|
||||
print(f" {t['name']:<28} {C_DIM}{t.get('touched', '')}{C_OFF}")
|
||||
print(f"\n{C_DIM}capture profiles:{C_OFF}")
|
||||
for k, v in man.get("capture", {}).items():
|
||||
print(f" {k:<12} {C_DIM}{v.get('desc', '')}{C_OFF}")
|
||||
|
||||
|
||||
def do_clean(args):
|
||||
if RESULT.exists():
|
||||
shutil.rmtree(RESULT)
|
||||
print("result/ removed")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(prog="ivb")
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
r = sub.add_parser("run", help="run tests and diff against the baseline")
|
||||
r.add_argument("--only", nargs="+", metavar="NAME")
|
||||
r.add_argument("--group", nargs="+", metavar="G")
|
||||
r.add_argument("--group-by", choices=["group", "none"], default="group")
|
||||
r.add_argument("--update-baseline", action="store_true")
|
||||
r.set_defaults(fn=do_run)
|
||||
|
||||
c = sub.add_parser("capture", help="run one test under a low-level profile")
|
||||
c.add_argument("name")
|
||||
c.add_argument("--profile", default="bat")
|
||||
c.set_defaults(fn=do_capture)
|
||||
|
||||
sub.add_parser("doctor", help="check the driver trees the lab points at").set_defaults(fn=do_doctor)
|
||||
sub.add_parser("list", help="list tests and capture profiles").set_defaults(fn=do_list)
|
||||
sub.add_parser("clean", help="remove result/").set_defaults(fn=do_clean)
|
||||
|
||||
args = ap.parse_args()
|
||||
sys.exit(args.fn(args) or 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Whitespace-only changes.
@@ -0,0 +1,46 @@
|
||||
diff --git a/src/intel/vulkan_hasvk/genX_cmd_video.c b/src/intel/vulkan_hasvk/genX_cmd_video.c
|
||||
index 68878e71a24..93f27eb56db 100644
|
||||
--- a/src/intel/vulkan_hasvk/genX_cmd_video.c
|
||||
+++ b/src/intel/vulkan_hasvk/genX_cmd_video.c
|
||||
@@ -1191,6 +1191,16 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer,
|
||||
{ .bo = dst_buffer->address.bo, .offset = bs_abs & ~4095ull };
|
||||
uint32_t dst_page_off = (uint32_t)(bs_abs & 4095);
|
||||
|
||||
+ /* The PAK gets an access upper bound so it cannot write past the
|
||||
+ * allocation. dstBufferRange is what the client declares it needs, and
|
||||
+ * clients do leave it at zero -- both the CTS and the Vulkan video sample
|
||||
+ * encoder do -- which taken literally bounds the writes to nothing. Use
|
||||
+ * what the buffer actually holds instead. */
|
||||
+ uint64_t dst_avail = dst_buffer->vk.size - encode_info->dstBufferOffset;
|
||||
+ uint64_t dst_range = encode_info->dstBufferRange;
|
||||
+ if (dst_range == 0 || dst_range > dst_avail)
|
||||
+ dst_range = dst_avail;
|
||||
+
|
||||
/* Reconstructed picture (pre-deblocking destination). PAK requires a valid
|
||||
* target since PreDeblockingOutputEnable is set, so the setup DPB slot is
|
||||
* mandatory; there is no internal recon-image fallback. */
|
||||
@@ -1251,7 +1261,7 @@ anv_h264_encode_video(struct anv_cmd_buffer *cmd_buffer,
|
||||
/* Frame-level static state, emitted once before any slice. */
|
||||
genX(hasvk_mfc_emit_static_state)(cmd_buffer, img, recon_address, sps, pps,
|
||||
dst_address, dst_page_off,
|
||||
- encode_info->dstBufferRange,
|
||||
+ dst_range,
|
||||
width_in_mbs, height_in_mbs, is_p, &refs);
|
||||
|
||||
/* Intra refresh, if the session was created with a mode and this picture
|
||||
|
||||
--- NOT ---
|
||||
Park edildi. Bkz. docs/vulkan_hasvk.md §10o ve §10q.
|
||||
|
||||
Ne yapiyor: dstBufferRange sifir geldiginde PAK'in erisim ust sinirini
|
||||
tamponun kalan boyutuna kelepceliyor. Istemciler o alani sifir birakiyor
|
||||
(hem CTS hem Vulkan ornek encoder'i), sifir alininca sinir tabana esitleniyor
|
||||
ve donanima "sifir bayt yazabilirsin" denmis oluyor.
|
||||
|
||||
Olculen etki (ikisinde de B frame acik, 60 testlik ayri-DPB listesi):
|
||||
yamasiz 48 Pass 0 Fail 0 Warning | bagimsiz istemci BOZUK (64 bayt iniyor)
|
||||
yamali 6 Pass 0 Fail 42 Warning | bagimsiz istemci CALISIYOR (45 dB)
|
||||
Uyarilar fail degil: I frame 29,81 dB, esik 30.
|
||||
|
||||
Cozulmemis soru: bir erisim bariyerinin kodlama kalitesini nicin 0,2 dB
|
||||
etkiledigi. Anlasilmadan hangisinin dogru oldugu secilemez.
|
||||
Reference in new issue
Block a user