using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using Unity.Profiling; using UnityEngine; using UnityEngine.UI; namespace TKS { /// /// UI 층 구조 실측(PD 2026-11-10 캔버스 분할 검증) — 지금 화면의 렌더 통계(배치 · 드로우콜 · SetPass)와 /// 켜진 캔버스마다 그래픽 수 · 등급 · 레이캐스터 · 매 프레임 움직이는 요소(UiPulse · UiPop · 스크롤 내용 · 미니맵 · 커서)를 기록한다. /// 「매 프레임 재구성 그래픽」 = 움직이는 요소가 든 캔버스들의 그래픽 수 합(캔버스는 자식 하나만 바뀌어도 전체를 다시 묶음). /// public static class UiLayerProbe { static ProfilerRecorder batches; static ProfilerRecorder draws; static ProfilerRecorder setPass; static void Begin() { if (!batches.Valid) batches = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count"); if (!draws.Valid) draws = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count"); if (!setPass.Valid) setPass = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count"); } /// 몇 프레임 렌더 뒤 측정 → 로그 한 줄 + `ui_layers.csv`(캔버스별 행) 추가. public static IEnumerator Measure(string shot, System.Action log, string dir) { Begin(); for (int i = 0; i < 4; i++) yield return null; long b = batches.Valid ? batches.LastValue : -1; long d = draws.Valid ? draws.LastValue : -1; long sp = setPass.Valid ? setPass.LastValue : -1; #if UNITY_EDITOR if (b <= 0) b = UnityEditor.UnityStats.batches; if (d <= 0) d = UnityEditor.UnityStats.drawCalls; if (sp <= 0) sp = UnityEditor.UnityStats.setPassCalls; #endif var movers = Movers(); var frameCanvases = new HashSet(); foreach (var m in movers) { var c = m.GetComponentInParent(); if (c != null) frameCanvases.Add(c); } var rows = new StringBuilder(); int roots = 0, nested = 0, graphics = 0, frameGraphics = 0; foreach (var c in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { if (!c.isActiveAndEnabled) continue; int n = GraphicRegistry.GetGraphicsForCanvas(c).Count; if (c.isRootCanvas) roots++; else nested++; graphics += n; bool frame = frameCanvases.Contains(c); if (frame) frameGraphics += n; var tag = c.GetComponent(); rows.Append(shot).Append(',').Append(Path(c.transform)).Append(',').Append(c.isRootCanvas ? "root" : "nested").Append(',') .Append(tag != null ? tag.rate.ToString() : "-").Append(',').Append(n).Append(',') .Append(c.GetComponent() != null ? "ray" : "-").Append(',').Append(frame ? "frame" : "-").Append('\n'); } log("layers " + shot + " batches=" + b + " drawcalls=" + d + " setpass=" + sp + " canvases=" + roots + "+" + nested + " graphics=" + graphics + " frameRebuildGraphics=" + frameGraphics); try { string csv = System.IO.Path.Combine(dir, "ui_layers.csv"); if (!File.Exists(csv)) File.WriteAllText(csv, "shot,canvas,kind,rate,graphics,raycaster,frame_mover\n", new UTF8Encoding(true)); File.AppendAllText(csv, rows.ToString(), new UTF8Encoding(false)); } catch { } } /// 매 프레임 바뀌는 요소(켜진 것만). static List Movers() { var list = new List(); foreach (var p in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) list.Add(p.transform); foreach (var p in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) list.Add(p.transform); foreach (var s in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) if (s.content != null) list.Add(s.content); foreach (var t in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { // 월드맵 커서(위아래 흔들림) · 전투 미니맵 시야 사각형(카메라 이동) · 유닛 점(이동) if (t.name == "cursor") list.Add(t); else if (t.name == "View" && t.parent != null && (t.parent.name == "Minimap" || t.parent.name == "MinimapLive")) list.Add(t); } return list; } static string Path(Transform t) { var s = t.name; for (var p = t.parent; p != null; p = p.parent) s = p.name + "/" + s; return s.Replace(',', ' '); } } }