Exporting is working and rendering correctly in pcsx-redux

This commit is contained in:
2025-03-14 00:15:35 +01:00
parent 1e480f6c15
commit 8a6679dff6
10 changed files with 241 additions and 55 deletions

View File

@@ -8,13 +8,6 @@ using UnityEngine.Rendering;
public class VRAMEditorWindow : EditorWindow public class VRAMEditorWindow : EditorWindow
{ {
class ProhibitedArea
{
public int X;
public int Y;
public int Width;
public int Height;
}
private const int VramWidth = 1024; private const int VramWidth = 1024;
private const int VramHeight = 512; private const int VramHeight = 512;
@@ -28,6 +21,9 @@ public class VRAMEditorWindow : EditorWindow
private Color bufferColor2 = new Color(0, 1, 0, 0.5f); private Color bufferColor2 = new Color(0, 1, 0, 0.5f);
private Color prohibitedColor = new Color(1, 0, 0, 0.3f); private Color prohibitedColor = new Color(1, 0, 0, 0.3f);
private static string _psxDataPath = "Assets/PSXData.asset";
private PSXData _psxData;
private static readonly Vector2[] resolutions = private static readonly Vector2[] resolutions =
{ {
new Vector2(256, 240), new Vector2(256, 480), new Vector2(256, 240), new Vector2(256, 480),
@@ -50,6 +46,8 @@ public class VRAMEditorWindow : EditorWindow
vramImage.SetPixelData(blackPixels, 0); vramImage.SetPixelData(blackPixels, 0);
vramImage.Apply(); vramImage.Apply();
blackPixels.Dispose(); blackPixels.Dispose();
LoadData();
} }
public static void PasteTexture(Texture2D baseTexture, Texture2D overlayTexture, int posX, int posY) public static void PasteTexture(Texture2D baseTexture, Texture2D overlayTexture, int posX, int posY)
@@ -73,7 +71,7 @@ public class VRAMEditorWindow : EditorWindow
for (int x = 0; x < overlayWidth; x++) for (int x = 0; x < overlayWidth; x++)
{ {
int baseX = posX + x; int baseX = posX + x;
int baseY = posY + y; int baseY = posY + y;
if (baseX >= 0 && baseX < baseWidth && baseY >= 0 && baseY < baseHeight) if (baseX >= 0 && baseX < baseWidth && baseY >= 0 && baseY < baseHeight)
{ {
int baseIndex = baseY * baseWidth + baseX; int baseIndex = baseY * baseWidth + baseX;
@@ -102,26 +100,21 @@ public class VRAMEditorWindow : EditorWindow
exp.CreatePSXTexture2D(); exp.CreatePSXTexture2D();
} }
List<Rect> dontPackAreas = new List<Rect>();
foreach (ProhibitedArea area in prohibitedAreas)
{
dontPackAreas.Add(new Rect(area.X, area.Y, area.Width, area.Height));
}
Rect buffer1 = new Rect(0, 0, selectedResolution.x, selectedResolution.y); Rect buffer1 = new Rect(0, 0, selectedResolution.x, selectedResolution.y);
Rect buffer2 = verticalLayout ? new Rect(0, 256, selectedResolution.x, selectedResolution.y) Rect buffer2 = verticalLayout ? new Rect(0, 256, selectedResolution.x, selectedResolution.y)
: new Rect(selectedResolution.x, 0, selectedResolution.x, selectedResolution.y); : new Rect(selectedResolution.x, 0, selectedResolution.x, selectedResolution.y);
dontPackAreas.Add(buffer1); List<Rect> framebuffers = new List<Rect> { buffer1 };
if (dualBuffering) if (dualBuffering)
{ {
dontPackAreas.Add(buffer2); framebuffers.Add(buffer2);
} }
VRAMPacker tp = new VRAMPacker(dontPackAreas); VRAMPacker tp = new VRAMPacker(framebuffers, prohibitedAreas);
var packed = tp.PackTexturesIntoVRAM(objects); var packed = tp.PackTexturesIntoVRAM(objects);
for (int y = 0; y < VramHeight; y++) for (int y = 0; y < VramHeight; y++)
{ {
for (int x = 0; x < VramWidth; x++) for (int x = 0; x < VramWidth; x++)
@@ -190,10 +183,10 @@ public class VRAMEditorWindow : EditorWindow
if (GUILayout.Button("Remove")) if (GUILayout.Button("Remove"))
{ {
prohibitedAreas.RemoveAt(i); prohibitedAreas.RemoveAt(i);
break; // Avoid out-of-bounds errors after removal break;
} }
prohibitedAreas[i] = area; // Update the list with edited values prohibitedAreas[i] = area;
GUILayout.Space(10); GUILayout.Space(10);
} }
@@ -204,7 +197,6 @@ public class VRAMEditorWindow : EditorWindow
prohibitedAreas.Add(new ProhibitedArea()); prohibitedAreas.Add(new ProhibitedArea());
} }
// New "Pack Textures" Button
if (GUILayout.Button("Pack Textures")) if (GUILayout.Button("Pack Textures"))
{ {
PackTextures(); PackTextures();
@@ -235,5 +227,38 @@ public class VRAMEditorWindow : EditorWindow
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
StoreData();
}
private void LoadData()
{
_psxData = AssetDatabase.LoadAssetAtPath<PSXData>(_psxDataPath);
if (!_psxData)
{
_psxData = CreateInstance<PSXData>();
AssetDatabase.CreateAsset(_psxData, _psxDataPath);
AssetDatabase.SaveAssets();
}
selectedResolution = _psxData.OutputResolution;
dualBuffering = _psxData.DualBuffering;
verticalLayout = _psxData.VerticalBuffering;
prohibitedAreas = _psxData.ProhibitedAreas;
}
private void StoreData()
{
if (_psxData != null)
{
_psxData.OutputResolution = selectedResolution;
_psxData.DualBuffering = dualBuffering;
_psxData.VerticalBuffering = verticalLayout;
_psxData.ProhibitedAreas = prohibitedAreas;
EditorUtility.SetDirty(_psxData);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
} }
} }

12
Runtime/PSXData.cs Normal file
View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using PSXSplash.RuntimeCode;
using UnityEngine;
[CreateAssetMenu(fileName = "PSXData", menuName = "Scriptable Objects/PSXData")]
public class PSXData : ScriptableObject
{
public Vector2 OutputResolution = new Vector2(320, 240);
public bool DualBuffering = true;
public bool VerticalBuffering = true;
public List<ProhibitedArea> ProhibitedAreas = new List<ProhibitedArea>();
}

2
Runtime/PSXData.cs.meta Normal file
View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cbd8c66199e036896848ce1569567dd6

View File

@@ -3,7 +3,6 @@ using UnityEngine;
namespace PSXSplash.RuntimeCode namespace PSXSplash.RuntimeCode
{ {
public struct PSXVertex public struct PSXVertex
{ {
public short vx, vy, vz; public short vx, vy, vz;
@@ -22,7 +21,7 @@ namespace PSXSplash.RuntimeCode
{ {
public List<Tri> Triangles; public List<Tri> Triangles;
public static PSXMesh CreateFromUnityMesh(Mesh mesh, int textureWidth, int textureHeight) public static PSXMesh CreateFromUnityMesh(Mesh mesh, int textureWidth = 256, int textureHeight = 256, Transform transform = null)
{ {
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() }; PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
@@ -36,11 +35,16 @@ namespace PSXSplash.RuntimeCode
int vid1 = indices[i + 1]; int vid1 = indices[i + 1];
int vid2 = indices[i + 2]; int vid2 = indices[i + 2];
PSXVertex v0 = ConvertToPSXVertex(vertices[vid0], uv[vid0], textureWidth, textureHeight); // Convert to world space only if a transform is provided
PSXVertex v1 = ConvertToPSXVertex(vertices[vid1], uv[vid1], textureWidth, textureHeight); Vector3 v0 = transform ? transform.TransformPoint(vertices[vid0]) : vertices[vid0];
PSXVertex v2 = ConvertToPSXVertex(vertices[vid2], uv[vid2], textureWidth, textureHeight); Vector3 v1 = transform ? transform.TransformPoint(vertices[vid1]) : vertices[vid1];
Vector3 v2 = transform ? transform.TransformPoint(vertices[vid2]) : vertices[vid2];
psxMesh.Triangles.Add(new Tri { v0 = v0, v1 = v1, v2 = v2 }); PSXVertex psxV0 = ConvertToPSXVertex(v0, uv[vid0], textureWidth, textureHeight);
PSXVertex psxV1 = ConvertToPSXVertex(v1, uv[vid1], textureWidth, textureHeight);
PSXVertex psxV2 = ConvertToPSXVertex(v2, uv[vid2], textureWidth, textureHeight);
psxMesh.Triangles.Add(new Tri { v0 = psxV0, v1 = psxV1, v2 = psxV2 });
} }
return psxMesh; return psxMesh;
@@ -51,12 +55,12 @@ namespace PSXSplash.RuntimeCode
PSXVertex psxVertex = new PSXVertex PSXVertex psxVertex = new PSXVertex
{ {
vx = (short)(Mathf.Clamp(vertex.x, -4f, 3.999f) * 4096), vx = (short)(Mathf.Clamp(vertex.x, -4f, 3.999f) * 4096),
vy = (short)(Mathf.Clamp(vertex.y, -4f, 3.999f) * 4096), vy = (short)(Mathf.Clamp(-vertex.y, -4f, 3.999f) * 4096),
vz = (short)(Mathf.Clamp(vertex.z, -4f, 3.999f) * 4096), vz = (short)(Mathf.Clamp(vertex.z, -4f, 3.999f) * 4096),
u = (byte)(Mathf.Clamp(uv.x * textureWidth, 0, 255)), u = (byte)(Mathf.Clamp((uv.x * (textureWidth-1)), 0, 255)),
v = (byte)(Mathf.Clamp((1.0f - uv.y) * textureHeight, 0, 255)) v = (byte)(Mathf.Clamp(((1.0f - uv.y) * (textureHeight-1)), 0, 255))
}; };
return psxVertex; return psxVertex;
} }
} }
} }

