using System; using System.IO; using UnityEngine; namespace SplashEdit.EditorCode { /// /// Memory analysis report for a single exported scene. /// All values are in bytes unless noted otherwise. /// [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; /// RAM used by scene data (live portion of splashpack). public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize; /// Total estimated RAM: fixed overhead + scene data. Does NOT include code/BSS. 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; } /// /// Builds a SceneMemoryReport by reading the exported splashpack binary header /// and the scene's VRAM/audio data. /// public static class SceneMemoryAnalyzer { /// /// Analyze an exported scene. Call after ExportToPath(). /// /// Display name for the scene. /// Path to the exported .splashpack file. /// Path to the loading screen file (may be null). /// Texture atlases from the export pipeline. /// Array of ADPCM byte sizes per audio clip. /// Custom font descriptors. 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; } } } }