first commit

This commit is contained in:
elwrcl committed 2026-08-23 04:59:41 +03:00
commit 0bbe6b34e6
54 files changed
+156785

No files matched your search

+18
View File
@@ -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
+104
View File
@@ -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()
+53
View File
@@ -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()
+240
View File
@@ -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
View File
@@ -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
+8
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
/nix/store/jr8wwd19z6yvsfxykva6brazipm06r9b-pocl-7.1/etc/OpenCL/vendors/pocl.icd
BIN
View File
Binary file not shown.
+964
View File
@@ -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, &params_ci, NULL, &params));
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
View File
@@ -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
}
+174
View File
@@ -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)"}
'';
};
};
};
}
+1
View File
@@ -0,0 +1 @@
/nix/store/jr8wwd19z6yvsfxykva6brazipm06r9b-pocl-7.1/etc/OpenCL/vendors/pocl.icd
+156
View File
@@ -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"
}
}
+197
View File
@@ -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]"
+605
View File
@@ -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()
View File
Whitespace-only changes.
+46
View File
@@ -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.