View File

@@ -5,6 +5,7 @@ namespace PSXSplash.RuntimeCode
public class PSXObjectExporter : MonoBehaviour public class PSXObjectExporter : MonoBehaviour
{ {
public PSXBPP BitDepth; public PSXBPP BitDepth;
public bool MeshIsStatic = true;
[HideInInspector] [HideInInspector]
public PSXTexture2D Texture; public PSXTexture2D Texture;
@@ -27,7 +28,12 @@ namespace PSXSplash.RuntimeCode
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>(); MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter != null) if (meshFilter != null)
{ {
Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.mesh, Texture.Width, Texture.Height); if(MeshIsStatic) {
Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.sharedMesh, Texture.Width, Texture.Height, transform);
}
else {
Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.sharedMesh, Texture.Width, Texture.Height);
}
} }
} }
} }

View File

@@ -1,34 +1,128 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Overlays;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
namespace PSXSplash.RuntimeCode namespace PSXSplash.RuntimeCode
{ {
[ExecuteInEditMode]
public class PSXSceneExporter : MonoBehaviour public class PSXSceneExporter : MonoBehaviour
{ {
private PSXObjectExporter[] _exporters;
private PSXData _psxData;
private readonly string _psxDataPath = "Assets/PSXData.asset";
private Vector2 selectedResolution;
private bool dualBuffering;
private bool verticalLayout;
private List<ProhibitedArea> prohibitedAreas;
private VRAMPixel[,] vramPixels;
public void Export() public void Export()
{ {
Debug.Log($"Exporting scene: {SceneManager.GetActiveScene().name}"); LoadData();
_exporters = FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
Scene activeScene = SceneManager.GetActiveScene(); foreach (PSXObjectExporter exp in _exporters)
foreach (GameObject obj in activeScene.GetRootGameObjects())
{ {
ExportAllPSXExporters(obj.transform); exp.CreatePSXTexture2D();
exp.CreatePSXMesh();
} }
PackTextures();
ExportFile();
} }
void ExportAllPSXExporters(Transform parentTransform) void PackTextures()
{ {
PSXObjectExporter exporter = parentTransform.GetComponent<PSXObjectExporter>();
if (exporter != null) Rect buffer1 = new Rect(0, 0, selectedResolution.x, selectedResolution.y);
Rect buffer2 = verticalLayout ? new Rect(0, 256, selectedResolution.x, selectedResolution.y)
: new Rect(selectedResolution.x, 0, selectedResolution.x, selectedResolution.y);
List<Rect> framebuffers = new List<Rect> { buffer1 };
if (dualBuffering)
{ {
//exporter.Export(); framebuffers.Add(buffer2);
} }
foreach (Transform child in parentTransform) VRAMPacker tp = new VRAMPacker(framebuffers, prohibitedAreas);
var packed = tp.PackTexturesIntoVRAM(_exporters);
_exporters = packed.processedObjects;
vramPixels = packed._vramPixels;
}
void ExportFile() {
string path = EditorUtility.SaveFilePanel("Select Output File", "", "output", "bin");
int totalFaces = 0;
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
{
// VramPixels are always 1MB
for (int y = 0; y < vramPixels.GetLength(1); y++)
{
for (int x = 0; x < vramPixels.GetLength(0); x++)
{
writer.Write(vramPixels[x, y].Pack());
}
}
writer.Write((ushort) _exporters.Length);
foreach(PSXObjectExporter exporter in _exporters) {
int expander = 16 / ((int) exporter.Texture.BitDepth);
totalFaces += exporter.Mesh.Triangles.Count;
writer.Write((ushort) exporter.Mesh.Triangles.Count);
writer.Write((byte) exporter.Texture.BitDepth);
writer.Write((byte)exporter.Texture.TexpageX);
writer.Write((byte)exporter.Texture.TexpageY);
writer.Write((ushort)exporter.Texture.ClutPackingX);
writer.Write((ushort)exporter.Texture.ClutPackingY);
writer.Write((byte) 0);
foreach(Tri tri in exporter.Mesh.Triangles) {
writer.Write((short)tri.v0.vx);
writer.Write((short)tri.v0.vy);
writer.Write((short)tri.v0.vz);
writer.Write((byte)(tri.v0.u + exporter.Texture.PackingX * expander));
writer.Write((byte)(tri.v0.v + exporter.Texture.PackingY));
writer.Write((short)tri.v1.vx);
writer.Write((short)tri.v1.vy);
writer.Write((short)tri.v1.vz);
writer.Write((byte)(tri.v1.u + exporter.Texture.PackingX * expander));
writer.Write((byte)(tri.v1.v + exporter.Texture.PackingY));
writer.Write((short)tri.v2.vx);
writer.Write((short)tri.v2.vy);
writer.Write((short)tri.v2.vz);
writer.Write((byte)(tri.v2.u + exporter.Texture.PackingX * expander));
writer.Write((byte)(tri.v2.v + exporter.Texture.PackingY));
}
}
}
Debug.Log(totalFaces);
}
public void LoadData()
{
_psxData = AssetDatabase.LoadAssetAtPath<PSXData>(_psxDataPath);
if (!_psxData)
{ {
ExportAllPSXExporters(child); _psxData = ScriptableObject.CreateInstance<PSXData>();
AssetDatabase.CreateAsset(_psxData, _psxDataPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
} }
selectedResolution = _psxData.OutputResolution;
dualBuffering = _psxData.DualBuffering;
verticalLayout = _psxData.VerticalBuffering;
prohibitedAreas = _psxData.ProhibitedAreas;
} }
void OnDrawGizmos() void OnDrawGizmos()

View File

@@ -102,14 +102,15 @@ namespace PSXSplash.RuntimeCode
public Texture2D OriginalTexture; public Texture2D OriginalTexture;
// Within supertexture // Within supertexture
public int PackingX; public byte PackingX;
public int PackingY; public byte PackingY;
public int TexpageNum; public byte TexpageX;
public byte TexpageY;
// Absolute positioning // Absolute positioning
public int ClutPackingX; public ushort ClutPackingX;
public int ClutPackingY; public ushort ClutPackingY;
private int _maxColors; private int _maxColors;
@@ -263,7 +264,6 @@ namespace PSXSplash.RuntimeCode
Texture2D vramTexture = new Texture2D(QuantizedWidth, Height); Texture2D vramTexture = new Texture2D(QuantizedWidth, Height);
List<Color> colors = new List<Color>();
for (int y = 0; y < Height; y++) for (int y = 0; y < Height; y++)
{ {
for (int x = 0; x < QuantizedWidth; x++) for (int x = 0; x < QuantizedWidth; x++)

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework;
using UnityEngine; using UnityEngine;
@@ -29,9 +30,16 @@ namespace PSXSplash.RuntimeCode
private VRAMPixel[,] _vramPixels; private VRAMPixel[,] _vramPixels;
public VRAMPacker(List<Rect> reservedAreas) public VRAMPacker(List<Rect> framebuffers, List<ProhibitedArea> reservedAreas)
{ {
_reservedAreas = reservedAreas; List<Rect> areasConvertedToRect = new List<Rect>();
foreach (ProhibitedArea area in reservedAreas)
{
areasConvertedToRect.Add(new Rect(area.X, area.Y, area.Width, area.Height));
}
_reservedAreas = areasConvertedToRect;
_reservedAreas.Add(framebuffers[0]);
_reservedAreas.Add(framebuffers[1]);
_vramPixels = new VRAMPixel[VRAM_WIDTH, VRAM_HEIGHT]; _vramPixels = new VRAMPixel[VRAM_WIDTH, VRAM_HEIGHT];
} }
@@ -77,15 +85,16 @@ namespace PSXSplash.RuntimeCode
ArrangeAtlasesInVRAM(); ArrangeAtlasesInVRAM();
AllocateCLUTs(); AllocateCLUTs();
BuildVram(); BuildVram();
return (objects, _vramPixels); return (objects, _vramPixels);
} }
private bool TryPlaceTextureInAtlas(TextureAtlas atlas, PSXTexture2D texture) private bool TryPlaceTextureInAtlas(TextureAtlas atlas, PSXTexture2D texture)
{ {
for (int y = 0; y <= TextureAtlas.Height - texture.Height; y++) for (byte y = 0; y <= TextureAtlas.Height - texture.Height; y++)
{ {
for (int x = 0; x <= atlas.Width - texture.QuantizedWidth; x++) for (byte x = 0; x <= atlas.Width - texture.QuantizedWidth; x++)
{ {
var candidateRect = new Rect(x, y, texture.QuantizedWidth, texture.Height); var candidateRect = new Rect(x, y, texture.QuantizedWidth, texture.Height);
if (!atlas.ContainedTextures.Any(tex => new Rect(tex.PackingX, tex.PackingY, tex.QuantizedWidth, tex.Height).Overlaps(candidateRect))) if (!atlas.ContainedTextures.Any(tex => new Rect(tex.PackingX, tex.PackingY, tex.QuantizedWidth, tex.Height).Overlaps(candidateRect)))
@@ -129,7 +138,11 @@ namespace PSXSplash.RuntimeCode
{ {
foreach (PSXTexture2D texture in atlas.ContainedTextures) foreach (PSXTexture2D texture in atlas.ContainedTextures)
{ {
texture.TexpageNum = CalculateTexpage(atlas.PositionX, atlas.PositionY); int colIndex = atlas.PositionX / 64;
int rowIndex = atlas.PositionY / 256;
texture.TexpageX = (byte)colIndex;
texture.TexpageY = (byte)rowIndex;
} }
break; break;
} }
@@ -153,9 +166,9 @@ namespace PSXSplash.RuntimeCode
int clutHeight = 1; int clutHeight = 1;
bool placed = false; bool placed = false;
for (int x = 0; x < VRAM_WIDTH; x+=16) for (ushort x = 0; x < VRAM_WIDTH; x += 16)
{ {
for (int y = 0; y <= VRAM_HEIGHT; y++) for (ushort y = 0; y <= VRAM_HEIGHT; y++)
{ {
var candidate = new Rect(x, y, clutWidth, clutHeight); var candidate = new Rect(x, y, clutWidth, clutHeight);
if (IsPlacementValid(candidate)) if (IsPlacementValid(candidate))

28
Runtime/Utils.cs Normal file
View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace PSXSplash.RuntimeCode
{
public class ProhibitedArea
{
public int X;
public int Y;
public int Width;
public int Height;
public static ProhibitedArea FromUnityRect(Rect rect)
{
return new ProhibitedArea
{
X = Mathf.RoundToInt(rect.x),
Y = Mathf.RoundToInt(rect.y),
Width = Mathf.RoundToInt(rect.width),
Height = Mathf.RoundToInt(rect.height)
};
}
public Rect ToUnityRect()
{
return new Rect(X, Y, Width, Height);
}
}
}

2
Runtime/Utils.cs.meta Normal file
View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8c9b0581c1e4eeb6296f4c162359043f