memory reports
This commit is contained in:
183
Editor/Core/SceneMemoryReport.cs
Normal file
183
Editor/Core/SceneMemoryReport.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace SplashEdit.EditorCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Memory analysis report for a single exported scene.
|
||||||
|
/// All values are in bytes unless noted otherwise.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class SceneMemoryReport
|
||||||
|
{
|
||||||
|
public string sceneName;
|
||||||
|
|
||||||
|
// ─── Main RAM ───
|
||||||
|
public long splashpackFileSize; // Total file on disc
|
||||||
|
public long splashpackLiveSize; // Bytes kept in RAM at runtime (before bulk data freed)
|
||||||
|
public int triangleCount;
|
||||||
|
public int gameObjectCount;
|
||||||
|
|
||||||
|
// ─── VRAM (1024 x 512 x 2 = 1,048,576 bytes) ───
|
||||||
|
public long framebufferSize; // 2 x W x H x 2
|
||||||
|
public long textureAtlasSize; // Sum of atlas pixel data
|
||||||
|
public long clutSize; // Sum of CLUT entries x 2
|
||||||
|
public long fontVramSize; // Custom font textures
|
||||||
|
public int atlasCount;
|
||||||
|
public int clutCount;
|
||||||
|
|
||||||
|
// ─── SPU RAM (512KB, 0x1010 reserved) ───
|
||||||
|
public long audioDataSize;
|
||||||
|
public int audioClipCount;
|
||||||
|
|
||||||
|
// ─── CD Storage ───
|
||||||
|
public long loaderPackSize;
|
||||||
|
|
||||||
|
// ─── Constants ───
|
||||||
|
public const long TOTAL_RAM = 2 * 1024 * 1024;
|
||||||
|
public const long KERNEL_RESERVED = 0x10000; // 64KB kernel area
|
||||||
|
public const long USABLE_RAM = TOTAL_RAM - KERNEL_RESERVED;
|
||||||
|
public const long TOTAL_VRAM = 1024 * 512 * 2; // 1MB
|
||||||
|
public const long TOTAL_SPU = 512 * 1024;
|
||||||
|
public const long SPU_RESERVED = 0x1010;
|
||||||
|
public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED;
|
||||||
|
|
||||||
|
// Fixed runtime overhead from C++ (renderer.hh constants)
|
||||||
|
public const long BUMP_ALLOC_TOTAL = 2L * 8096 * 24; // ~380KB
|
||||||
|
public const long OT_TOTAL = 2L * 16384 * 4; // ~128KB
|
||||||
|
public const long VIS_REFS = 4096 * 4; // 16KB
|
||||||
|
public const long STACK_ESTIMATE = 32 * 1024; // 32KB
|
||||||
|
public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate
|
||||||
|
public const long SYSTEM_FONT_VRAM = 4 * 1024; // ~4KB
|
||||||
|
|
||||||
|
public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD;
|
||||||
|
|
||||||
|
/// <summary>RAM used by scene data (live portion of splashpack).</summary>
|
||||||
|
public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize;
|
||||||
|
|
||||||
|
/// <summary>Total estimated RAM: fixed overhead + scene data. Does NOT include code/BSS.</summary>
|
||||||
|
public long TotalRamUsage => FixedOverhead + SceneRamUsage;
|
||||||
|
|
||||||
|
public long TotalVramUsed => framebufferSize + textureAtlasSize + clutSize + fontVramSize + SYSTEM_FONT_VRAM;
|
||||||
|
public long TotalSpuUsed => audioDataSize;
|
||||||
|
public long TotalDiscSize => splashpackFileSize + loaderPackSize;
|
||||||
|
|
||||||
|
public float RamPercent => Mathf.Clamp01((float)TotalRamUsage / USABLE_RAM) * 100f;
|
||||||
|
public float VramPercent => Mathf.Clamp01((float)TotalVramUsed / TOTAL_VRAM) * 100f;
|
||||||
|
public float SpuPercent => USABLE_SPU > 0 ? Mathf.Clamp01((float)TotalSpuUsed / USABLE_SPU) * 100f : 0f;
|
||||||
|
|
||||||
|
public long RamFree => USABLE_RAM - TotalRamUsage;
|
||||||
|
public long VramFree => TOTAL_VRAM - TotalVramUsed;
|
||||||
|
public long SpuFree => USABLE_SPU - TotalSpuUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a SceneMemoryReport by reading the exported splashpack binary header
|
||||||
|
/// and the scene's VRAM/audio data.
|
||||||
|
/// </summary>
|
||||||
|
public static class SceneMemoryAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Analyze an exported scene. Call after ExportToPath().
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sceneName">Display name for the scene.</param>
|
||||||
|
/// <param name="splashpackPath">Path to the exported .splashpack file.</param>
|
||||||
|
/// <param name="loaderPackPath">Path to the loading screen file (may be null).</param>
|
||||||
|
/// <param name="atlases">Texture atlases from the export pipeline.</param>
|
||||||
|
/// <param name="audioExportSizes">Array of ADPCM byte sizes per audio clip.</param>
|
||||||
|
/// <param name="fonts">Custom font descriptors.</param>
|
||||||
|
public static SceneMemoryReport Analyze(
|
||||||
|
string sceneName,
|
||||||
|
string splashpackPath,
|
||||||
|
string loaderPackPath,
|
||||||
|
SplashEdit.RuntimeCode.TextureAtlas[] atlases,
|
||||||
|
long[] audioExportSizes,
|
||||||
|
SplashEdit.RuntimeCode.PSXFontData[] fonts,
|
||||||
|
int triangleCount = 0)
|
||||||
|
{
|
||||||
|
var r = new SceneMemoryReport { sceneName = sceneName };
|
||||||
|
|
||||||
|
// ── File sizes ──
|
||||||
|
if (File.Exists(splashpackPath))
|
||||||
|
r.splashpackFileSize = new FileInfo(splashpackPath).Length;
|
||||||
|
if (!string.IsNullOrEmpty(loaderPackPath) && File.Exists(loaderPackPath))
|
||||||
|
r.loaderPackSize = new FileInfo(loaderPackPath).Length;
|
||||||
|
|
||||||
|
r.triangleCount = triangleCount;
|
||||||
|
|
||||||
|
// ── Parse splashpack header for counts and pixelDataOffset ──
|
||||||
|
if (File.Exists(splashpackPath))
|
||||||
|
{
|
||||||
|
try { ReadHeader(splashpackPath, r); }
|
||||||
|
catch (Exception e) { Debug.LogWarning($"Memory report: failed to read header: {e.Message}"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Framebuffers ──
|
||||||
|
int fbW = SplashSettings.ResolutionWidth;
|
||||||
|
int fbH = SplashSettings.ResolutionHeight;
|
||||||
|
int fbCount = SplashSettings.DualBuffering ? 2 : 1;
|
||||||
|
r.framebufferSize = fbW * fbH * 2L * fbCount;
|
||||||
|
|
||||||
|
// ── VRAM: Texture atlases + CLUTs ──
|
||||||
|
if (atlases != null)
|
||||||
|
{
|
||||||
|
r.atlasCount = atlases.Length;
|
||||||
|
foreach (var atlas in atlases)
|
||||||
|
{
|
||||||
|
r.textureAtlasSize += atlas.Width * SplashEdit.RuntimeCode.TextureAtlas.Height * 2L;
|
||||||
|
foreach (var tex in atlas.ContainedTextures)
|
||||||
|
{
|
||||||
|
if (tex.ColorPalette != null)
|
||||||
|
{
|
||||||
|
r.clutCount++;
|
||||||
|
r.clutSize += tex.ColorPalette.Count * 2L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── VRAM: Custom fonts ──
|
||||||
|
if (fonts != null)
|
||||||
|
{
|
||||||
|
foreach (var font in fonts)
|
||||||
|
{
|
||||||
|
if (font.TextureHeight > 0)
|
||||||
|
r.fontVramSize += 64L * font.TextureHeight * 2; // 4bpp = 64 hwords wide
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SPU: Audio ──
|
||||||
|
if (audioExportSizes != null)
|
||||||
|
{
|
||||||
|
r.audioClipCount = audioExportSizes.Length;
|
||||||
|
foreach (long sz in audioExportSizes)
|
||||||
|
r.audioDataSize += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReadHeader(string path, SceneMemoryReport r)
|
||||||
|
{
|
||||||
|
using (var reader = new BinaryReader(File.OpenRead(path)))
|
||||||
|
{
|
||||||
|
if (reader.BaseStream.Length < 104) return;
|
||||||
|
|
||||||
|
// Magic + version (4 bytes)
|
||||||
|
reader.ReadBytes(4);
|
||||||
|
|
||||||
|
// luaFileCount(2) + gameObjectCount(2) + textureAtlasCount(2) + clutCount(2)
|
||||||
|
reader.ReadUInt16(); // luaFileCount
|
||||||
|
r.gameObjectCount = reader.ReadUInt16();
|
||||||
|
reader.ReadUInt16(); // textureAtlasCount
|
||||||
|
reader.ReadUInt16(); // clutCount
|
||||||
|
|
||||||
|
// Skip to pixelDataOffset at byte 100
|
||||||
|
reader.BaseStream.Seek(100, SeekOrigin.Begin);
|
||||||
|
uint pixelDataOffset = reader.ReadUInt32();
|
||||||
|
r.splashpackLiveSize = pixelDataOffset > 0 ? pixelDataOffset : r.splashpackFileSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Editor/Core/SceneMemoryReport.cs.meta
Normal file
2
Editor/Core/SceneMemoryReport.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f68ba273eb88c3b4796e43f40b226c71
|
||||||
@@ -42,6 +42,10 @@ namespace SplashEdit.EditorCode
|
|||||||
// ───── Scene List ─────
|
// ───── Scene List ─────
|
||||||
private List<SceneEntry> _sceneList = new List<SceneEntry>();
|
private List<SceneEntry> _sceneList = new List<SceneEntry>();
|
||||||
|
|
||||||
|
// ───── Memory Reports ─────
|
||||||
|
private List<SceneMemoryReport> _memoryReports = new List<SceneMemoryReport>();
|
||||||
|
private bool _showMemoryReport = true;
|
||||||
|
|
||||||
// ───── Toolchain Cache ─────
|
// ───── Toolchain Cache ─────
|
||||||
private bool _hasMIPS;
|
private bool _hasMIPS;
|
||||||
private bool _hasMake;
|
private bool _hasMake;
|
||||||
@@ -585,6 +589,11 @@ namespace SplashEdit.EditorCode
|
|||||||
// Clean Build toggle
|
// Clean Build toggle
|
||||||
SplashSettings.CleanBuild = EditorGUILayout.Toggle("Clean Build", SplashSettings.CleanBuild);
|
SplashSettings.CleanBuild = EditorGUILayout.Toggle("Clean Build", SplashSettings.CleanBuild);
|
||||||
|
|
||||||
|
// Memory Overlay toggle
|
||||||
|
SplashSettings.MemoryOverlay = EditorGUILayout.Toggle(
|
||||||
|
new GUIContent("Memory Overlay", "Show heap/RAM usage bar at top-right during gameplay"),
|
||||||
|
SplashSettings.MemoryOverlay);
|
||||||
|
|
||||||
// Serial port (only for Real Hardware)
|
// Serial port (only for Real Hardware)
|
||||||
if (SplashSettings.Target == BuildTarget.RealHardware)
|
if (SplashSettings.Target == BuildTarget.RealHardware)
|
||||||
{
|
{
|
||||||
@@ -680,6 +689,145 @@ namespace SplashEdit.EditorCode
|
|||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
|
|
||||||
|
// Memory report (shown after export)
|
||||||
|
if (_memoryReports.Count > 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(8);
|
||||||
|
DrawMemoryReports();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// Memory Reports
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private void DrawMemoryReports()
|
||||||
|
{
|
||||||
|
_showMemoryReport = DrawSectionFoldout("Memory Report", _showMemoryReport);
|
||||||
|
if (!_showMemoryReport) return;
|
||||||
|
|
||||||
|
foreach (var report in _memoryReports)
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
|
||||||
|
|
||||||
|
GUILayout.Label($"Scene: {report.sceneName}", PSXEditorStyles.SectionHeader);
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
|
// Main RAM bar
|
||||||
|
DrawMemoryBar("Main RAM",
|
||||||
|
report.TotalRamUsage, SceneMemoryReport.USABLE_RAM,
|
||||||
|
report.RamPercent,
|
||||||
|
new Color(0.3f, 0.6f, 1f),
|
||||||
|
$"Scene: {FormatBytes(report.SceneRamUsage)} | " +
|
||||||
|
$"Fixed: {FormatBytes(report.FixedOverhead)} | " +
|
||||||
|
$"Free: {FormatBytes(report.RamFree)}");
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
|
// VRAM bar
|
||||||
|
DrawMemoryBar("VRAM",
|
||||||
|
report.TotalVramUsed, SceneMemoryReport.TOTAL_VRAM,
|
||||||
|
report.VramPercent,
|
||||||
|
new Color(0.9f, 0.5f, 0.2f),
|
||||||
|
$"FB: {FormatBytes(report.framebufferSize)} | " +
|
||||||
|
$"Tex: {FormatBytes(report.textureAtlasSize)} | " +
|
||||||
|
$"CLUT: {FormatBytes(report.clutSize)} | " +
|
||||||
|
$"Free: {FormatBytes(report.VramFree)}");
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
|
// SPU RAM bar
|
||||||
|
DrawMemoryBar("SPU RAM",
|
||||||
|
report.TotalSpuUsed, SceneMemoryReport.USABLE_SPU,
|
||||||
|
report.SpuPercent,
|
||||||
|
new Color(0.6f, 0.3f, 0.9f),
|
||||||
|
report.audioClipCount > 0
|
||||||
|
? $"{report.audioClipCount} clips | {FormatBytes(report.audioDataSize)} | Free: {FormatBytes(report.SpuFree)}"
|
||||||
|
: "No audio clips");
|
||||||
|
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
|
// CD Storage (no bar, just info)
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label("CD Storage:", EditorStyles.miniLabel, GUILayout.Width(70));
|
||||||
|
GUILayout.Label(
|
||||||
|
$"Scene: {FormatBytes(report.splashpackFileSize)}" +
|
||||||
|
(report.loaderPackSize > 0 ? $" | Loader: {FormatBytes(report.loaderPackSize)}" : "") +
|
||||||
|
$" | Total: {FormatBytes(report.TotalDiscSize)}",
|
||||||
|
EditorStyles.miniLabel);
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// Summary stats
|
||||||
|
PSXEditorStyles.DrawSeparator(4, 4);
|
||||||
|
EditorGUILayout.LabelField(
|
||||||
|
$"<b>{report.gameObjectCount}</b> objects | " +
|
||||||
|
$"<b>{report.triangleCount}</b> tris | " +
|
||||||
|
$"<b>{report.atlasCount}</b> atlases | " +
|
||||||
|
$"<b>{report.clutCount}</b> CLUTs",
|
||||||
|
PSXEditorStyles.RichLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMemoryBar(string label, long used, long total, float percent, Color barColor, string details)
|
||||||
|
{
|
||||||
|
// Label row
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label(label, EditorStyles.boldLabel, GUILayout.Width(70));
|
||||||
|
GUILayout.Label($"{FormatBytes(used)} / {FormatBytes(total)} ({percent:F1}%)", EditorStyles.miniLabel);
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// Progress bar
|
||||||
|
Rect barRect = GUILayoutUtility.GetRect(0, 16, GUILayout.ExpandWidth(true));
|
||||||
|
|
||||||
|
// Background
|
||||||
|
EditorGUI.DrawRect(barRect, new Color(0.15f, 0.15f, 0.17f));
|
||||||
|
|
||||||
|
// Fill
|
||||||
|
float fillFraction = Mathf.Clamp01((float)used / total);
|
||||||
|
Rect fillRect = new Rect(barRect.x, barRect.y, barRect.width * fillFraction, barRect.height);
|
||||||
|
|
||||||
|
// Color shifts toward red when over 80%
|
||||||
|
Color fillColor = barColor;
|
||||||
|
if (percent > 90f)
|
||||||
|
fillColor = Color.Lerp(PSXEditorStyles.Warning, PSXEditorStyles.Error, (percent - 90f) / 10f);
|
||||||
|
else if (percent > 80f)
|
||||||
|
fillColor = Color.Lerp(barColor, PSXEditorStyles.Warning, (percent - 80f) / 10f);
|
||||||
|
EditorGUI.DrawRect(fillRect, fillColor);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
DrawRectOutline(barRect, new Color(0.3f, 0.3f, 0.35f));
|
||||||
|
|
||||||
|
// Percent text overlay
|
||||||
|
var style = new GUIStyle(EditorStyles.miniLabel)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
normal = { textColor = Color.white }
|
||||||
|
};
|
||||||
|
GUI.Label(barRect, $"{percent:F1}%", style);
|
||||||
|
|
||||||
|
// Details row
|
||||||
|
GUILayout.Label(details, EditorStyles.miniLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawRectOutline(Rect rect, Color color)
|
||||||
|
{
|
||||||
|
EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, 1), color);
|
||||||
|
EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 1, rect.width, 1), color);
|
||||||
|
EditorGUI.DrawRect(new Rect(rect.x, rect.y, 1, rect.height), color);
|
||||||
|
EditorGUI.DrawRect(new Rect(rect.xMax - 1, rect.y, 1, rect.height), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatBytes(long bytes)
|
||||||
|
{
|
||||||
|
if (bytes < 0) return "N/A";
|
||||||
|
if (bytes < 1024) return $"{bytes} B";
|
||||||
|
if (bytes < 1024 * 1024) return $"{bytes / 1024f:F1} KB";
|
||||||
|
return $"{bytes / (1024f * 1024f):F2} MB";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
@@ -808,6 +956,7 @@ namespace SplashEdit.EditorCode
|
|||||||
{
|
{
|
||||||
SplashBuildPaths.EnsureDirectories();
|
SplashBuildPaths.EnsureDirectories();
|
||||||
_loaderPackCache = new Dictionary<string, string>();
|
_loaderPackCache = new Dictionary<string, string>();
|
||||||
|
_memoryReports.Clear();
|
||||||
|
|
||||||
// Save current scene
|
// Save current scene
|
||||||
string currentScenePath = SceneManager.GetActiveScene().path;
|
string currentScenePath = SceneManager.GetActiveScene().path;
|
||||||
@@ -841,15 +990,34 @@ namespace SplashEdit.EditorCode
|
|||||||
|
|
||||||
// Export to the build directory
|
// Export to the build directory
|
||||||
string outputPath = SplashBuildPaths.GetSceneSplashpackPath(i, scene.name);
|
string outputPath = SplashBuildPaths.GetSceneSplashpackPath(i, scene.name);
|
||||||
|
string loaderPath = null;
|
||||||
exporter.ExportToPath(outputPath);
|
exporter.ExportToPath(outputPath);
|
||||||
Log($"Exported '{scene.name}' → {Path.GetFileName(outputPath)}", LogType.Log);
|
Log($"Exported '{scene.name}' → {Path.GetFileName(outputPath)}", LogType.Log);
|
||||||
|
|
||||||
// Export loading screen if assigned
|
// Export loading screen if assigned
|
||||||
if (exporter.LoadingScreenPrefab != null)
|
if (exporter.LoadingScreenPrefab != null)
|
||||||
{
|
{
|
||||||
string loaderPath = SplashBuildPaths.GetSceneLoaderPackPath(i, scene.name);
|
loaderPath = SplashBuildPaths.GetSceneLoaderPackPath(i, scene.name);
|
||||||
ExportLoaderPack(exporter.LoadingScreenPrefab, loaderPath, i, scene.name);
|
ExportLoaderPack(exporter.LoadingScreenPrefab, loaderPath, i, scene.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate memory report for this scene
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var report = SceneMemoryAnalyzer.Analyze(
|
||||||
|
scene.name,
|
||||||
|
outputPath,
|
||||||
|
loaderPath,
|
||||||
|
exporter.LastExportAtlases,
|
||||||
|
exporter.LastExportAudioSizes,
|
||||||
|
exporter.LastExportFonts,
|
||||||
|
exporter.LastExportTriangleCount);
|
||||||
|
_memoryReports.Add(report);
|
||||||
|
}
|
||||||
|
catch (Exception reportEx)
|
||||||
|
{
|
||||||
|
Log($"Memory report for '{scene.name}' failed: {reportEx.Message}", LogType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1017,6 +1185,9 @@ namespace SplashEdit.EditorCode
|
|||||||
if (SplashSettings.Target == BuildTarget.ISO)
|
if (SplashSettings.Target == BuildTarget.ISO)
|
||||||
buildArg += " LOADER=cdrom";
|
buildArg += " LOADER=cdrom";
|
||||||
|
|
||||||
|
if (SplashSettings.MemoryOverlay)
|
||||||
|
buildArg += " MEMOVERLAY=1";
|
||||||
|
|
||||||
int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
|
int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
|
||||||
string cleanPrefix = SplashSettings.CleanBuild ? "make clean && " : "";
|
string cleanPrefix = SplashSettings.CleanBuild ? "make clean && " : "";
|
||||||
string makeCmd = $"{cleanPrefix}make all -j{jobCount} {buildArg}".Trim();
|
string makeCmd = $"{cleanPrefix}make all -j{jobCount} {buildArg}".Trim();
|
||||||
|
|||||||
@@ -124,6 +124,18 @@ namespace SplashEdit.EditorCode
|
|||||||
set => EditorPrefs.SetBool(Prefix + "CleanBuild", value);
|
set => EditorPrefs.SetBool(Prefix + "CleanBuild", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Memory Overlay ---
|
||||||
|
/// <summary>
|
||||||
|
/// When enabled, compiles the runtime with a heap/RAM usage progress bar
|
||||||
|
/// and text overlay at the top-right corner of the screen.
|
||||||
|
/// Passes MEMOVERLAY=1 to the native Makefile.
|
||||||
|
/// </summary>
|
||||||
|
public static bool MemoryOverlay
|
||||||
|
{
|
||||||
|
get => EditorPrefs.GetBool(Prefix + "MemoryOverlay", false);
|
||||||
|
set => EditorPrefs.SetBool(Prefix + "MemoryOverlay", value);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Export settings ---
|
// --- Export settings ---
|
||||||
public static float DefaultGTEScaling
|
public static float DefaultGTEScaling
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,11 +54,32 @@ namespace SplashEdit.RuntimeCode
|
|||||||
|
|
||||||
private PSXObjectExporter[] _exporters;
|
private PSXObjectExporter[] _exporters;
|
||||||
private TextureAtlas[] _atlases;
|
private TextureAtlas[] _atlases;
|
||||||
|
|
||||||
// Component arrays
|
// Component arrays
|
||||||
private PSXInteractable[] _interactables;
|
private PSXInteractable[] _interactables;
|
||||||
private PSXAudioClip[] _audioSources;
|
private PSXAudioClip[] _audioSources;
|
||||||
private PSXTriggerBox[] _triggerBoxes;
|
private PSXTriggerBox[] _triggerBoxes;
|
||||||
|
|
||||||
|
// ── Post-export data for memory analysis ──
|
||||||
|
/// <summary>Texture atlases from the last export (null before first export).</summary>
|
||||||
|
public TextureAtlas[] LastExportAtlases => _atlases;
|
||||||
|
/// <summary>Custom font data from the last export.</summary>
|
||||||
|
public PSXFontData[] LastExportFonts => _fonts;
|
||||||
|
/// <summary>Audio clip ADPCM sizes from the last export.</summary>
|
||||||
|
public long[] LastExportAudioSizes => _lastAudioSizes;
|
||||||
|
private long[] _lastAudioSizes;
|
||||||
|
/// <summary>Total triangle count from the last export.</summary>
|
||||||
|
public int LastExportTriangleCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_exporters == null) return 0;
|
||||||
|
int count = 0;
|
||||||
|
foreach (var exp in _exporters)
|
||||||
|
if (exp.Mesh != null) count += exp.Mesh.Triangles.Count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 3+4: World collision and nav regions
|
// Phase 3+4: World collision and nav regions
|
||||||
private PSXCollisionExporter _collisionExporter;
|
private PSXCollisionExporter _collisionExporter;
|
||||||
@@ -271,6 +292,18 @@ namespace SplashEdit.RuntimeCode
|
|||||||
audioExports = list.ToArray();
|
audioExports = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache audio sizes for memory analysis
|
||||||
|
if (audioExports != null)
|
||||||
|
{
|
||||||
|
_lastAudioSizes = new long[audioExports.Length];
|
||||||
|
for (int i = 0; i < audioExports.Length; i++)
|
||||||
|
_lastAudioSizes[i] = audioExports[i].adpcmData != null ? audioExports[i].adpcmData.Length : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_lastAudioSizes = null;
|
||||||
|
}
|
||||||
|
|
||||||
var scene = new PSXSceneWriter.SceneData
|
var scene = new PSXSceneWriter.SceneData
|
||||||
{
|
{
|
||||||
exporters = _exporters,
|
exporters = _exporters,
|
||||||
|
|||||||
255
Runtime/SceneMemoryAnalyzer.cs
Normal file
255
Runtime/SceneMemoryAnalyzer.cs
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
using System.Text;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace SplashEdit.RuntimeCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a SceneMemoryReport from scene export data.
|
||||||
|
/// All sizes match PSXSceneWriter's binary layout exactly.
|
||||||
|
/// </summary>
|
||||||
|
public static class SceneMemoryAnalyzer
|
||||||
|
{
|
||||||
|
// Per-triangle binary size in splashpack (matches PSXSceneWriter.Write mesh section)
|
||||||
|
// 3 vertices * 6 bytes + normal 6 bytes + 3 colors * 4 bytes + 3 UVs * 2 bytes + 2 pad + tpage 2 + clutXY 4 + pad 2
|
||||||
|
private const int BytesPerTriangle = 52;
|
||||||
|
|
||||||
|
// Per-GameObject entry in splashpack
|
||||||
|
// offset(4) + position(12) + rotation(36) + polyCount(2) + luaIdx(2) + flags(4) + components(8) + AABB(24) = 92
|
||||||
|
private const int BytesPerGameObject = 92;
|
||||||
|
|
||||||
|
// Per-collider entry
|
||||||
|
private const int BytesPerCollider = 32;
|
||||||
|
|
||||||
|
// Per-interactable entry
|
||||||
|
private const int BytesPerInteractable = 24;
|
||||||
|
|
||||||
|
// Atlas metadata entry
|
||||||
|
private const int BytesPerAtlasMetadata = 12;
|
||||||
|
|
||||||
|
// CLUT metadata entry
|
||||||
|
private const int BytesPerClutMetadata = 12;
|
||||||
|
|
||||||
|
// Audio clip metadata entry (offset + size + sampleRate + flags + nameOffset)
|
||||||
|
private const int BytesPerAudioMetadata = 16;
|
||||||
|
|
||||||
|
// Renderer ordering table size (from renderer.hh)
|
||||||
|
private const int OrderingTableSize = 2048 * 3;
|
||||||
|
|
||||||
|
// Renderer bump allocator size (from renderer.hh)
|
||||||
|
private const int BumpAllocatorSize = 8096 * 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyze scene data and produce a memory report.
|
||||||
|
/// Call this after PSXSceneWriter.Write to get accurate stats,
|
||||||
|
/// or before to get estimates.
|
||||||
|
/// </summary>
|
||||||
|
public static SceneMemoryReport Analyze(
|
||||||
|
in PSXSceneWriter.SceneData scene,
|
||||||
|
string sceneName,
|
||||||
|
int resolutionWidth,
|
||||||
|
int resolutionHeight,
|
||||||
|
bool dualBuffering,
|
||||||
|
long compiledExeBytes = 0)
|
||||||
|
{
|
||||||
|
var report = new SceneMemoryReport();
|
||||||
|
report.sceneName = sceneName;
|
||||||
|
|
||||||
|
// ---- Count CLUTs ----
|
||||||
|
int clutCount = 0;
|
||||||
|
foreach (var atlas in scene.atlases)
|
||||||
|
foreach (var tex in atlas.ContainedTextures)
|
||||||
|
if (tex.ColorPalette != null)
|
||||||
|
clutCount++;
|
||||||
|
|
||||||
|
// ---- Count Lua files ----
|
||||||
|
var luaFiles = new System.Collections.Generic.List<LuaFile>();
|
||||||
|
foreach (var exporter in scene.exporters)
|
||||||
|
if (exporter.LuaFile != null && !luaFiles.Contains(exporter.LuaFile))
|
||||||
|
luaFiles.Add(exporter.LuaFile);
|
||||||
|
if (scene.sceneLuaFile != null && !luaFiles.Contains(scene.sceneLuaFile))
|
||||||
|
luaFiles.Add(scene.sceneLuaFile);
|
||||||
|
|
||||||
|
// ---- Count colliders ----
|
||||||
|
int colliderCount = 0;
|
||||||
|
foreach (var e in scene.exporters)
|
||||||
|
if (e.CollisionType != PSXCollisionType.None)
|
||||||
|
colliderCount++;
|
||||||
|
|
||||||
|
// ---- Count triangles ----
|
||||||
|
int totalTriangles = 0;
|
||||||
|
foreach (var e in scene.exporters)
|
||||||
|
totalTriangles += e.Mesh.Triangles.Count;
|
||||||
|
|
||||||
|
// ---- Store counts ----
|
||||||
|
report.objectCount = scene.exporters.Length;
|
||||||
|
report.triangleCount = totalTriangles;
|
||||||
|
report.atlasCount = scene.atlases.Length;
|
||||||
|
report.clutCount = clutCount;
|
||||||
|
report.audioClipCount = scene.audioClips?.Length ?? 0;
|
||||||
|
report.navRegionCount = scene.navRegionBuilder?.RegionCount ?? 0;
|
||||||
|
report.roomCount = scene.roomBuilder?.RoomCount ?? 0;
|
||||||
|
report.portalCount = scene.roomBuilder?.PortalCount ?? 0;
|
||||||
|
|
||||||
|
// ---- Header (v11, ~96 bytes with all extensions) ----
|
||||||
|
report.headerBytes = 96; // approximate header size including all version extensions
|
||||||
|
|
||||||
|
// ---- Lua section ----
|
||||||
|
long luaBytes = 0;
|
||||||
|
foreach (var lua in luaFiles)
|
||||||
|
{
|
||||||
|
luaBytes += 8; // offset placeholder + size
|
||||||
|
luaBytes += Align4(Encoding.UTF8.GetByteCount(lua.LuaScript));
|
||||||
|
}
|
||||||
|
report.luaBytes = luaBytes;
|
||||||
|
|
||||||
|
// ---- GameObject section ----
|
||||||
|
report.gameObjectBytes = (long)scene.exporters.Length * BytesPerGameObject;
|
||||||
|
|
||||||
|
// ---- Collider section ----
|
||||||
|
report.colliderBytes = (long)colliderCount * BytesPerCollider;
|
||||||
|
|
||||||
|
// ---- BVH section ----
|
||||||
|
report.bvhBytes = EstimateBvhSize(scene.bvh);
|
||||||
|
|
||||||
|
// ---- Interactable section ----
|
||||||
|
report.interactableBytes = (long)scene.interactables.Length * BytesPerInteractable;
|
||||||
|
|
||||||
|
// ---- Collision soup ----
|
||||||
|
report.collisionBytes = EstimateCollisionSize(scene.collisionExporter);
|
||||||
|
|
||||||
|
// ---- Nav region data ----
|
||||||
|
report.navRegionBytes = EstimateNavRegionSize(scene.navRegionBuilder);
|
||||||
|
|
||||||
|
// ---- Room/portal data ----
|
||||||
|
report.roomPortalBytes = EstimateRoomPortalSize(scene.roomBuilder);
|
||||||
|
|
||||||
|
// ---- Atlas metadata ----
|
||||||
|
report.atlasMetadataBytes = (long)scene.atlases.Length * BytesPerAtlasMetadata;
|
||||||
|
|
||||||
|
// ---- CLUT metadata ----
|
||||||
|
report.clutMetadataBytes = (long)clutCount * BytesPerClutMetadata;
|
||||||
|
|
||||||
|
// ---- Mesh data ----
|
||||||
|
report.meshDataBytes = (long)totalTriangles * BytesPerTriangle;
|
||||||
|
|
||||||
|
// ---- Atlas pixel data ----
|
||||||
|
long atlasPixelBytes = 0;
|
||||||
|
foreach (var atlas in scene.atlases)
|
||||||
|
atlasPixelBytes += Align4((long)atlas.Width * TextureAtlas.Height * 2);
|
||||||
|
report.atlasPixelBytes = atlasPixelBytes;
|
||||||
|
|
||||||
|
// ---- CLUT data ----
|
||||||
|
long clutDataBytes = 0;
|
||||||
|
foreach (var atlas in scene.atlases)
|
||||||
|
foreach (var tex in atlas.ContainedTextures)
|
||||||
|
if (tex.ColorPalette != null)
|
||||||
|
clutDataBytes += Align4((long)tex.ColorPalette.Count * 2);
|
||||||
|
report.clutDataBytes = clutDataBytes;
|
||||||
|
|
||||||
|
// ---- Name table ----
|
||||||
|
long nameTableBytes = 0;
|
||||||
|
foreach (var e in scene.exporters)
|
||||||
|
{
|
||||||
|
string name = e.gameObject.name;
|
||||||
|
if (name.Length > 24) name = name.Substring(0, 24);
|
||||||
|
nameTableBytes += 1 + Encoding.UTF8.GetByteCount(name) + 1; // length byte + name + null
|
||||||
|
}
|
||||||
|
report.nameTableBytes = nameTableBytes;
|
||||||
|
|
||||||
|
// ---- Audio metadata + data ----
|
||||||
|
int audioClipCount = scene.audioClips?.Length ?? 0;
|
||||||
|
report.audioMetadataBytes = (long)audioClipCount * BytesPerAudioMetadata;
|
||||||
|
|
||||||
|
long audioDataBytes = 0;
|
||||||
|
long spuAudioBytes = 0;
|
||||||
|
if (scene.audioClips != null)
|
||||||
|
{
|
||||||
|
foreach (var clip in scene.audioClips)
|
||||||
|
{
|
||||||
|
if (clip.adpcmData != null)
|
||||||
|
{
|
||||||
|
audioDataBytes += Align4(clip.adpcmData.Length);
|
||||||
|
spuAudioBytes += clip.adpcmData.Length;
|
||||||
|
}
|
||||||
|
// clip name data
|
||||||
|
string name = clip.clipName ?? "";
|
||||||
|
audioDataBytes += name.Length + 1; // name + null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report.audioDataBytes = audioDataBytes;
|
||||||
|
report.spuAudioBytes = spuAudioBytes;
|
||||||
|
|
||||||
|
// ---- VRAM breakdown ----
|
||||||
|
int fbCount = dualBuffering ? 2 : 1;
|
||||||
|
report.framebufferVramBytes = (long)resolutionWidth * resolutionHeight * 2 * fbCount;
|
||||||
|
report.textureVramBytes = atlasPixelBytes; // same data, uploaded to VRAM
|
||||||
|
report.clutVramBytes = clutDataBytes;
|
||||||
|
|
||||||
|
// ---- Renderer overhead (double-buffered OTs + bump allocators) ----
|
||||||
|
long otBytes = 2L * OrderingTableSize * 4; // two OTs, each entry is a pointer (4 bytes)
|
||||||
|
long bumpBytes = 2L * BumpAllocatorSize;
|
||||||
|
report.rendererOverhead = otBytes + bumpBytes;
|
||||||
|
|
||||||
|
// ---- Executable size ----
|
||||||
|
report.executableBytes = compiledExeBytes > 0 ? compiledExeBytes : 150 * 1024; // estimate if unknown
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overload that reads the compiled .ps-exe file size if available.
|
||||||
|
/// </summary>
|
||||||
|
public static SceneMemoryReport Analyze(
|
||||||
|
in PSXSceneWriter.SceneData scene,
|
||||||
|
string sceneName,
|
||||||
|
int resolutionWidth,
|
||||||
|
int resolutionHeight,
|
||||||
|
bool dualBuffering,
|
||||||
|
string compiledExePath)
|
||||||
|
{
|
||||||
|
long exeBytes = 0;
|
||||||
|
if (!string.IsNullOrEmpty(compiledExePath) && System.IO.File.Exists(compiledExePath))
|
||||||
|
exeBytes = new System.IO.FileInfo(compiledExePath).Length;
|
||||||
|
return Analyze(in scene, sceneName, resolutionWidth, resolutionHeight, dualBuffering, exeBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long Align4(long size)
|
||||||
|
{
|
||||||
|
return (size + 3) & ~3L;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateBvhSize(BVH bvh)
|
||||||
|
{
|
||||||
|
if (bvh == null) return 0;
|
||||||
|
// BVH nodes: each node has AABB (24 bytes) + child/tri info (8 bytes) = 32 bytes
|
||||||
|
// Triangle refs: 2 bytes each (uint16)
|
||||||
|
return (long)bvh.NodeCount * 32 + (long)bvh.TriangleRefCount * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateCollisionSize(PSXCollisionExporter collision)
|
||||||
|
{
|
||||||
|
if (collision == null || collision.MeshCount == 0) return 0;
|
||||||
|
// Each collision mesh header: AABB + triangle count (28 bytes)
|
||||||
|
// Each collision triangle: 3 vertices * 6 bytes = 18 bytes + normal 6 bytes = 24 bytes
|
||||||
|
return (long)collision.MeshCount * 28 + (long)collision.TriangleCount * 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateNavRegionSize(PSXNavRegionBuilder nav)
|
||||||
|
{
|
||||||
|
if (nav == null || nav.RegionCount == 0) return 0;
|
||||||
|
// Region: 84 bytes each (header + vertex data)
|
||||||
|
// Portal: 20 bytes each
|
||||||
|
return (long)nav.RegionCount * 84 + (long)nav.PortalCount * 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateRoomPortalSize(PSXRoomBuilder rooms)
|
||||||
|
{
|
||||||
|
if (rooms == null || rooms.RoomCount == 0) return 0;
|
||||||
|
// Room data: AABB (24 bytes) + tri ref range (4 bytes) + portal range (4 bytes) = 32 bytes per room
|
||||||
|
// Portal data: 40 bytes each (with right/up vectors)
|
||||||
|
// Tri refs: 4 bytes each (uint32)
|
||||||
|
int roomCount = rooms.RoomCount + 1; // +1 for catch-all room
|
||||||
|
return (long)roomCount * 32 + (long)rooms.PortalCount * 40 + (long)rooms.TotalTriRefCount * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/SceneMemoryAnalyzer.cs.meta
Normal file
2
Runtime/SceneMemoryAnalyzer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: edf19eb00f51d7a4dad3f01c6b019432
|
||||||
161
Runtime/SceneMemoryReport.cs
Normal file
161
Runtime/SceneMemoryReport.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace SplashEdit.RuntimeCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Memory usage breakdown for a single exported scene.
|
||||||
|
/// Covers Main RAM, VRAM, SPU RAM, and CD storage.
|
||||||
|
/// </summary>
|
||||||
|
public class SceneMemoryReport
|
||||||
|
{
|
||||||
|
// ---- Capacities (PS1 hardware limits) ----
|
||||||
|
public const long MainRamCapacity = 2 * 1024 * 1024; // 2 MB
|
||||||
|
public const long VramCapacity = 1024 * 512 * 2; // 1 MB (1024x512 @ 16bpp)
|
||||||
|
public const long SpuRamCapacity = 512 * 1024; // 512 KB
|
||||||
|
public const long CdRomCapacity = 650L * 1024 * 1024; // 650 MB (standard CD)
|
||||||
|
|
||||||
|
// ---- Scene name ----
|
||||||
|
public string sceneName;
|
||||||
|
|
||||||
|
// ---- Splashpack section sizes (bytes) ----
|
||||||
|
public long headerBytes;
|
||||||
|
public long luaBytes;
|
||||||
|
public long gameObjectBytes;
|
||||||
|
public long colliderBytes;
|
||||||
|
public long bvhBytes;
|
||||||
|
public long interactableBytes;
|
||||||
|
public long collisionBytes;
|
||||||
|
public long navRegionBytes;
|
||||||
|
public long roomPortalBytes;
|
||||||
|
public long atlasMetadataBytes;
|
||||||
|
public long clutMetadataBytes;
|
||||||
|
public long meshDataBytes;
|
||||||
|
public long atlasPixelBytes;
|
||||||
|
public long clutDataBytes;
|
||||||
|
public long nameTableBytes;
|
||||||
|
public long audioMetadataBytes;
|
||||||
|
public long audioDataBytes;
|
||||||
|
|
||||||
|
// ---- Counts ----
|
||||||
|
public int objectCount;
|
||||||
|
public int triangleCount;
|
||||||
|
public int atlasCount;
|
||||||
|
public int clutCount;
|
||||||
|
public int audioClipCount;
|
||||||
|
public int navRegionCount;
|
||||||
|
public int roomCount;
|
||||||
|
public int portalCount;
|
||||||
|
|
||||||
|
// ---- Main RAM estimate ----
|
||||||
|
// Renderer double-buffered overhead (ordering tables + bump allocators)
|
||||||
|
public long rendererOverhead;
|
||||||
|
// PSYQo + psxsplash executable code (estimated from .ps-exe if available)
|
||||||
|
public long executableBytes;
|
||||||
|
|
||||||
|
// ---- VRAM breakdown ----
|
||||||
|
public long framebufferVramBytes;
|
||||||
|
public long textureVramBytes;
|
||||||
|
public long clutVramBytes;
|
||||||
|
|
||||||
|
// ---- SPU RAM ----
|
||||||
|
public const long SpuReservedBytes = 0x1010; // capture buffers + PSYQo dummy sample
|
||||||
|
public long spuAudioBytes;
|
||||||
|
|
||||||
|
// ---- Computed properties ----
|
||||||
|
|
||||||
|
public long TotalSplashpackBytes =>
|
||||||
|
headerBytes + luaBytes + gameObjectBytes + colliderBytes +
|
||||||
|
bvhBytes + interactableBytes + collisionBytes + navRegionBytes +
|
||||||
|
roomPortalBytes + atlasMetadataBytes + clutMetadataBytes +
|
||||||
|
meshDataBytes + atlasPixelBytes + clutDataBytes +
|
||||||
|
nameTableBytes + audioMetadataBytes + audioDataBytes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total Main RAM usage estimate. Splashpack data is loaded into heap at runtime.
|
||||||
|
/// </summary>
|
||||||
|
public long TotalMainRamBytes =>
|
||||||
|
executableBytes + TotalSplashpackBytes + rendererOverhead + 8192 /* stack */ + 16384 /* misc heap */;
|
||||||
|
|
||||||
|
public float MainRamPercent => (float)TotalMainRamBytes / MainRamCapacity;
|
||||||
|
public long MainRamFree => MainRamCapacity - TotalMainRamBytes;
|
||||||
|
|
||||||
|
public long TotalVramBytes => framebufferVramBytes + textureVramBytes + clutVramBytes;
|
||||||
|
public float VramPercent => (float)TotalVramBytes / VramCapacity;
|
||||||
|
public long VramFree => VramCapacity - TotalVramBytes;
|
||||||
|
|
||||||
|
public long TotalSpuBytes => SpuReservedBytes + spuAudioBytes;
|
||||||
|
public float SpuPercent => (float)TotalSpuBytes / SpuRamCapacity;
|
||||||
|
public long SpuFree => SpuRamCapacity - TotalSpuBytes;
|
||||||
|
|
||||||
|
// CD storage includes the splashpack file
|
||||||
|
public long CdSceneBytes => TotalSplashpackBytes;
|
||||||
|
|
||||||
|
// ---- Breakdown lists for UI ----
|
||||||
|
|
||||||
|
public struct BarSegment
|
||||||
|
{
|
||||||
|
public string label;
|
||||||
|
public long bytes;
|
||||||
|
public Color color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BarSegment> GetMainRamSegments()
|
||||||
|
{
|
||||||
|
var segments = new List<BarSegment>();
|
||||||
|
if (executableBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Executable", bytes = executableBytes, color = new Color(0.4f, 0.6f, 0.9f) });
|
||||||
|
if (meshDataBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Mesh Data", bytes = meshDataBytes, color = new Color(0.3f, 0.85f, 0.45f) });
|
||||||
|
if (atlasPixelBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Texture Data", bytes = atlasPixelBytes, color = new Color(0.95f, 0.75f, 0.2f) });
|
||||||
|
if (audioDataBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Audio Data", bytes = audioDataBytes, color = new Color(0.85f, 0.3f, 0.65f) });
|
||||||
|
|
||||||
|
long otherSplashpack = TotalSplashpackBytes - meshDataBytes - atlasPixelBytes - audioDataBytes;
|
||||||
|
if (otherSplashpack > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Scene Metadata", bytes = otherSplashpack, color = new Color(0.6f, 0.6f, 0.65f) });
|
||||||
|
|
||||||
|
if (rendererOverhead > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Renderer Buffers", bytes = rendererOverhead, color = new Color(0.3f, 0.85f, 0.95f) });
|
||||||
|
|
||||||
|
long misc = 8192 + 16384; // stack + misc heap
|
||||||
|
segments.Add(new BarSegment { label = "Stack + Misc", bytes = misc, color = new Color(0.45f, 0.45f, 0.5f) });
|
||||||
|
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BarSegment> GetVramSegments()
|
||||||
|
{
|
||||||
|
var segments = new List<BarSegment>();
|
||||||
|
if (framebufferVramBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Framebuffers", bytes = framebufferVramBytes, color = new Color(0.9f, 0.3f, 0.35f) });
|
||||||
|
if (textureVramBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Texture Atlases", bytes = textureVramBytes, color = new Color(0.95f, 0.75f, 0.2f) });
|
||||||
|
if (clutVramBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "CLUTs", bytes = clutVramBytes, color = new Color(0.85f, 0.5f, 0.2f) });
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BarSegment> GetSpuSegments()
|
||||||
|
{
|
||||||
|
var segments = new List<BarSegment>();
|
||||||
|
segments.Add(new BarSegment { label = "Reserved", bytes = SpuReservedBytes, color = new Color(0.45f, 0.45f, 0.5f) });
|
||||||
|
if (spuAudioBytes > 0)
|
||||||
|
segments.Add(new BarSegment { label = "Audio Clips", bytes = spuAudioBytes, color = new Color(0.85f, 0.3f, 0.65f) });
|
||||||
|
return segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a severity color for a usage percentage.
|
||||||
|
/// </summary>
|
||||||
|
public static Color GetUsageColor(float percent)
|
||||||
|
{
|
||||||
|
if (percent < 0.6f) return new Color(0.35f, 0.85f, 0.45f); // green
|
||||||
|
if (percent < 0.8f) return new Color(0.95f, 0.75f, 0.2f); // yellow
|
||||||
|
if (percent < 0.95f) return new Color(0.95f, 0.5f, 0.2f); // orange
|
||||||
|
return new Color(0.9f, 0.3f, 0.35f); // red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Runtime/SceneMemoryReport.cs.meta
Normal file
2
Runtime/SceneMemoryReport.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 09443b447544693488a25a9dfbf8714b
|
||||||
Reference in New Issue
Block a user