native: perf telemetry, 2 frames in flight, 120 Hz pacing, GPU shadow map
- --perf-log CSV + logcat telemetry (perf_log.h, EterBase/PerfCounters.h), incl. display refresh rate / app vsync rate, thermal, CPU clocks; tools/pull_perf_logs.sh + tools/perf_summary.py - Vulkan renderer: two frames in flight, in-frame texture uploads (no vkQueueWaitIdle), --fps-cap / --refresh-rate / --frames-in-flight - Android: ANativeWindow_setFrameRate, ADPF performance hint, appCategory=game (ColorOS no longer drops to 60 Hz when idle) - Character shadow map rendered on the GPU into an offscreen render target instead of CPU rasterization + per-frame 1 MB upload; --cpu-shadow keeps the old path. Device (120 Hz, 32 mobs): 75 -> ~115 fps, script thread 8.9 -> 3.4 ms. draw_capture format v10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3e3708ef06
commit
c2cc093ed0
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Summarises a native client --perf-log CSV (native_render/perf_log.h).
|
||||
|
||||
usage: perf_summary.py perf-YYYYMMDD-HHMMSS.csv [--all]
|
||||
|
||||
Only in-game rows (a map is loaded) are summarised unless --all is given. Prints the median of each
|
||||
frame-time section so the largest cost stands out, the worst windows, thermal/clock trend and the
|
||||
"# hitch" lines.
|
||||
"""
|
||||
import csv
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
SECTIONS = [
|
||||
("frame_ms", "whole frame"),
|
||||
("app_ms", " script thread (AppFrame)"),
|
||||
("net_ms", " network"),
|
||||
("camera_ms", " camera/resource"),
|
||||
("update_game_ms", " UpdateGame"),
|
||||
("render_game_ms", " RenderGame"),
|
||||
("shadow_raster_ms", " CPU shadow raster"),
|
||||
("py_ms", " Python callbacks (incl. Update/RenderGame)"),
|
||||
("handoff_ms", " baton handoff"),
|
||||
("pace_ms", " fps-cap sleep (before the frame)"),
|
||||
("host_ms", " host UI/audio"),
|
||||
("render_ms", " renderer"),
|
||||
("sync_ms", " fence wait + acquire"),
|
||||
("fence_ms", " frame-slot fence wait"),
|
||||
("prepare_ms", " prepare (incl. uploads)"),
|
||||
("geo_upload_ms", " geometry uploads"),
|
||||
("tex_upload_ms", " texture uploads"),
|
||||
("submit_ms", " submit"),
|
||||
("present_ms", " present"),
|
||||
("gpu_ms", " GPU (timestamps)"),
|
||||
]
|
||||
|
||||
|
||||
def num(row, key):
|
||||
try:
|
||||
return float(row.get(key, "") or "nan")
|
||||
except ValueError:
|
||||
return float("nan")
|
||||
|
||||
|
||||
def median(rows, key):
|
||||
values = [num(r, key) for r in rows]
|
||||
values = [v for v in values if v == v]
|
||||
return statistics.median(values) if values else float("nan")
|
||||
|
||||
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
if not args:
|
||||
sys.exit(__doc__)
|
||||
path = args[0]
|
||||
comments, lines = [], []
|
||||
with open(path, encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
(comments if line.startswith("#") else lines).append(line)
|
||||
rows = list(csv.DictReader(lines))
|
||||
if "--all" not in sys.argv:
|
||||
rows = [r for r in rows if r.get("map")]
|
||||
print(path)
|
||||
for c in comments[:2]:
|
||||
print(c.rstrip())
|
||||
if not rows:
|
||||
print("no in-game rows (use --all to include the login/select screens)")
|
||||
return
|
||||
duration = num(rows[-1], "t_s") - num(rows[0], "t_s")
|
||||
print("%d windows, %.0f s in game, maps: %s" % (len(rows), duration, ", ".join(sorted({r["map"] for r in rows}))))
|
||||
print("display %s Hz app vsync (render_hz) median %.1f fps cap %s" % (
|
||||
"/".join(sorted({r.get("refresh_hz", "?") for r in rows})), median(rows, "render_hz"),
|
||||
rows[-1].get("fps_cap", "?")))
|
||||
print("median fps %.1f frame p95 %.1f ms jank>33ms %d frames jank>50ms %d frames" % (
|
||||
median(rows, "fps"), median(rows, "frame_p95_ms"),
|
||||
sum(int(num(r, "jank33")) for r in rows), sum(int(num(r, "jank50")) for r in rows)))
|
||||
print("median actors %.0f draws %.0f skinned %.0f python calls/frame %.0f (missing %.0f)" % (
|
||||
median(rows, "actors"), median(rows, "draws"), median(rows, "skinned_draws"),
|
||||
median(rows, "py_calls"), median(rows, "py_missing")))
|
||||
print("uploads/frame: geometry %.1f (%.1f KB) texture %.2f (%.0f KB)" % (
|
||||
median(rows, "geo_uploads"), median(rows, "geo_kb"), median(rows, "tex_uploads"), median(rows, "tex_kb")))
|
||||
print()
|
||||
print("median ms per frame")
|
||||
for key, label in SECTIONS:
|
||||
print(" %-48s %7.2f" % (label, median(rows, key)))
|
||||
|
||||
print()
|
||||
print("worst windows (lowest fps)")
|
||||
for r in sorted(rows, key=lambda r: num(r, "fps"))[:5]:
|
||||
print(" t=%6.0fs fps=%5.1f frame=%5.1f app=%5.1f render_game=%5.1f render=%5.1f sync=%5.1f gpu=%5.1f "
|
||||
"actors=%s thermal=%s cpu_mhz=%s" % (
|
||||
num(r, "t_s"), num(r, "fps"), num(r, "frame_ms"), num(r, "app_ms"), num(r, "render_game_ms"),
|
||||
num(r, "render_ms"), num(r, "sync_ms"), num(r, "gpu_ms"), r.get("actors"), r.get("thermal"),
|
||||
r.get("cpu_mhz")))
|
||||
|
||||
step = max(1, len(rows) // 8)
|
||||
print()
|
||||
print("trend (thermal: 0 none .. 3 severe; -1 unknown)")
|
||||
for r in rows[::step]:
|
||||
print(" t=%6.0fs fps=%5.1f thermal=%s batt=%s C cpu_mhz=%s (max %s) rss=%s MB" % (
|
||||
num(r, "t_s"), num(r, "fps"), r.get("thermal"), r.get("batt_c"), r.get("cpu_mhz"),
|
||||
r.get("cpu_max_mhz", "?"), r.get("rss_mb")))
|
||||
|
||||
hitches = [c.rstrip() for c in comments if c.startswith("# hitch")]
|
||||
if hitches:
|
||||
print()
|
||||
print("%d hitches >= 100 ms (first 10)" % len(hitches))
|
||||
for h in hitches[:10]:
|
||||
print(" " + h[2:])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Copies the Android client's --perf-log files (native_render/perf_log.h) to ./perf-logs and prints a
|
||||
# summary of the newest one. The phone must be connected with USB debugging; the files can also be
|
||||
# taken off the phone without a PC from Android/data/org.metin2port.client/files/perf.
|
||||
set -e
|
||||
PKG=org.metin2port.client
|
||||
OUT=${1:-perf-logs}
|
||||
mkdir -p "$OUT"
|
||||
adb pull "/sdcard/Android/data/$PKG/files/perf/." "$OUT"
|
||||
newest=$(ls -t "$OUT"/perf-*.csv 2>/dev/null | head -1)
|
||||
[ -n "$newest" ] && python3 "$(dirname "$0")/perf_summary.py" "$newest"
|
||||
Reference in New Issue
Block a user