Lua bytecode builder, cdrom fixes
This commit is contained in:
@@ -43,9 +43,9 @@ namespace SplashEdit.EditorCode
|
|||||||
public const long SPU_RESERVED = 0x1010;
|
public const long SPU_RESERVED = 0x1010;
|
||||||
public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED;
|
public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED;
|
||||||
|
|
||||||
// Fixed runtime overhead from C++ (renderer.hh constants)
|
// Fixed runtime overhead from C++ (renderer.hh constants, now configurable)
|
||||||
public const long BUMP_ALLOC_TOTAL = 2L * 8096 * 24; // ~380KB
|
public static long BUMP_ALLOC_TOTAL => 2L * SplashSettings.BumpSize;
|
||||||
public const long OT_TOTAL = 2L * 16384 * 4; // ~128KB
|
public static long OT_TOTAL => 2L * SplashSettings.OtSize * 4;
|
||||||
public const long VIS_REFS = 4096 * 4; // 16KB
|
public const long VIS_REFS = 4096 * 4; // 16KB
|
||||||
public const long STACK_ESTIMATE = 32 * 1024; // 32KB
|
public const long STACK_ESTIMATE = 32 * 1024; // 32KB
|
||||||
public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate
|
public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate
|
||||||
@@ -53,6 +53,11 @@ namespace SplashEdit.EditorCode
|
|||||||
|
|
||||||
public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD;
|
public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD;
|
||||||
|
|
||||||
|
// Heap estimate and warnings
|
||||||
|
public long EstimatedHeapFree => USABLE_RAM - TotalRamUsage;
|
||||||
|
public bool IsHeapWarning => EstimatedHeapFree < 128 * 1024; // < 128KB free
|
||||||
|
public bool IsHeapCritical => EstimatedHeapFree < 64 * 1024; // < 64KB free
|
||||||
|
|
||||||
/// <summary>RAM used by scene data (live portion of splashpack).</summary>
|
/// <summary>RAM used by scene data (live portion of splashpack).</summary>
|
||||||
public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize;
|
public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize;
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,45 @@ namespace SplashEdit.EditorCode
|
|||||||
EnsureGitIgnore();
|
EnsureGitIgnore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ───── Lua bytecode compilation paths ─────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directory for Lua source files extracted during export.
|
||||||
|
/// </summary>
|
||||||
|
public static string LuaSrcDir =>
|
||||||
|
Path.Combine(BuildOutputDir, "lua_src");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Directory for compiled Lua bytecode files.
|
||||||
|
/// </summary>
|
||||||
|
public static string LuaCompiledDir =>
|
||||||
|
Path.Combine(BuildOutputDir, "lua_compiled");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manifest file listing input/output pairs for the PS1 Lua compiler.
|
||||||
|
/// </summary>
|
||||||
|
public static string LuaManifestPath =>
|
||||||
|
Path.Combine(LuaSrcDir, "manifest.txt");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sentinel file written by luac_psx when compilation is complete.
|
||||||
|
/// Contains "OK" on success or "ERROR" on failure.
|
||||||
|
/// </summary>
|
||||||
|
public static string LuaDoneSentinel =>
|
||||||
|
Path.Combine(LuaSrcDir, "__done__");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the luac_psx PS1 compiler executable (built from tools/luac_psx/).
|
||||||
|
/// </summary>
|
||||||
|
public static string LuacPsxExePath =>
|
||||||
|
Path.Combine(NativeSourceDir, "tools", "luac_psx", "luac_psx.ps-exe");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the luac_psx tools directory (for building the compiler).
|
||||||
|
/// </summary>
|
||||||
|
public static string LuacPsxDir =>
|
||||||
|
Path.Combine(NativeSourceDir, "tools", "luac_psx");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if PCSX-Redux is installed in the tools directory.
|
/// Checks if PCSX-Redux is installed in the tools directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ namespace SplashEdit.EditorCode
|
|||||||
// ───── Build State ─────
|
// ───── Build State ─────
|
||||||
private static bool _isBuilding;
|
private static bool _isBuilding;
|
||||||
private static bool _isRunning;
|
private static bool _isRunning;
|
||||||
|
private static bool _luaBytecodeCompiled;
|
||||||
private static Process _emulatorProcess;
|
private static Process _emulatorProcess;
|
||||||
|
|
||||||
// ───── Scene List ─────
|
// ───── Scene List ─────
|
||||||
@@ -669,6 +670,13 @@ namespace SplashEdit.EditorCode
|
|||||||
new GUIContent("FPS Overlay", "Show an FPS counter at top-left during gameplay"),
|
new GUIContent("FPS Overlay", "Show an FPS counter at top-left during gameplay"),
|
||||||
SplashSettings.FpsOverlay);
|
SplashSettings.FpsOverlay);
|
||||||
|
|
||||||
|
SplashSettings.OtSize = EditorGUILayout.IntField(
|
||||||
|
new GUIContent("OT Size", "Ordering table entries. Lower = less RAM, shallower Z-sorting."),
|
||||||
|
SplashSettings.OtSize);
|
||||||
|
SplashSettings.BumpSize = EditorGUILayout.IntField(
|
||||||
|
new GUIContent("Bump Alloc Size", "Per-frame primitive buffer (bytes). Lower = less RAM, fewer triangles per frame."),
|
||||||
|
SplashSettings.BumpSize);
|
||||||
|
|
||||||
// Serial port (only for Real Hardware)
|
// Serial port (only for Real Hardware)
|
||||||
if (SplashSettings.Target == BuildTarget.RealHardware)
|
if (SplashSettings.Target == BuildTarget.RealHardware)
|
||||||
{
|
{
|
||||||
@@ -789,14 +797,18 @@ namespace SplashEdit.EditorCode
|
|||||||
GUILayout.Label($"Scene: {report.sceneName}", PSXEditorStyles.SectionHeader);
|
GUILayout.Label($"Scene: {report.sceneName}", PSXEditorStyles.SectionHeader);
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
// Main RAM bar
|
// Main RAM bar — segmented: OT | Bump | Scene | Heap
|
||||||
DrawMemoryBar("Main RAM",
|
DrawSegmentedMemoryBar("Main RAM",
|
||||||
report.TotalRamUsage, SceneMemoryReport.USABLE_RAM,
|
SceneMemoryReport.USABLE_RAM,
|
||||||
report.RamPercent,
|
report.RamPercent,
|
||||||
new Color(0.3f, 0.6f, 1f),
|
new (string label, long size, Color color)[] {
|
||||||
$"Scene: {FormatBytes(report.SceneRamUsage)} | " +
|
("OT", SceneMemoryReport.OT_TOTAL, new Color(0.9f, 0.4f, 0.2f)),
|
||||||
$"Fixed: {FormatBytes(report.FixedOverhead)} | " +
|
("Bump", SceneMemoryReport.BUMP_ALLOC_TOTAL, new Color(0.8f, 0.6f, 0.1f)),
|
||||||
$"Free: {FormatBytes(report.RamFree)}");
|
("Scene", report.SceneRamUsage, new Color(0.3f, 0.6f, 1.0f)),
|
||||||
|
("Other", SceneMemoryReport.VIS_REFS + SceneMemoryReport.STACK_ESTIMATE + SceneMemoryReport.LUA_OVERHEAD,
|
||||||
|
new Color(0.5f, 0.5f, 0.5f)),
|
||||||
|
},
|
||||||
|
report.EstimatedHeapFree);
|
||||||
|
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
|
|
||||||
@@ -842,6 +854,15 @@ namespace SplashEdit.EditorCode
|
|||||||
$"<b>{report.clutCount}</b> CLUTs",
|
$"<b>{report.clutCount}</b> CLUTs",
|
||||||
PSXEditorStyles.RichLabel);
|
PSXEditorStyles.RichLabel);
|
||||||
|
|
||||||
|
if (report.IsHeapCritical)
|
||||||
|
EditorGUILayout.HelpBox(
|
||||||
|
$"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Lua scripts may OOM!",
|
||||||
|
MessageType.Error);
|
||||||
|
else if (report.IsHeapWarning)
|
||||||
|
EditorGUILayout.HelpBox(
|
||||||
|
$"Estimated heap: {report.EstimatedHeapFree / 1024}KB free. Consider reducing OT/Bump sizes.",
|
||||||
|
MessageType.Warning);
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
EditorGUILayout.Space(4);
|
EditorGUILayout.Space(4);
|
||||||
}
|
}
|
||||||
@@ -889,6 +910,62 @@ namespace SplashEdit.EditorCode
|
|||||||
GUILayout.Label(details, EditorStyles.miniLabel);
|
GUILayout.Label(details, EditorStyles.miniLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawSegmentedMemoryBar(
|
||||||
|
string label, long total, float percent,
|
||||||
|
(string label, long size, Color color)[] segments,
|
||||||
|
long heapFree)
|
||||||
|
{
|
||||||
|
long used = 0;
|
||||||
|
foreach (var seg in segments) used += seg.size;
|
||||||
|
|
||||||
|
// Label row
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label(label, EditorStyles.boldLabel, GUILayout.Width(70));
|
||||||
|
GUILayout.Label($"{FormatBytes(used)} / {FormatBytes(total)} ({percent:F1}%) | Heap free: {FormatBytes(heapFree)}",
|
||||||
|
EditorStyles.miniLabel);
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// Bar
|
||||||
|
Rect barRect = GUILayoutUtility.GetRect(0, 16, GUILayout.ExpandWidth(true));
|
||||||
|
EditorGUI.DrawRect(barRect, new Color(0.15f, 0.15f, 0.17f));
|
||||||
|
|
||||||
|
// Draw each segment
|
||||||
|
float x = barRect.x;
|
||||||
|
foreach (var seg in segments)
|
||||||
|
{
|
||||||
|
float w = barRect.width * Mathf.Clamp01((float)seg.size / total);
|
||||||
|
if (w > 0)
|
||||||
|
EditorGUI.DrawRect(new Rect(x, barRect.y, w, barRect.height), seg.color);
|
||||||
|
x += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border
|
||||||
|
DrawRectOutline(barRect, new Color(0.3f, 0.3f, 0.35f));
|
||||||
|
|
||||||
|
// Percent overlay
|
||||||
|
var style = new GUIStyle(EditorStyles.miniLabel)
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
normal = { textColor = Color.white }
|
||||||
|
};
|
||||||
|
GUI.Label(barRect, $"{percent:F1}%", style);
|
||||||
|
|
||||||
|
// Legend row
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
foreach (var seg in segments)
|
||||||
|
{
|
||||||
|
// Color swatch + label
|
||||||
|
Rect swatchRect = GUILayoutUtility.GetRect(10, 10, GUILayout.Width(10));
|
||||||
|
swatchRect.y += 2;
|
||||||
|
EditorGUI.DrawRect(swatchRect, seg.color);
|
||||||
|
GUILayout.Label($"{seg.label}: {FormatBytes(seg.size)}", EditorStyles.miniLabel);
|
||||||
|
GUILayout.Space(6);
|
||||||
|
}
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
private static void DrawRectOutline(Rect rect, Color color)
|
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.y, rect.width, 1), color);
|
||||||
@@ -930,6 +1007,7 @@ namespace SplashEdit.EditorCode
|
|||||||
}
|
}
|
||||||
|
|
||||||
_isBuilding = true;
|
_isBuilding = true;
|
||||||
|
_luaBytecodeCompiled = false;
|
||||||
|
|
||||||
var console = EditorWindow.GetWindow<PSXConsoleWindow>();
|
var console = EditorWindow.GetWindow<PSXConsoleWindow>();
|
||||||
console.titleContent = new GUIContent("PSX Console", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image);
|
console.titleContent = new GUIContent("PSX Console", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image);
|
||||||
@@ -946,7 +1024,32 @@ namespace SplashEdit.EditorCode
|
|||||||
}
|
}
|
||||||
Log("Toolchain OK.", LogType.Log);
|
Log("Toolchain OK.", LogType.Log);
|
||||||
|
|
||||||
|
// Collect Lua files from all scenes and compile to bytecode
|
||||||
|
Log("Collecting Lua scripts...", LogType.Log);
|
||||||
|
EditorUtility.DisplayProgressBar("SplashEdit", "Collecting Lua scripts...", 0.2f);
|
||||||
|
var luaFiles = CollectLuaSources();
|
||||||
|
if (luaFiles != null && luaFiles.Count > 0)
|
||||||
|
{
|
||||||
|
Log($"Found {luaFiles.Count} Lua script(s). Compiling...", LogType.Log);
|
||||||
|
EditorUtility.DisplayProgressBar("SplashEdit", "Compiling Lua scripts...", 0.3f);
|
||||||
|
var bytecodeMap = await CompileLuaAsync(luaFiles);
|
||||||
|
if (bytecodeMap == null)
|
||||||
|
{
|
||||||
|
Log("Lua compilation failed.", LogType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PSXSceneWriter.CompiledLuaBytecode = bytecodeMap;
|
||||||
|
_luaBytecodeCompiled = true;
|
||||||
|
Log($"Compiled {bytecodeMap.Count} Lua script(s) to bytecode.", LogType.Log);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log("No Lua scripts found.", LogType.Log);
|
||||||
|
PSXSceneWriter.CompiledLuaBytecode = null;
|
||||||
|
}
|
||||||
|
|
||||||
Log("Exporting scenes...", LogType.Log);
|
Log("Exporting scenes...", LogType.Log);
|
||||||
|
EditorUtility.DisplayProgressBar("SplashEdit", "Exporting scenes...", 0.4f);
|
||||||
if (!ExportAllScenes())
|
if (!ExportAllScenes())
|
||||||
{
|
{
|
||||||
Log("Export failed.", LogType.Error);
|
Log("Export failed.", LogType.Error);
|
||||||
@@ -954,6 +1057,9 @@ namespace SplashEdit.EditorCode
|
|||||||
}
|
}
|
||||||
Log($"Exported {_sceneList.Count} scene(s).", LogType.Log);
|
Log($"Exported {_sceneList.Count} scene(s).", LogType.Log);
|
||||||
|
|
||||||
|
// Clear bytecode cache after export
|
||||||
|
PSXSceneWriter.CompiledLuaBytecode = null;
|
||||||
|
|
||||||
Log("Compiling native code...", LogType.Log);
|
Log("Compiling native code...", LogType.Log);
|
||||||
EditorUtility.DisplayProgressBar("SplashEdit", "Compiling native code...", 0.6f);
|
EditorUtility.DisplayProgressBar("SplashEdit", "Compiling native code...", 0.6f);
|
||||||
if (!await CompileNativeAsync())
|
if (!await CompileNativeAsync())
|
||||||
@@ -1222,6 +1328,345 @@ namespace SplashEdit.EditorCode
|
|||||||
Log("Wrote scene manifest.", LogType.Log);
|
Log("Wrote scene manifest.", LogType.Log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ───── Lua bytecode compilation ─────
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scan all scenes in the scene list and collect unique Lua source files.
|
||||||
|
/// Writes each source to PSXBuild/lua_src/ for compilation.
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<LuaFile, string> CollectLuaSources()
|
||||||
|
{
|
||||||
|
SplashBuildPaths.EnsureDirectories();
|
||||||
|
string luaSrcDir = SplashBuildPaths.LuaSrcDir;
|
||||||
|
if (Directory.Exists(luaSrcDir))
|
||||||
|
Directory.Delete(luaSrcDir, true);
|
||||||
|
Directory.CreateDirectory(luaSrcDir);
|
||||||
|
|
||||||
|
string currentScenePath = SceneManager.GetActiveScene().path;
|
||||||
|
var luaFileMap = new Dictionary<LuaFile, string>(); // LuaFile -> filename (no extension)
|
||||||
|
|
||||||
|
for (int i = 0; i < _sceneList.Count; i++)
|
||||||
|
{
|
||||||
|
var scene = _sceneList[i];
|
||||||
|
if (scene.asset == null) continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EditorSceneManager.OpenScene(scene.path, OpenSceneMode.Single);
|
||||||
|
// Collect from object exporters
|
||||||
|
var objExporters = UnityEngine.Object.FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
|
||||||
|
foreach (var objExporter in objExporters)
|
||||||
|
{
|
||||||
|
if (objExporter.LuaFile != null && !luaFileMap.ContainsKey(objExporter.LuaFile))
|
||||||
|
{
|
||||||
|
string name = $"script_{luaFileMap.Count}";
|
||||||
|
luaFileMap[objExporter.LuaFile] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect scene-level Lua file
|
||||||
|
var sceneExporter = UnityEngine.Object.FindFirstObjectByType<PSXSceneExporter>();
|
||||||
|
if (sceneExporter != null && sceneExporter.SceneLuaFile != null &&
|
||||||
|
!luaFileMap.ContainsKey(sceneExporter.SceneLuaFile))
|
||||||
|
{
|
||||||
|
string name = $"script_{luaFileMap.Count}";
|
||||||
|
luaFileMap[sceneExporter.SceneLuaFile] = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect from trigger boxes
|
||||||
|
var triggerBoxes = UnityEngine.Object.FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None);
|
||||||
|
foreach (var tb in triggerBoxes)
|
||||||
|
{
|
||||||
|
if (tb.LuaFile != null && !luaFileMap.ContainsKey(tb.LuaFile))
|
||||||
|
{
|
||||||
|
string name = $"script_{luaFileMap.Count}";
|
||||||
|
luaFileMap[tb.LuaFile] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"Error scanning '{scene.name}' for Lua files: {ex.Message}", LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write source files to disk
|
||||||
|
foreach (var kvp in luaFileMap)
|
||||||
|
{
|
||||||
|
string srcPath = Path.Combine(luaSrcDir, kvp.Value + ".lua");
|
||||||
|
File.WriteAllText(srcPath, kvp.Key.LuaScript, new System.Text.UTF8Encoding(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore original scene
|
||||||
|
if (!string.IsNullOrEmpty(currentScenePath))
|
||||||
|
EditorSceneManager.OpenScene(currentScenePath, OpenSceneMode.Single);
|
||||||
|
|
||||||
|
return luaFileMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compile Lua source files to PS1 bytecode using luac_psx inside PCSX-Redux.
|
||||||
|
/// Returns a dictionary mapping LuaFile assets to their compiled bytecode,
|
||||||
|
/// or null on failure.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<Dictionary<string, byte[]>> CompileLuaAsync(Dictionary<LuaFile, string> luaFileMap)
|
||||||
|
{
|
||||||
|
string luaSrcDir = SplashBuildPaths.LuaSrcDir;
|
||||||
|
string nativeDir = SplashBuildPaths.NativeSourceDir;
|
||||||
|
|
||||||
|
// Build luac_psx if needed
|
||||||
|
string luacDir = SplashBuildPaths.LuacPsxDir;
|
||||||
|
string luacExe = SplashBuildPaths.LuacPsxExePath;
|
||||||
|
|
||||||
|
if (!File.Exists(luacExe))
|
||||||
|
{
|
||||||
|
Log("Building Lua compiler (luac_psx)...", LogType.Log);
|
||||||
|
if (!await BuildLuacPsxAsync(luacDir))
|
||||||
|
{
|
||||||
|
Log("Failed to build luac_psx.", LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate manifest.txt
|
||||||
|
string manifestPath = SplashBuildPaths.LuaManifestPath;
|
||||||
|
using (var sw = new StreamWriter(manifestPath, false, new System.Text.UTF8Encoding(false)))
|
||||||
|
{
|
||||||
|
foreach (var kvp in luaFileMap)
|
||||||
|
{
|
||||||
|
sw.WriteLine(kvp.Value + ".lua");
|
||||||
|
sw.WriteLine(kvp.Value + ".luac");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up previous sentinel
|
||||||
|
string sentinelPath = SplashBuildPaths.LuaDoneSentinel;
|
||||||
|
if (File.Exists(sentinelPath))
|
||||||
|
File.Delete(sentinelPath);
|
||||||
|
|
||||||
|
// Launch PCSX-Redux headless with luac_psx
|
||||||
|
string reduxBinary = SplashBuildPaths.PCSXReduxBinary;
|
||||||
|
if (!File.Exists(reduxBinary))
|
||||||
|
{
|
||||||
|
Log("PCSX-Redux not found. Install it via the Toolchain section.", LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string args = $"--no-ui --run --fastboot --pcdrv --stdout --pcdrvbase \"{luaSrcDir}\" --loadexe \"{luacExe}\"";
|
||||||
|
Log($"luac_psx: {luacExe}", LogType.Log);
|
||||||
|
Log($"pcdrvbase: {luaSrcDir}", LogType.Log);
|
||||||
|
Log($"manifest: {File.ReadAllText(manifestPath).Trim()}", LogType.Log);
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = reduxBinary,
|
||||||
|
Arguments = args,
|
||||||
|
WorkingDirectory = luaSrcDir,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
Process process = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||||
|
var stdoutBuf = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
var stderrBuf = new System.Text.StringBuilder();
|
||||||
|
process.OutputDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data != null)
|
||||||
|
{
|
||||||
|
stdoutBuf.AppendLine(e.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
process.ErrorDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data != null)
|
||||||
|
{
|
||||||
|
stderrBuf.AppendLine(e.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
// Poll for sentinel file (100ms interval, 30s timeout)
|
||||||
|
bool done = false;
|
||||||
|
int elapsed = 0;
|
||||||
|
const int timeoutMs = 30000;
|
||||||
|
const int pollMs = 100;
|
||||||
|
|
||||||
|
while (elapsed < timeoutMs)
|
||||||
|
{
|
||||||
|
await Task.Delay(pollMs);
|
||||||
|
elapsed += pollMs;
|
||||||
|
|
||||||
|
if (File.Exists(sentinelPath))
|
||||||
|
{
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if process died unexpectedly
|
||||||
|
if (process.HasExited)
|
||||||
|
{
|
||||||
|
Log("PCSX-Redux exited unexpectedly during Lua compilation.", LogType.Error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill emulator
|
||||||
|
if (!process.HasExited)
|
||||||
|
{
|
||||||
|
try { process.Kill(); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done)
|
||||||
|
{
|
||||||
|
Log("Lua compilation timed out (30s).", LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check sentinel content
|
||||||
|
// Give a tiny delay for file to flush on Windows
|
||||||
|
await Task.Delay(200);
|
||||||
|
string sentinelContent = File.ReadAllText(sentinelPath).Trim();
|
||||||
|
if (string.IsNullOrEmpty(sentinelContent))
|
||||||
|
sentinelContent = "(empty)";
|
||||||
|
if (sentinelContent != "OK")
|
||||||
|
{
|
||||||
|
// Sentinel contains error details from luac_psx
|
||||||
|
foreach (string errLine in sentinelContent.Split('\n'))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(errLine))
|
||||||
|
Log(errLine.TrimEnd(), LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump full stdout and stderr for diagnosis
|
||||||
|
string fullStdout = stdoutBuf.ToString().Trim();
|
||||||
|
string fullStderr = stderrBuf.ToString().Trim();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(fullStdout))
|
||||||
|
{
|
||||||
|
Log("--- PCSX-Redux stdout ---", LogType.Log);
|
||||||
|
foreach (string line in fullStdout.Split('\n'))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
LogToPanel(line.TrimEnd(), LogType.Log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log("No stdout captured from PCSX-Redux.", LogType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(fullStderr))
|
||||||
|
{
|
||||||
|
Log("--- PCSX-Redux stderr ---", LogType.Log);
|
||||||
|
foreach (string line in fullStderr.Split('\n'))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
LogToPanel(line.TrimEnd(), LogType.Log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read compiled bytecode files, keyed by source text
|
||||||
|
var result = new Dictionary<string, byte[]>();
|
||||||
|
foreach (var kvp in luaFileMap)
|
||||||
|
{
|
||||||
|
string luacPath = Path.Combine(luaSrcDir, kvp.Value + ".luac");
|
||||||
|
if (!File.Exists(luacPath))
|
||||||
|
{
|
||||||
|
Log($"Missing compiled bytecode: {kvp.Value}.luac", LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
result[kvp.Key.LuaScript] = File.ReadAllBytes(luacPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"Lua compilation error: {ex.Message}", LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (process != null)
|
||||||
|
{
|
||||||
|
if (!process.HasExited)
|
||||||
|
try { process.Kill(); } catch { }
|
||||||
|
process.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build the luac_psx PS1 compiler executable.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<bool> BuildLuacPsxAsync(string luacDir)
|
||||||
|
{
|
||||||
|
int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
|
||||||
|
string makeCmd = $"make -j{jobCount}";
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = Application.platform == RuntimePlatform.WindowsEditor ? "cmd.exe" : "/bin/bash",
|
||||||
|
Arguments = Application.platform == RuntimePlatform.WindowsEditor
|
||||||
|
? $"/c \"cd /d \"{luacDir}\" && {makeCmd}\""
|
||||||
|
: $"-c \"cd \\\"{luacDir}\\\" && {makeCmd}\"",
|
||||||
|
WorkingDirectory = luacDir,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
|
||||||
|
var stderrBuf = new System.Text.StringBuilder();
|
||||||
|
process.OutputDataReceived += (s, e) => { };
|
||||||
|
process.ErrorDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.Data != null) stderrBuf.AppendLine(e.Data);
|
||||||
|
};
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<int>();
|
||||||
|
process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode);
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
int exitCode = await tcs.Task;
|
||||||
|
process.Dispose();
|
||||||
|
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
foreach (string line in stderrBuf.ToString().Split('\n'))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
LogToPanel(line.Trim(), LogType.Error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"Failed to build luac_psx: {ex.Message}", LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ───── Step 3: Compile ─────
|
// ───── Step 3: Compile ─────
|
||||||
|
|
||||||
private async void CompileOnly()
|
private async void CompileOnly()
|
||||||
@@ -1266,9 +1711,20 @@ namespace SplashEdit.EditorCode
|
|||||||
if (SplashSettings.FpsOverlay)
|
if (SplashSettings.FpsOverlay)
|
||||||
buildArg += " FPSOVERLAY=1";
|
buildArg += " FPSOVERLAY=1";
|
||||||
|
|
||||||
|
buildArg += $" OT_SIZE={SplashSettings.OtSize} BUMP_SIZE={SplashSettings.BumpSize}";
|
||||||
|
|
||||||
|
// Use noparser Lua library when bytecode was pre-compiled
|
||||||
|
string noparserPrefix = "";
|
||||||
|
if (_luaBytecodeCompiled)
|
||||||
|
{
|
||||||
|
buildArg += " NOPARSER=1";
|
||||||
|
// Build liblua-noparser.a first
|
||||||
|
noparserPrefix = "make -C third_party/nugget/third_party/psxlua psx-noparser && ";
|
||||||
|
}
|
||||||
|
|
||||||
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}{noparserPrefix}make all -j{jobCount} {buildArg}".Trim();
|
||||||
Log($"Running: {makeCmd}", LogType.Log);
|
Log($"Running: {makeCmd}", LogType.Log);
|
||||||
|
|
||||||
var psi = new ProcessStartInfo
|
var psi = new ProcessStartInfo
|
||||||
|
|||||||
@@ -149,6 +149,19 @@ namespace SplashEdit.EditorCode
|
|||||||
set => EditorPrefs.SetBool(Prefix + "FpsOverlay", value);
|
set => EditorPrefs.SetBool(Prefix + "FpsOverlay", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Renderer sizes ---
|
||||||
|
public static int OtSize
|
||||||
|
{
|
||||||
|
get => EditorPrefs.GetInt(Prefix + "OtSize", 2048 * 4);
|
||||||
|
set => EditorPrefs.SetInt(Prefix + "OtSize", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int BumpSize
|
||||||
|
{
|
||||||
|
get => EditorPrefs.GetInt(Prefix + "BumpSize", 8096 * 16);
|
||||||
|
set => EditorPrefs.SetInt(Prefix + "BumpSize", value);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Export settings ---
|
// --- Export settings ---
|
||||||
public static float DefaultGTEScaling
|
public static float DefaultGTEScaling
|
||||||
{
|
{
|
||||||
@@ -188,7 +201,8 @@ namespace SplashEdit.EditorCode
|
|||||||
"PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate",
|
"PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate",
|
||||||
"ResWidth", "ResHeight", "DualBuffering", "VerticalLayout",
|
"ResWidth", "ResHeight", "DualBuffering", "VerticalLayout",
|
||||||
"GTEScaling", "AutoValidate",
|
"GTEScaling", "AutoValidate",
|
||||||
"LicenseFilePath", "ISOVolumeLabel"
|
"LicenseFilePath", "ISOVolumeLabel",
|
||||||
|
"OtSize", "BumpSize"
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (string key in keys)
|
foreach (string key in keys)
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ namespace SplashEdit.RuntimeCode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PSXSceneWriter
|
public static class PSXSceneWriter
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-compiled Lua bytecode, keyed by Lua source text.
|
||||||
|
/// When populated, Write() packs bytecode instead of source text.
|
||||||
|
/// Set by the build pipeline after running luac_psx, cleared after export.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<string, byte[]> CompiledLuaBytecode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All scene data needed to produce a .bin file.
|
/// All scene data needed to produce a .bin file.
|
||||||
/// Populated by PSXSceneExporter before calling <see cref="Write"/>.
|
/// Populated by PSXSceneExporter before calling <see cref="Write"/>.
|
||||||
@@ -239,7 +246,12 @@ namespace SplashEdit.RuntimeCode
|
|||||||
{
|
{
|
||||||
luaOffset.PlaceholderPositions.Add(writer.BaseStream.Position);
|
luaOffset.PlaceholderPositions.Add(writer.BaseStream.Position);
|
||||||
writer.Write((int)0); // placeholder
|
writer.Write((int)0); // placeholder
|
||||||
writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript));
|
|
||||||
|
// Use compiled bytecode length if available, otherwise source length
|
||||||
|
if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode))
|
||||||
|
writer.Write((uint)bytecode.Length);
|
||||||
|
else
|
||||||
|
writer.Write((uint)Encoding.UTF8.GetByteCount(luaFile.LuaScript));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────
|
||||||
@@ -429,12 +441,26 @@ namespace SplashEdit.RuntimeCode
|
|||||||
// Data sections
|
// Data sections
|
||||||
// ══════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════
|
||||||
|
|
||||||
// Lua data
|
// Lua data (bytecode if compiled, source text otherwise)
|
||||||
|
int luaIdx = 0;
|
||||||
|
log?.Invoke($"CompiledLuaBytecode: {(CompiledLuaBytecode != null ? CompiledLuaBytecode.Count + " entries" : "null")}", LogType.Log);
|
||||||
foreach (LuaFile luaFile in luaFiles)
|
foreach (LuaFile luaFile in luaFiles)
|
||||||
{
|
{
|
||||||
AlignToFourBytes(writer);
|
AlignToFourBytes(writer);
|
||||||
luaOffset.DataOffsets.Add(writer.BaseStream.Position);
|
luaOffset.DataOffsets.Add(writer.BaseStream.Position);
|
||||||
writer.Write(Encoding.UTF8.GetBytes(luaFile.LuaScript));
|
|
||||||
|
if (CompiledLuaBytecode != null && CompiledLuaBytecode.TryGetValue(luaFile.LuaScript, out byte[] bytecode))
|
||||||
|
{
|
||||||
|
log?.Invoke($" Lua [{luaIdx}]: using bytecode ({bytecode.Length} bytes)", LogType.Log);
|
||||||
|
writer.Write(bytecode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte[] srcBytes = Encoding.UTF8.GetBytes(luaFile.LuaScript);
|
||||||
|
log?.Invoke($" Lua [{luaIdx}]: FALLBACK to source ({srcBytes.Length} bytes, script hash={luaFile.LuaScript.GetHashCode()})", LogType.Warning);
|
||||||
|
writer.Write(srcBytes);
|
||||||
|
}
|
||||||
|
luaIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mesh data
|
// Mesh data
|
||||||
|
|||||||
Reference in New Issue
Block a user