SHA256
248 lines
12 KiB
C#
248 lines
12 KiB
C#
using System;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using ADOFAIRenderer.Core;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace ADOFAIRenderer.UI;
|
|
|
|
/// <summary>
|
|
/// On-screen overlay shown while a render is running: a statistics panel in the top-right corner and a level progress
|
|
/// bar near the bottom of the screen.
|
|
/// <para>
|
|
/// Created by <c>RenderManager.StartFFmpegThread</c> on a GameObject kept across scene loads, and reused for later
|
|
/// renders. The overlay is drawn on the game screen only; it is not part of the encoded video frames.
|
|
/// <see cref="Update"/> rebuilds the text every frame on the Unity main thread.
|
|
/// </para>
|
|
/// </summary>
|
|
internal class DebugMonitorUI : MonoBehaviour
|
|
{
|
|
// --- Progress Bar UI Configuration Constants ---
|
|
// Sizes and positions are canvas units at the 1920x1080 reference resolution of the CanvasScaler.
|
|
|
|
/// <summary>Width of the progress bar; the fill width is <c>BarWidth * progress</c>.</summary>
|
|
private const float BarWidth = 800f;
|
|
/// <summary>Height of the progress bar.</summary>
|
|
private const float BarHeight = 28f;
|
|
/// <summary>Horizontal offset of the bar center from the screen center (0 = centered).</summary>
|
|
private const float BarPosX = 0f;
|
|
/// <summary>Vertical offset of the bar center from the screen center; -420 is 120 units above the bottom edge (540 - 420).</summary>
|
|
private const float BarPosY = -420f; // Y position relative to screen center (Reference: 1920x1080)
|
|
/// <summary>Font size of the percentage text drawn on the bar.</summary>
|
|
private const int BarTextFontSize = 16; // Increased font size for visibility
|
|
/// <summary>Unfilled bar color: opaque near-black with a slight blue tint.</summary>
|
|
private static readonly Color BarBackgroundColor = new(0.08f, 0.08f, 0.1f, 1.0f);
|
|
/// <summary>Filled part color: opaque cyan (#00CCFF).</summary>
|
|
private static readonly Color BarFillColor = new(0.0f, 0.8f, 1.0f, 1.0f); // Sleek cyan/blue
|
|
/// <summary>Percentage text color: opaque white.</summary>
|
|
private static readonly Color BarTextColor = new(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
// --- Progress Bar Outline Configuration Constants ---
|
|
|
|
/// <summary>Color of the uGUI <see cref="Outline"/> drawn around the bar background.</summary>
|
|
private static readonly Color BarOutlineColor = Color.white; // Contrasting white outline
|
|
/// <summary>
|
|
/// <c>Outline.effectDistance</c> of the bar outline: the background is redrawn shifted by +-3 units on both axes,
|
|
/// which shows as a border about 3 units wide.
|
|
/// </summary>
|
|
private static readonly Vector2 BarOutlineDistance = new(3.0f, -3.0f); // Outline thickness
|
|
|
|
/// <summary>Statistics text inside the top-right panel.</summary>
|
|
private TextMeshProUGUI _text = null!;
|
|
/// <summary>Progress bar background object (parent of the fill and the percentage text).</summary>
|
|
private GameObject _progressBarObj = null!;
|
|
/// <summary>Progress bar fill object.</summary>
|
|
private GameObject _progressBarFillObj = null!;
|
|
/// <summary>Fill rect, anchored to the left edge of the bar; its width is set every frame.</summary>
|
|
private RectTransform _fillRect = null!;
|
|
/// <summary>Text on the bar: percentage and "current floor / last floor index".</summary>
|
|
private TextMeshProUGUI _progressText = null!;
|
|
|
|
/// <summary>
|
|
/// Builds the overlay canvas, the statistics panel and the progress bar. Layout values are canvas units at the
|
|
/// 1920x1080 reference resolution.
|
|
/// </summary>
|
|
private void Start()
|
|
{
|
|
// Set UI layer (5, Unity's built-in "UI" layer)
|
|
gameObject.layer = 5;
|
|
|
|
// Canvas setup. Sorting order 9999 is below EscapeHandlerUI (10000) and the dialogs/windows of the mod.
|
|
var canvas = gameObject.AddComponent<Canvas>();
|
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
|
canvas.sortingOrder = 9999;
|
|
|
|
// Dynamic resolution scaling against a 1920x1080 reference resolution; match 0.5 blends width and height
|
|
var scaler = gameObject.AddComponent<CanvasScaler>();
|
|
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
|
scaler.referenceResolution = new Vector2(1920, 1080);
|
|
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
|
scaler.matchWidthOrHeight = 0.5f;
|
|
|
|
gameObject.AddComponent<GraphicRaycaster>();
|
|
|
|
// Create background panel: 360x220 units, pivot and anchor at the top-right corner, inset 30 units from both edges
|
|
var panelObj = new GameObject("BackgroundPanel");
|
|
panelObj.transform.SetParent(transform, false);
|
|
panelObj.layer = 5;
|
|
|
|
var image = panelObj.AddComponent<Image>();
|
|
image.color = new Color(0.08f, 0.08f, 0.1f, 0.88f); // Dark tone acrylic style
|
|
|
|
var panelRect = panelObj.GetComponent<RectTransform>();
|
|
panelRect.anchorMin = new Vector2(1, 1);
|
|
panelRect.anchorMax = new Vector2(1, 1);
|
|
panelRect.pivot = new Vector2(1, 1);
|
|
panelRect.anchoredPosition = new Vector2(-30, -30);
|
|
panelRect.sizeDelta = new Vector2(360, 220);
|
|
|
|
// Create text: 16 pt rich text, top-left aligned, filling the panel minus a 20-unit margin
|
|
var textObj = new GameObject("MonitorText");
|
|
textObj.transform.SetParent(panelObj.transform, false);
|
|
textObj.layer = 5;
|
|
|
|
_text = textObj.AddComponent<TextMeshProUGUI>();
|
|
_text.richText = true;
|
|
_text.fontSize = 16;
|
|
_text.alignment = TextAlignmentOptions.TopLeft;
|
|
|
|
// Wrap long lines and let text that exceeds the panel height overflow instead of being truncated
|
|
_text.textWrappingMode = TextWrappingModes.Normal;
|
|
_text.overflowMode = TextOverflowModes.Overflow;
|
|
|
|
// Apply the mod's bundled font (loaded by ModAssets.Load at mod initialization)
|
|
_text.font = ModAssets.NanumSquareNeoNormal;
|
|
|
|
var textRect = textObj.GetComponent<RectTransform>();
|
|
textRect.anchorMin = Vector2.zero;
|
|
textRect.anchorMax = Vector2.one;
|
|
textRect.offsetMin = new Vector2(20, 20); // Padding margin
|
|
textRect.offsetMax = new Vector2(-20, -20);
|
|
|
|
// --- Progress Bar UI Setup ---
|
|
// 1. Progress Bar Container: centered anchor/pivot, placed at (BarPosX, BarPosY) from the screen center
|
|
_progressBarObj = new GameObject("ProgressBar");
|
|
_progressBarObj.transform.SetParent(transform, false);
|
|
_progressBarObj.layer = 5;
|
|
|
|
var bgImage = _progressBarObj.AddComponent<Image>();
|
|
bgImage.color = BarBackgroundColor;
|
|
|
|
// Add progress bar outline component
|
|
var bgOutline = _progressBarObj.AddComponent<Outline>();
|
|
bgOutline.effectColor = BarOutlineColor;
|
|
bgOutline.effectDistance = BarOutlineDistance;
|
|
|
|
var barRect = _progressBarObj.GetComponent<RectTransform>();
|
|
barRect.anchorMin = new Vector2(0.5f, 0.5f);
|
|
barRect.anchorMax = new Vector2(0.5f, 0.5f);
|
|
barRect.pivot = new Vector2(0.5f, 0.5f);
|
|
barRect.anchoredPosition = new Vector2(BarPosX, BarPosY);
|
|
barRect.sizeDelta = new Vector2(BarWidth, BarHeight);
|
|
|
|
// 2. Progress Bar Fill: anchored to the left edge and stretched vertically (anchor x 0..0, y 0..1).
|
|
// sizeDelta.x is the fill width in units; Update sets it to BarWidth * progress.
|
|
_progressBarFillObj = new GameObject("ProgressBarFill");
|
|
_progressBarFillObj.transform.SetParent(_progressBarObj.transform, false);
|
|
_progressBarFillObj.layer = 5;
|
|
|
|
var fillImage = _progressBarFillObj.AddComponent<Image>();
|
|
fillImage.color = BarFillColor;
|
|
|
|
_fillRect = _progressBarFillObj.GetComponent<RectTransform>();
|
|
_fillRect.anchorMin = new Vector2(0, 0);
|
|
_fillRect.anchorMax = new Vector2(0, 1);
|
|
_fillRect.pivot = new Vector2(0, 0.5f);
|
|
_fillRect.anchoredPosition = Vector2.zero;
|
|
_fillRect.sizeDelta = new Vector2(0, 0); // width will be updated dynamically in Update()
|
|
|
|
// 3. Progress Bar Text: centered over the whole bar
|
|
var progressTextObj = new GameObject("ProgressBarText");
|
|
progressTextObj.transform.SetParent(_progressBarObj.transform, false);
|
|
progressTextObj.layer = 5;
|
|
|
|
_progressText = progressTextObj.AddComponent<TextMeshProUGUI>();
|
|
_progressText.richText = true;
|
|
_progressText.fontSize = BarTextFontSize;
|
|
_progressText.alignment = TextAlignmentOptions.Center;
|
|
_progressText.font = ModAssets.NanumSquareNeoNormal;
|
|
_progressText.color = BarTextColor;
|
|
|
|
// Black outline (90% opacity, 1.5-unit offset) keeps the white text readable over both the fill and the background
|
|
var textOutline = progressTextObj.AddComponent<Outline>();
|
|
textOutline.effectColor = new Color(0f, 0f, 0f, 0.9f);
|
|
textOutline.effectDistance = new Vector2(1.5f, -1.5f);
|
|
|
|
var textRectBar = progressTextObj.GetComponent<RectTransform>();
|
|
textRectBar.anchorMin = Vector2.zero;
|
|
textRectBar.anchorMax = Vector2.one;
|
|
textRectBar.offsetMin = Vector2.zero;
|
|
textRectBar.offsetMax = Vector2.zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refreshes the statistics text and the progress bar from <see cref="RenderManager"/> and the game state.
|
|
/// Values fall back to 0 (and the queue limit to <c>RenderManager.MaxQueuedFrames</c>) when no RenderManager exists.
|
|
/// </summary>
|
|
private void Update()
|
|
{
|
|
if (!_text) return;
|
|
|
|
// Statistics lines:
|
|
// Queue - frames waiting in ImageQueue for the render thread / queue frame limit
|
|
// Rendered Frames - RenderManager.frameCount (frames produced by the game)
|
|
// Frames Sent - RenderManager.framesSent (frames handed to the encoder; written by the render thread)
|
|
// Throttling - RenderManager.throttleCount (times the main thread started waiting because ImageQueue
|
|
// held more frames than its limit)
|
|
// Readback failures- RenderManager.readbackFailureCount (AsyncGPUReadback requests that completed with an error)
|
|
// Mono heap - GC.GetTotalMemory(false), managed heap size in MiB without forcing a collection
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("<b><color=#00ccff>ADOFAI Renderer Realtime Monitor</color></b>");
|
|
sb.AppendLine();
|
|
|
|
var queueCount = 0;
|
|
if (RenderManager.Instance)
|
|
{
|
|
// ImageQueue is shared with the render thread, so its count is read under the same lock
|
|
lock (RenderManager.Instance.ImageQueue)
|
|
{
|
|
queueCount = RenderManager.Instance.ImageQueue.Count;
|
|
}
|
|
}
|
|
|
|
sb.AppendLine($"• Queue: <b>{queueCount}</b> / {(RenderManager.Instance ? RenderManager.Instance.QueueFrameLimit : RenderManager.MaxQueuedFrames)} frames");
|
|
sb.AppendLine($"• Rendered Frames: <b>{(RenderManager.Instance ? RenderManager.Instance.frameCount : 0)}</b>");
|
|
sb.AppendLine($"• Frames Sent: <b>{(RenderManager.Instance ? Interlocked.Read(ref RenderManager.Instance.framesSent) : 0)}</b>");
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine($"• Throttling Controls: {(RenderManager.Instance ? RenderManager.Instance.throttleCount : 0)} times");
|
|
sb.AppendLine($"• Readback Hard Failures: <color=#ff3333>{(RenderManager.Instance ? RenderManager.Instance.readbackFailureCount : 0)}</color> times");
|
|
|
|
var memoryMb = GC.GetTotalMemory(false) / (1024.0 * 1024.0);
|
|
sb.AppendLine($"• Mono Heap Memory: {memoryMb:F2} MB");
|
|
|
|
_text.text = sb.ToString();
|
|
|
|
// Update Progress Bar.
|
|
// progress = seqID of the current floor / index of the last floor (listFloors.Count - 1), clamped to 0..1.
|
|
// The text shows the percentage with one decimal and "(current / last)".
|
|
var progress = 0f;
|
|
var currentFloor = 0;
|
|
var totalFloors = 0;
|
|
|
|
if (scrController.instance && ADOBase.lm && ADOBase.lm.listFloors != null)
|
|
{
|
|
currentFloor = scrController.instance.currFloor ? scrController.instance.currFloor.seqID : 0;
|
|
totalFloors = ADOBase.lm.listFloors.Count - 1;
|
|
if (totalFloors > 0)
|
|
{
|
|
progress = Mathf.Clamp01((float)currentFloor / totalFloors);
|
|
}
|
|
}
|
|
|
|
if (_fillRect) _fillRect.sizeDelta = new Vector2(BarWidth * progress, 0);
|
|
if (_progressText) _progressText.text = $"{progress * 100f:F1}% ({currentFloor}/{totalFloors})";
|
|
}
|
|
} |