vkgs-sim / tests /test_engine_config.py
kimjunhan's picture
vkgs-sim μ†ŒμŠ€ (nusim hub upload code)
9a28212 verified
Raw History Blame Contribute Delete
6.32 kB
"""μ„€μ • 파일 ↔ μ—”μ§„ μ½”λ“œ 일관성 (GPU 없이). 2026-10-05 전체 μž¬κ²€ν† μ—μ„œ λ‚˜μ˜¨ '쑰용히 λ¬΄μ‹œλ˜λŠ” μ„€μ •' λ°©μ§€.
- configs/sim.json 의 ν‚€λ₯Ό viewer.cpp load_sim_config κ°€ μ‹€μ œλ‘œ μ½λŠ”κ°€
- configs/render_graph.json 의 μ°Έμ‘°(νŒŒμ΄ν”„λΌμΈΒ·μ‹€ν–‰κΈ°Β·κ·Έλ¦¬κΈ° ν•­λͺ©Β·λ Œλ” λŒ€μƒΒ·μ…°μ΄λ” λΉŒλ“œ)κ°€ λ‹€ μžˆλŠ”κ°€
- bindless 슬둯 μƒμˆ˜κ°€ 셰이더와 C++ μ—μ„œ 같은가
- λΉŒλ“œ λͺ©λ‘μ˜ 셰이더가 λͺ¨λ‘ μ»΄νŒŒμΌλ˜λŠ”κ°€ (glslc κ°€ 있으면)
"""
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
VIEWER = (ROOT / "src/vkgs/src/viewer.cpp").read_text()
CMAKE = (ROOT / "src/vkgs/CMakeLists.txt").read_text()
SHADERS = ROOT / "src/vkgs/shaders"
def _strip_doc(d):
return {k: v for k, v in d.items() if not k.startswith("_")}
def _load_sim_config_src() -> str:
i = VIEWER.index("void load_sim_config(")
return VIEWER[i: VIEWER.index("\n}\n", i)]
PY_ONLY = {"drive.start_offset_m"} # 파이썬(nusim render map 좜발 ν”„λ ˆμž„)이 μ½λŠ” ν‚€
def test_sim_json_keys_are_read():
src = _load_sim_config_src()
read = set(re.findall(r'"([a-z_0-9]+)"', src))
sim = json.loads((ROOT / "configs/sim.json").read_text())
missing = []
for sec, val in _strip_doc(sim).items():
if sec == "ego": # 파이썬(nusim render view)이 μ½λŠ”λ‹€
continue
if sec not in read:
missing.append(sec)
if isinstance(val, dict) and sec not in ("shader_graphs", "passes"): # 이 λ‘˜μ€ 이름이 데이터
for k, v in _strip_doc(val).items():
if k == "presets":
continue
if k not in read and f"{sec}.{k}" not in PY_ONLY:
missing.append(f"{sec}.{k}")
if isinstance(v, dict) and k != "defaults":
missing += [f"{sec}.{k}.{kk}" for kk in _strip_doc(v) if kk not in read]
assert not missing, f"viewer.cpp load_sim_config κ°€ 읽지 μ•ŠλŠ” sim.json ν‚€: {missing}"
def test_weather_preset_fields_and_aug_weather_names():
i = VIEWER.index("void set_weather_field(")
fields = set(re.findall(r'k == "([a-z_]+)"', VIEWER[i: VIEWER.index("\n}\n", i)]))
sim = json.loads((ROOT / "configs/sim.json").read_text())
names = set()
for p in sim["weather"]["presets"]:
names.add(p["name"])
bad = set(p["values"]) - fields
assert not bad, f"프리셋 {p['name']} 의 λͺ¨λ₯΄λŠ” κ°’ {bad} (set_weather_field: {sorted(fields)})"
assert set(sim["weather"]["defaults"]) <= fields
assert sim["weather"].get("start", "clear") in names
variants = json.loads((ROOT / "configs/aug/rig_variants.json").read_text())["variants"]
for v in variants:
if v.get("weather"):
assert v["weather"] in names, f"rig λ³€ν˜• {v['name']} 의 날씨 {v['weather']} κ°€ sim.json 프리셋에 μ—†μŒ"
def _registered(kind: str) -> set:
return set(re.findall(kind + r'\["([a-z_0-9]+)"\]\s*=', VIEWER))
def test_render_graph_references():
g = json.loads((ROOT / "configs/render_graph.json").read_text())
pipes = _strip_doc(g["pipelines"])
res = _strip_doc(g["resources"])
targets = _strip_doc(g.get("targets", {}))
pass_kinds, draws = _registered("pass_exec_"), _registered("draw_exec_")
built = set(re.findall(r"vkgs_add_shader\(shaders/([^)]+)\)", CMAKE))
graphs = {p.stem + ".comp" for p in (ROOT / "configs/shader_graphs").glob("*.json")}
for name, p in pipes.items():
for stage in ("vertex", "fragment", "compute"):
if stage in p:
assert p[stage] in built | graphs, f"νŒŒμ΄ν”„λΌμΈ {name}: {p[stage]} κ°€ CMake λΉŒλ“œ λͺ©λ‘μ— μ—†μŒ"
if p.get("target", "scene") != "scene":
assert p["target"] in targets, f"νŒŒμ΄ν”„λΌμΈ {name}: λͺ¨λ₯΄λŠ” target {p['target']}"
for t, d in targets.items():
for c in d["color"]:
assert c in res and res[c].get("type") == "image" and "create" in res[c], f"target {t}: {c} λŠ” create 이미지여야 함"
names = [p["name"] for p in g["passes"]]
assert len(names) == len(set(names))
for p in g["passes"]:
assert p["kind"] in pass_kinds, f"패슀 {p['name']}: μ‹€ν–‰κΈ° '{p['kind']}' κ°€ 등둝돼 μžˆμ§€ μ•ŠμŒ ({sorted(pass_kinds)})"
for r, _ in p.get("uses", []):
assert r in res, f"패슀 {p['name']}: λͺ¨λ₯΄λŠ” λ¦¬μ†ŒμŠ€ {r}"
prm = p.get("params", {})
for key in ("pipeline", "hist", "scan", "scatter", "fallback_pipeline"):
if key in prm:
assert prm[key] in pipes, f"패슀 {p['name']}: {key}={prm[key]} νŒŒμ΄ν”„λΌμΈ μ—†μŒ"
for d in prm.get("draws", []):
assert d in draws, f"패슀 {p['name']}: 그리기 ν•­λͺ© '{d}' μ‹€ν–‰κΈ° μ—†μŒ"
if prm.get("target", "scene") != "scene":
assert prm["target"] in targets
if p.get("follows"):
assert p["follows"] in names
if p["kind"] == "radix_sort":
assert prm.get("key_bits", 32) == 32
hist = res[prm["histogram"]]["create"]["bytes"]
assert hist >= prm.get("groups", 512) * 256 * 4, "sort_hist κ°€ groups Γ— 256 Γ— 4 보닀 μž‘μŒ"
def test_bindless_slots_match():
glsl = (SHADERS / "bindless.glsl").read_text()
consts = {k: int(v) for k, v in re.findall(r"(k(?:Ssbo|Ubo|Img)\w+) = (\d+)u", glsl)}
cpp = {k: int(v) for k, v in re.findall(r"(k(?:Ssbo|Ubo|Img)\w+) = (\d+)", VIEWER)}
assert consts, "bindless.glsl μ—μ„œ 슬둯 μƒμˆ˜λ₯Ό λͺ» 찾음"
for k, v in consts.items():
assert cpp.get(k) == v, f"{k}: bindless.glsl {v} vs viewer.cpp {cpp.get(k)}"
@pytest.mark.skipif(shutil.which("glslc") is None, reason="glslc μ—†μŒ (source scripts/local_env.sh)")
def test_all_shaders_compile(tmp_path):
bad = []
for name in sorted(set(re.findall(r"vkgs_add_shader\(shaders/([^)]+)\)", CMAKE))):
r = subprocess.run(["glslc", "--target-env=vulkan1.2", "-O", "-I", str(SHADERS), str(SHADERS / name),
"-o", str(tmp_path / (name + ".spv"))], capture_output=True, text=True)
if r.returncode:
bad.append(f"{name}: {r.stderr.strip()[:300]}")
assert not bad, "\n".join(bad)