diff --git a/audit/history.jsonl b/audit/history.jsonl index 6865e6bc..ebada9e7 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -648,3 +648,4 @@ {"date": "2026-09-30", "unit": "EterLib/GrpImageInstance.cpp", "action": "improve-platform-rendering", "evidence": "Retina UI clarity: dynamic font atlas rebuild preserving cached pointers and logical metrics, 8-bit HiDPI glyph alpha, physical pixel alignment and 1:1 sampling; 2406 bundled HD textures. Full native suite 29 passed, 2 skipped, macOS packaged inventory screenshots and strict signature verification. See audit/evidence/2026-09-30-mac-ui-clarity.md."} {"date": "2026-09-30", "unit": "UserInterface/PythonPlayerAutoHunt.cpp", "action": "user-authorized-extension", "evidence": "Replace tag-and-chase gather with free server-authoritative cloak-style BeginFight pull, nearest 3/10/20 actual monster groups. Server rate limit and safety checks, request generations, stationary approach wait. host.autohunt_pull and desktop/mobile port.login_flow pass; 203 FreeBSD compile/deploy and live 3-group/9-monster reply verified. See tools/server/autohunt-pull/README.md."} {"date": "2026-09-30", "unit": "EterBase/CRC32.cpp", "action": "fix-platform-adapter", "evidence": "MI 8 Lite Android 15 fake-login SIGABRT/std::bad_alloc from directory CRC. Reject non-regular/null FILE handles and failed seeks; preserve normal CRC/position. port_utils_test pass, port_map check 0 errors; rebuilt APK installed and on-device fake-login/map entry succeeds. docs/XIAOMI8LITE-DEVICE-TEST-20260930.md"} +{"date": "2026-09-30", "action": "user-authorized-render-diagnostics", "units": ["UserInterface/PythonApplication.cpp", "GameLib/MapOutdoorRenderHTP.cpp", "GameLib/MapOutdoorRenderSTP.cpp"], "evidence": "7 same-scene MI 8 Lite runs, 42 two-second sample windows. Baseline 4.88 FPS/205.3ms, no terrain 15.45FPS/64.7ms, no scene 30.03FPS/17.7ms; repeated baseline 4.83 FPS. Normal rendering defaults unchanged. See docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md."} diff --git a/audit/port-map/GameLib/MapOutdoorRenderHTP.cpp.json b/audit/port-map/GameLib/MapOutdoorRenderHTP.cpp.json index b5535fbd..0586ec2b 100644 --- a/audit/port-map/GameLib/MapOutdoorRenderHTP.cpp.json +++ b/audit/port-map/GameLib/MapOutdoorRenderHTP.cpp.json @@ -9,7 +9,7 @@ "impl": [ "src/platform/GameLib/MapOutdoorRenderHTP.cpp:CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch" ], - "note": "Verbatim 40250 body plus an early return when IsNativeTerrainRenderEnabled() is false (Godot terrain adapter path, PORT-PLAN §3). Fake-server frame: far band no longer white, layout matches the STP path; pixel parity vs a 40250 frame not verified." + "note": "Verbatim 40250 body plus an early return when IsNativeTerrainRenderEnabled() is false (Godot terrain adapter path, PORT-PLAN \u00a73). Fake-server frame: far band no longer white, layout matches the STP path; pixel parity vs a 40250 frame not verified. Opt-in MT_DIAGNOSTIC_SKIP_RENDER isolates drawing only for device profiling; absent on normal launches, simulation/network unchanged. See tools/perf/run_render_isolation.py and docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md." }, "CMapOutdoor::__HardwareTransformPatch_RenderPatchSplat": { "status": "NEEDS_LIVE", diff --git a/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json b/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json index ffaf377a..0e6824a8 100644 --- a/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json +++ b/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json @@ -9,7 +9,7 @@ "impl": [ "src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch" ], - "note": "Verbatim 40250 body plus an early return when IsNativeTerrainRenderEnabled() is false (Godot terrain adapter path, PORT-PLAN §3), same gate as HTP. Reached only with SOFTWARE_TILING 1 or a non-fast-T&L device. Fake-server STP frame matches HTP layout; pixel parity vs a 40250 STP frame not verified." + "note": "Verbatim 40250 body plus an early return when IsNativeTerrainRenderEnabled() is false (Godot terrain adapter path, PORT-PLAN \u00a73), same gate as HTP. Reached only with SOFTWARE_TILING 1 or a non-fast-T&L device. Fake-server STP frame matches HTP layout; pixel parity vs a 40250 STP frame not verified. Opt-in MT_DIAGNOSTIC_SKIP_RENDER isolates drawing only for device profiling; absent on normal launches, simulation/network unchanged. See tools/perf/run_render_isolation.py and docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md." }, "CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat": { "status": "NEEDS_LIVE", diff --git a/audit/port-map/UserInterface/PythonApplication.cpp.json b/audit/port-map/UserInterface/PythonApplication.cpp.json index 2577aced..54ff8b98 100644 --- a/audit/port-map/UserInterface/PythonApplication.cpp.json +++ b/audit/port-map/UserInterface/PythonApplication.cpp.json @@ -74,7 +74,7 @@ "src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::RenderGame" ], "test": "tests/port/port_login_flow_test.cpp", - "note": "40250's !PERF_CHECKER_RENDER_GAME branch line for line, members reached through their singletons; the perf-timing branch (PERF_CHECKER_RENDER_GAME is FALSE) is left out." + "note": "40250's !PERF_CHECKER_RENDER_GAME branch line for line, members reached through their singletons; the perf-timing branch (PERF_CHECKER_RENDER_GAME is FALSE) is left out. Opt-in MT_DIAGNOSTIC_SKIP_RENDER isolates drawing only for device profiling; absent on normal launches, simulation/network unchanged. See tools/perf/run_render_isolation.py and docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md." }, "CPythonApplication::UpdateGame": { "status": "ADAPTED", @@ -157,7 +157,7 @@ "impl": [ "src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::Create" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons; m_LightManager.Initialize() (40250 :1263) now called; Destroy() still empty (m_LightManager.Destroy at 40250 :1418 not yet mirrored); 40250 :1196-1251 tiling selection mirrored (SOFTWARE_TILING cfg → GRAPHICS_CAPS_SOFTWARE_TILING before device create, auto tiling IsFastTNL ? HTP : STP via ReserveSoftwareTilingEnable, !IsFastTNL → CGrannyLODController::SetMinLODMode(true)); RecordingDevice caps report VS 1.1 + CLIPTLVERTS so auto picks HTP", + "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons; m_LightManager.Initialize() (40250 :1263) now called; Destroy() still empty (m_LightManager.Destroy at 40250 :1418 not yet mirrored); 40250 :1196-1251 tiling selection mirrored (SOFTWARE_TILING cfg \u2192 GRAPHICS_CAPS_SOFTWARE_TILING before device create, auto tiling IsFastTNL ? HTP : STP via ReserveSoftwareTilingEnable, !IsFastTNL \u2192 CGrannyLODController::SetMinLODMode(true)); RecordingDevice caps report VS 1.1 + CLIPTLVERTS so auto picks HTP", "test": "tests/port/port_login_flow_test.cpp" }, "CPythonApplication::SetGlobalCenterPosition": { diff --git a/docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md b/docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md new file mode 100644 index 00000000..f72969f7 --- /dev/null +++ b/docs/XIAOMI8LITE-RENDER-ISOLATION-20260930.md @@ -0,0 +1,42 @@ +# 小米 8 Lite 渲染分项测试(2026-09-30) + +同一台 MI 8 Lite / Android 15 LineageOS 22.2 / Adreno 512 Vulkan 1.1.128。 +Release 原生库、同一村庄、24 只怪物、相同相机,60% 场景分辨率、无 MSAA、30 FPS 上限。 +每组丢弃第一个加载后采样窗口,取 6 个两秒窗口;最后重复基准。手机处于 USB 充电状态。 + +| 条件 | FPS 均值 | GPU ms 均值 | 每帧 3D 绘制次数 | 电池温度 | +| --- | ---: | ---: | ---: | ---: | +| 基准 | 4.88 | 205.3 | 225 | 35.0°C | +| 关闭地形绘制 | 15.45 | 64.7 | 100 | 35.0°C | +| 关闭角色及角色阴影 | 5.13 | 194.5 | 191 | 35.0°C | +| 关闭特效绘制 | 4.88 | 205.6 | 224 | 36.0°C | +| 关闭触控覆盖绘制 | 4.88 | 205.5 | 225 | 36.0°C | +| 关闭全部 3D 场景绘制 | 30.03 | 17.7 | 0 | 36.0°C | +| 重复基准 | 4.83 | 206.4 | 225 | 36.0°C | + +关闭地形后帧率约为基准的 3.16 倍,GPU 耗时减少约 141 ms(69%);关闭角色仅带来小幅提升。 +关闭特效和触控覆盖基本无变化,但静止场景没有密集技能特效,不能据此推断战斗特效成本。 +关闭全部 3D 后达到 30 FPS 上限,证明这个测试下 UI/显示链路可以达到该目标,不代表完整游戏已达到 30 FPS。 +重复基准与初始基准接近(帧率约低 1%),本轮未观察到随测试时间明显恶化的现象。 + +## 判断与下一步 + +优先针对地形路径分析:多层 splat 混合、重复覆盖、每块地形的绘制次数、纹理采样和通用固定功能着色器。 +本轮是关闭绘制的定位实验,没有降低正式地形质量,也没有交付性能优化;正常启动全部绘制仍开启。 +硬件性能和驱动均可能影响这条路径,结果不能单独证明第三方 ROM 有问题,也不能完全排除它。 +驱动统计显示 Vulkan 加载失败次数为 0;这只证明加载成功,不证明着色器性能正常。 + +## GPU 调频证据限制 + +普通 shell 和应用 run-as 均被拒绝读取 kgsl GPU 频率/忙碌节点。 +`adb root` 被系统拒绝,提示开发者选项未允许 root 调试;未修改该设置,ADB 仍是普通 shell。 +因此未取得负载下 GPU 时钟,不能排除固定低频或调度问题;未执行原厂 ROM 的对照测试。 +Thermal HAL 不可用,表中的温度是电池传感器,不是 GPU 温度。 + +## 复现与验证 + +测试开关 `--diagnostic-skip-render terrain|characters|effects|touch|scene` 仅关闭指定绘制,不改变角色更新或网络。 +自动化工具:`python3 tools/perf/run_render_isolation.py build/qa/render-isolation`。 +本轮 Android 构建通过,7 组均成功进入同一场景;截图确认各开关对应的绘制确实消失。 +审计检查 0 错误。CSV、截图、内存快照及 summary.json 保存于 `build/qa/xiaomi8lite-isolation-20260930/`。 +测试结束自动恢复正常登录入口。手机时钟与工作区日期相差一天,原始 CSV 保留设备时间。 diff --git a/src/host/README.md b/src/host/README.md index 9794fa3a..b9f19248 100644 --- a/src/host/README.md +++ b/src/host/README.md @@ -159,3 +159,21 @@ Capture format Version 5 (backward-compatible with Versions 1–4) stores: 4. Per-draw fog color, vertex/table mode, range flag, start/end distance and density. Older captures omit these fields and replay without fog. The native shader now evaluates recorded D3D8 stage 0/1 color and alpha operations, including the original cloud operation (`D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20`), and applies the captured linear or exponential fog. Expanded UI image modes use the 40250 blend factors for screen/color-dodge and modulate. These state fixes do not by themselves establish pixel parity with a Windows 40250 screenshot; compare the same map, time, camera and UI state before treating a color difference as resolved. + +## Android render isolation + +`--diagnostic-skip-render terrain|characters|effects|touch|scene` is an opt-in profiling switch. +It skips only the selected drawing section; normal launches keep all rendering enabled, and simulation/network +processing continues. `terrain` skips terrain patches only; `characters` skips character drawing and its shadow +pass; `effects` skips background and effect-manager drawing; `touch` hides the added touch-control overlay; +`scene` skips recorded 3D game-scene draws while retaining UI. A quiet scene does not measure active skill effects. + +After installing the debug APK and syncing its Client assets, run: + +```sh +python3 tools/perf/run_render_isolation.py build/qa/render-isolation +``` + +The runner uses the same village/24-monster fixture, 60% scene scale, no MSAA and a 30 FPS cap. It saves +six two-second windows after discarding the first interval for each section, screenshots, memory snapshots, +and `summary.json`. The final repeated baseline checks drift. It restores the normal login launch when finished. diff --git a/src/host/main.cpp b/src/host/main.cpp index 86aefd91..a854d9de 100644 --- a/src/host/main.cpp +++ b/src/host/main.cpp @@ -148,6 +148,14 @@ int main(int argc, char** argv) { else if (arg == "--frames-in-flight" && i+1 < argc) frames_in_flight = std::stoi(argv[++i]); else if (arg == "--msaa" && i+1 < argc) setenv("MT_MSAA", argv[++i], 1); else if (arg == "--render-scale" && i+1 < argc) setenv("MT_RENDER_SCALE", argv[++i], 1); + else if (arg == "--diagnostic-skip-render" && i+1 < argc) { + const std::string section = argv[++i]; + if (section != "terrain" && section != "characters" && section != "effects" && + section != "touch" && section != "scene") + throw std::runtime_error("unknown diagnostic render section"); + setenv("MT_DIAGNOSTIC_SKIP_RENDER", section.c_str(), 1); + std::cerr << "diagnostic: skip render " << section << std::endl; + } else if (arg == "--cpu-shadow") setenv("MT_CPU_SHADOW", "1", 1); else throw std::runtime_error( "usage: mt_native_render [--frames N] [--interactive] [--login-screen|--selection-screen] " @@ -155,7 +163,7 @@ int main(int argc, char** argv) { "[--draws N] [--triangles-per-draw N] [--capture FILE] [--animate-first-draw] " "[--animate-bones] [--no-vsync] [--live-client DIR] [--fake-mobs N] " "[--gpu-skinning|--no-gpu-skinning] [--no-terrain] [--mobile] [--capture-out FILE] [--screenshot-out FILE.bmp] [--show-fps] [--perf-log] " - "[--fps-cap N] [--refresh-rate HZ] [--fps-mode 0|1|2] [--idle-fps N] [--idle-after S] [--frames-in-flight 1|2] [--msaa 1|2|4|8] [--render-scale 0.25..1] [--fake-idle-ms MS] [--cpu-shadow]"); + "[--fps-cap N] [--refresh-rate HZ] [--fps-mode 0|1|2] [--idle-fps N] [--idle-after S] [--frames-in-flight 1|2] [--msaa 1|2|4|8] [--render-scale 0.25..1] [--fake-idle-ms MS] [--cpu-shadow] [--diagnostic-skip-render terrain|characters|effects|touch|scene]"); } #ifdef MT_NATIVE_HAS_LIVE_CLIENT if (native_perf::PerfLog::requested_by_env()) g_perf_log = true; diff --git a/src/host/vk_frame.cpp b/src/host/vk_frame.cpp index bfebbb58..cdd9ada7 100644 --- a/src/host/vk_frame.cpp +++ b/src/host/vk_frame.cpp @@ -2,6 +2,7 @@ #include "vulkan_window.h" #include "host_util.h" +#include "platform/EterLib/RenderDiagnostics.h" #include #include @@ -156,6 +157,7 @@ void VulkanWindow::prepare_draws(const std::vector& draws, const T }; for (const auto& draw : draws) { + if (draw.scene && SkipDiagnosticRender("scene")) continue; const bool in_scene = scaled_scene && draw.scene && !draw.render_target; RenderTarget* target = nullptr; if (draw.render_target) { @@ -305,7 +307,7 @@ void VulkanWindow::prepare_ui(std::uint32_t ui_width, std::uint32_t ui_height, c std::vector combined_ui = ui_commands; if (touch_controller.is_enabled()) { touch_controller.update_screen_size(int(target_ui_w), int(target_ui_h), ui_safe_inset()); - touch_controller.append_ui_commands(combined_ui); + if (!SkipDiagnosticRender("touch")) touch_controller.append_ui_commands(combined_ui); } if (show_fps_ && fps_ >= 0) { // Top left, inside the safe area and clear of 40250's corner icon. diff --git a/src/platform/EterLib/RenderDiagnostics.h b/src/platform/EterLib/RenderDiagnostics.h new file mode 100644 index 00000000..eb3e1646 --- /dev/null +++ b/src/platform/EterLib/RenderDiagnostics.h @@ -0,0 +1,8 @@ +#pragma once +// Opt-in rendering isolation for device profiling. No simulation or packet changes. +#include +#include +inline bool SkipDiagnosticRender(const char* section) { + const char* requested = std::getenv("MT_DIAGNOSTIC_SKIP_RENDER"); + return requested && std::strcmp(requested, section) == 0; +} diff --git a/src/platform/GameLib/MapOutdoorRenderHTP.cpp b/src/platform/GameLib/MapOutdoorRenderHTP.cpp index fdbe823b..906fa771 100644 --- a/src/platform/GameLib/MapOutdoorRenderHTP.cpp +++ b/src/platform/GameLib/MapOutdoorRenderHTP.cpp @@ -7,11 +7,12 @@ #include "EterLib/StateManager.h" #include "../EterLib/RenderCommands3D.h" +#include "../EterLib/RenderDiagnostics.h" void CMapOutdoor::__RenderTerrain_RenderHardwareTransformPatch() { // PORT: see the file comment. - if (!IsNativeTerrainRenderEnabled()) + if (!IsNativeTerrainRenderEnabled() || SkipDiagnosticRender("terrain")) return; DWORD dwFogColor; diff --git a/src/platform/GameLib/MapOutdoorRenderSTP.cpp b/src/platform/GameLib/MapOutdoorRenderSTP.cpp index eba239a5..b23f4bf2 100644 --- a/src/platform/GameLib/MapOutdoorRenderSTP.cpp +++ b/src/platform/GameLib/MapOutdoorRenderSTP.cpp @@ -10,6 +10,7 @@ #include "EterLib/Camera.h" #include "EterLib/StateManager.h" #include "../EterLib/RenderCommands3D.h" +#include "../EterLib/RenderDiagnostics.h" struct SoftwareTransformPatch_SSplatVertex { @@ -25,7 +26,7 @@ struct SoftwareTransformPatch_SSplatVertex void CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch() { // PORT: see the file comment. - if (!IsNativeTerrainRenderEnabled()) + if (!IsNativeTerrainRenderEnabled() || SkipDiagnosticRender("terrain")) return; SoftwareTransformPatch_SRenderState kTPRS; diff --git a/src/platform/UserInterface/PythonApplication.cpp b/src/platform/UserInterface/PythonApplication.cpp index 1d5977f5..e4933efa 100644 --- a/src/platform/UserInterface/PythonApplication.cpp +++ b/src/platform/UserInterface/PythonApplication.cpp @@ -16,6 +16,7 @@ #include "../PlatformStub.h" #include "../EterLib/UIRenderCommands.h" #include "../EterLib/RenderCommands3D.h" +#include "../EterLib/RenderDiagnostics.h" #include "EterLib/GrpDevice.h" #include "EterGrnLib/LODController.h" #include "UserInterface/PythonBackground.h" @@ -214,7 +215,7 @@ void CPythonApplication::RenderGame() CPythonCharacterManager::Instance().Deform(); CEffectManager::Instance().Update(); - CPythonBackground::Instance().RenderCharacterShadowToTexture(); + if (!SkipDiagnosticRender("characters")) CPythonBackground::Instance().RenderCharacterShadowToTexture(); CPythonGraphic::Instance().SetGameRenderState(); CPythonGraphic::Instance().PushState(); @@ -237,16 +238,16 @@ void CPythonApplication::RenderGame() CPythonBackground::Instance().Render(); CPythonBackground::Instance().SetCharacterDirLight(); - CPythonCharacterManager::Instance().Render(); + if (!SkipDiagnosticRender("characters")) CPythonCharacterManager::Instance().Render(); CPythonBackground::Instance().SetBackgroundDirLight(); CPythonBackground::Instance().RenderWater(); CPythonBackground::Instance().RenderSnow(); - CPythonBackground::Instance().RenderEffect(); + if (!SkipDiagnosticRender("effects")) CPythonBackground::Instance().RenderEffect(); CPythonBackground::Instance().EndEnvironment(); - CEffectManager::Instance().Render(); + if (!SkipDiagnosticRender("effects")) CEffectManager::Instance().Render(); CPythonItem::Instance().Render(); CFlyingManager::Instance().Render(); diff --git a/tools/perf/run_render_isolation.py b/tools/perf/run_render_isolation.py new file mode 100644 index 00000000..ddd39e8c --- /dev/null +++ b/tools/perf/run_render_isolation.py @@ -0,0 +1,79 @@ +# coding: utf-8 +"""Run opt-in single-section render isolation on a connected debug Android APK. +Requires its Client resources to be synced. Writes CSVs/screenshots/summary locally. +Normal startup is restored afterwards; device system settings are not changed. +""" +import argparse +import csv +import json +import shlex +import statistics +import subprocess +import time +from pathlib import Path + +PKG = 'org.metin2port.client' +PERF = '/sdcard/Android/data/' + PKG + '/files/perf/' + +def adb(*args): + return subprocess.check_output(['adb', *args], stderr=subprocess.STDOUT) + +def shell(command): + return adb('shell', command).decode(errors='replace').strip() + +def files(): + return set(shell('ls ' + PERF + 'perf-*.csv 2>/dev/null').splitlines()) + +def parse(text): + return list(csv.DictReader(line for line in text.splitlines() if line and not line.startswith('#'))) + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('output', type=Path) + parser.add_argument('--sections', default='baseline,terrain,characters,effects,touch,scene,baseline_repeat') + parser.add_argument('--windows', type=int, default=6) + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=True) + results = [] + try: + for section in args.sections.split(','): + before = files() + launch = '--interactive --fake-mobs 24 --perf-log --show-fps --fps-cap 30 --msaa 1 --render-scale 0.6' + if not section.startswith('baseline'): + launch += ' --diagnostic-skip-render ' + section + shell('am force-stop ' + PKG) + shell('am start -n ' + PKG + '/.MainActivity --es args ' + shlex.quote(launch)) + print('START ' + section, flush=True) + deadline = time.monotonic() + 100 + path = None + rows = [] + while time.monotonic() < deadline: + time.sleep(2) + candidates = files() - before + if candidates: + path = sorted(candidates)[-1] + raw = adb('exec-out', 'cat', path).decode(errors='replace') + rows = [r for r in parse(raw) if r.get('map') == 'metin2_map_a1' and r.get('actors') == '28'] + # Exclude the first interval after loading, then collect the requested count. + if len(rows) >= args.windows + 1: + break + if len(rows) < args.windows + 1: + raise RuntimeError('insufficient valid game samples: ' + section) + rows = rows[1:args.windows+1] + (args.output/(section+'.csv')).write_bytes(adb('exec-out', 'cat', path)) + (args.output/(section+'.png')).write_bytes(adb('exec-out','screencap','-p')) + (args.output/(section+'-memory.txt')).write_text(shell('dumpsys meminfo '+PKG)) + result = {'section':section,'args':launch,'device_csv':path,'windows':len(rows)} + for field in ['fps','gpu_ms','app_ms','draws','vertices','frame_p95_ms','rss_mb','batt_c']: + values = [float(r[field]) for r in rows] + result[field] = {'mean':statistics.mean(values),'min':min(values),'max':max(values)} + results.append(result) + (args.output/'summary.json').write_text(json.dumps(results,indent=2)+'\n') + print('DONE %s fps=%.2f gpu=%.1fms draws=%.0f batt=%.1fC' % + (section,result['fps']['mean'],result['gpu_ms']['mean'],result['draws']['mean'],result['batt_c']['mean']),flush=True) + finally: + shell('am force-stop ' + PKG) + shell('am start -n ' + PKG + '/.MainActivity') + +if __name__ == '__main__': + main()