Merge pull request #24 from ammaraskar/lifo

Add support for deserializing Scenegraph LIFO blocks.
This commit is contained in:
Nahuel Rocchetti 2023-02-15 15:41:48 -03:00 committed by GitHub
commit e07d862e6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 168 additions and 37 deletions

View file

@ -0,0 +1,9 @@
namespace OpenTS2.Content.DBPF.Scenegraph
{
public class ScenegraphMipLevelInfoAsset : AbstractAsset
{
public byte[] MipData { get; }
public ScenegraphMipLevelInfoAsset(byte[] mipData) => (MipData) = (mipData);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6b3b99c3961b425aa829124c461e827a
timeCreated: 1676344211

View file

@ -1,5 +1,7 @@
using System;
using System.Linq;
using OpenTS2.Common;
using OpenTS2.Files.Formats.DBPF;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using UnityEngine;
@ -16,8 +18,7 @@ namespace OpenTS2.Content.DBPF.Scenegraph
private Texture2D _texture;
// TODO: resolve LIFO references here.
// Also, maybe we should just use `ContentProvider.Get` and compute this eagerly instead of having a
// TODO: Maybe we should just use `ContentProvider.Get` and compute this eagerly instead of having a
// ContentProvider passed in here. That way we could also drop having to store the full ImageDataBlock
// and just have the more compact Texture2D.
public Texture2D GetSelectedImageAsUnityTexture(ContentProvider provider)
@ -28,44 +29,40 @@ namespace OpenTS2.Content.DBPF.Scenegraph
}
var subImage = ImageDataBlock.SubImages[ImageDataBlock.SelectedImage];
_texture = SubImageToTexture(ImageDataBlock.ColorFormat, ImageDataBlock.Width, ImageDataBlock.Height, subImage);
_texture = SubImageToTexture(provider, ImageDataBlock.ColorFormat, ImageDataBlock.Width, ImageDataBlock.Height, subImage);
return _texture;
}
/// <summary>
/// Compute the full Texture2D with mipmaps using the SubImage data.
/// </summary>
public static Texture2D SubImageToTexture(ScenegraphTextureFormat colorFormat, int width, int height, SubImage subImage)
public static Texture2D SubImageToTexture(ContentProvider provider, ScenegraphTextureFormat colorFormat, int width, int height, SubImage subImage)
{
// Iterate backwards through the MipMap looking for the first non-LIFO mip.
var highestReadableMipLevel = 0;
for (int i = subImage.MipMap.Length - 1; i >= 0; i--)
{
if (subImage.MipMap[i] is LifoReferenceMip)
{
continue;
}
highestReadableMipLevel++;
}
// Iterate up to the first non-LIFO mip.
for (int i = 0; i < (subImage.MipMap.Length - highestReadableMipLevel); i++)
{
width /= 2;
height /= 2;
}
var format = ScenegraphTextureFormatToUnity(colorFormat);
var texture = new Texture2D(width, height, format, mipChain: true);
var currentMipLevel = 0;
for (int i = highestReadableMipLevel - 1; i >= 0; i--)
for (int i = subImage.MipMap.Length - 1; i >= 0; i--)
{
Debug.Assert(subImage.MipMap[i] is ByteArrayMip);
var mip = subImage.MipMap[i] as ByteArrayMip;
var mip = subImage.MipMap[i];
byte[] mipData;
switch (mip)
{
case LifoReferenceMip lifoReferenceMip:
{
var lifoAsset = provider.GetAsset<ScenegraphMipLevelInfoAsset>(
new ResourceKey(lifoReferenceMip.LifoName, 0x1C0532FA, TypeIDs.SCENEGRAPH_LIFO));
mipData = lifoAsset.MipData;
break;
}
case ByteArrayMip byteArrayMip:
mipData = byteArrayMip.Data;
break;
default:
throw new ArgumentException($"SubImage has invalid mip type: {mip}");
}
var pixelData = ConvertPixelDataForUnity(colorFormat, mip.Data, width, height);
var pixelData = ConvertPixelDataForUnity(colorFormat, mipData, width, height);
texture.SetPixelData(pixelData, currentMipLevel);
currentMipLevel++;

View file

@ -653,6 +653,10 @@ namespace OpenTS2.Files.Formats.DBPF
return Changes.ChangedEntries[entry.TGI].Change.Data.GetAsset();
var item = GetBytes(entry, ignoreChanges);
var codec = Codecs.Get(entry.GlobalTGI.TypeID);
if (codec == null)
{
throw new ArgumentException($"No codec to handle type {entry.GlobalTGI.TypeID}");
}
var asset = codec.Deserialize(item, entry.GlobalTGI, this);
asset.Compressed = InternalGetUncompressedSize(entry) > 0;
asset.TGI = entry.TGI;

View file

@ -19,6 +19,7 @@ namespace OpenTS2.Files.Formats.DBPF
/// Texture Image in an RCOL wrapper.
/// </summary>
public const uint SCENEGRAPH_TXTR = Scenegraph.Block.ImageDataBlock.TYPE_ID;
public const uint SCENEGRAPH_LIFO = Scenegraph.Block.MipLevelInfoBlock.TYPE_ID;
public const uint STR = 0x53545223;
public const uint IMG = 0x856DDBAC;

View file

@ -0,0 +1,43 @@
using OpenTS2.Files.Utils;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Block
{
/// <summary>
/// A Scenegraph block representing image data for a single mip level.
/// </summary>
public class MipLevelInfoBlock : ScenegraphDataBlock
{
public const uint TYPE_ID = 0xED534136;
public const string BLOCK_NAME = "cLevelInfo";
public override string BlockName => BLOCK_NAME;
public int Width { get; }
public int Height { get; }
/// <summary>
/// Number of bytes in a single row of the image.
/// </summary>
public uint Pitch { get; }
public byte[] Data { get; }
public MipLevelInfoBlock(PersistTypeInfo blockTypeInfo, int width, int height, uint pitch, byte[] data) : base(
blockTypeInfo) =>
(Width, Height, Pitch, Data) = (width, height, pitch, data);
}
public class MipLevelInfoBlockReader : IScenegraphDataBlockReader<MipLevelInfoBlock>
{
public MipLevelInfoBlock Deserialize(IoBuffer reader, PersistTypeInfo blockTypeInfo)
{
var resource = ScenegraphResource.Deserialize(reader);
var width = reader.ReadInt32();
var height = reader.ReadInt32();
var pitch = reader.ReadUInt32();
var data = reader.ReadBytes(reader.ReadUInt32());
return new MipLevelInfoBlock(blockTypeInfo, width, height, pitch, data);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6a5ed1bd5cc744e7ac549e9937bd5f2b
timeCreated: 1676137777

View file

@ -0,0 +1,22 @@
using System.IO;
using OpenTS2.Common;
using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using OpenTS2.Files.Utils;
namespace OpenTS2.Files.Formats.DBPF.Scenegraph.Codecs
{
[Codec(TypeIDs.SCENEGRAPH_LIFO)]
public class ScenegraphMipLevelInfoCodec : AbstractCodec
{
public override AbstractAsset Deserialize(byte[] bytes, ResourceKey tgi, DBPFFile sourceFile)
{
var stream = new MemoryStream(bytes);
var reader = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
var mip = ScenegraphResourceCollection.DeserializeSingletonScenegraphBlock<MipLevelInfoBlock>(reader);
return new ScenegraphMipLevelInfoAsset(mip.Data);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8d1e7d1ed77427a9b6a8ae42a6fa0b9
timeCreated: 1676345291

View file

@ -93,6 +93,7 @@ namespace OpenTS2.Files.Formats.DBPF.Scenegraph
{
{ TagExtensionBlock.TYPE_ID, new TagExtensionBlockReader() },
{ ImageDataBlock.TYPE_ID, new ImageDataBlockReader() },
{ MipLevelInfoBlock.TYPE_ID, new MipLevelInfoBlockReader() },
};
}
}

View file

@ -1,5 +1,6 @@
using System.IO;
using NUnit.Framework;
using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using UnityEngine;
@ -22,7 +23,8 @@ public class ScenegraphTextureAssetTest
var mip = new ImageMip[] { new ByteArrayMip(imageData) };
var subImage = new SubImage(mip, Color.white, 1.0f);
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ScenegraphTextureFormat.DXT3, 128, 128, subImage);
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ContentProvider.Get(), ScenegraphTextureFormat.DXT3,
128, 128, subImage);
var actualImage = new Texture2D(128, 128, TextureFormat.RGBA32, mipChain:false);
actualImage.LoadImage(File.ReadAllBytes("TestAssets/Scenegraph/brick-texture.png"));
@ -43,7 +45,8 @@ public class ScenegraphTextureAssetTest
var mip = new ImageMip[] { new ByteArrayMip(imageData) };
var subImage = new SubImage(mip, Color.white, 1.0f);
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ScenegraphTextureFormat.DXT3, 256, 128, subImage);
var toTexture = ScenegraphTextureAsset.SubImageToTexture(ContentProvider.Get(), ScenegraphTextureFormat.DXT3,
256, 128, subImage);
var actualImage = new Texture2D(128, 128, TextureFormat.RGBA32, mipChain:false);
actualImage.LoadImage(File.ReadAllBytes("TestAssets/Scenegraph/cc0-logo.png"));

View file

@ -4,6 +4,7 @@ using NUnit.Framework;
using OpenTS2.Common;
using OpenTS2.Content;
using OpenTS2.Content.DBPF.Scenegraph;
using OpenTS2.Files.Formats.DBPF;
using OpenTS2.Files.Formats.DBPF.Scenegraph.Block;
using UnityEngine;
@ -17,29 +18,70 @@ public class ScenegraphTextureCodecTest
}
[Test]
public void TestLoadsTexture()
public void TestLoadsTexturesWithNoLifo()
{
var textureAsset =
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
0x1C4A276C));
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_no_lifo_txtr", 0x1C0532FA,
TypeIDs.SCENEGRAPH_TXTR));
var texture = textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get());
Assert.That(texture.width, Is.EqualTo(128));
Assert.That(texture.height, Is.EqualTo(128));
}
[Test]
public void TestLoadedImageBlockHasCorrectDetails()
public void TestLoadedImageBlockWithNoLifoHasCorrectDetails()
{
var textureAsset =
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
0x1C4A276C));
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_no_lifo_txtr", 0x1C0532FA,
TypeIDs.SCENEGRAPH_TXTR));
var imageBlock = textureAsset.ImageDataBlock;
Assert.That(imageBlock.BlockName, Is.EqualTo("cImageData"));
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
Assert.That(imageBlock.Width, Is.EqualTo(128));
Assert.That(imageBlock.Height, Is.EqualTo(128));
Assert.That(imageBlock.SubImages.Length, Is.EqualTo(1));
Assert.That(imageBlock.SelectedImage, Is.EqualTo(0));
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
Assert.That(imageBlock.SubImages[0].MipMap.Length, Is.EqualTo(8));
Assert.That(imageBlock.SubImages[0].MipMap[0], Is.InstanceOf<ByteArrayMip>());
Assert.That(imageBlock.SubImages[0].MipMap[7], Is.InstanceOf<ByteArrayMip>());
}
[Test]
public void TestLoadsTextureWithLifo()
{
var textureAsset =
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
TypeIDs.SCENEGRAPH_TXTR));
var texture = textureAsset.GetSelectedImageAsUnityTexture(ContentProvider.Get());
Assert.That(texture.width, Is.EqualTo(256));
Assert.That(texture.height, Is.EqualTo(256));
}
[Test]
public void TestLoadedImageBlockWithLifoHasCorrectDetails()
{
var textureAsset =
ContentProvider.Get().GetAsset<ScenegraphTextureAsset>(new ResourceKey("brick_dxt1_txtr", 0x1C0532FA,
TypeIDs.SCENEGRAPH_TXTR));
var imageBlock = textureAsset.ImageDataBlock;
Assert.That(imageBlock.BlockName, Is.EqualTo("cImageData"));
Assert.That(imageBlock.ColorFormat, Is.EqualTo(ScenegraphTextureFormat.DXT1));
Assert.That(imageBlock.Width, Is.EqualTo(256));
Assert.That(imageBlock.Height, Is.EqualTo(256));
Assert.That(imageBlock.SubImages.Length, Is.EqualTo(1));
Assert.That(imageBlock.SelectedImage, Is.EqualTo(0));
Assert.That(imageBlock.SubImages[0].MipMap.Length, Is.EqualTo(9));
// Check that the first mip just contains image data.
Assert.That(imageBlock.SubImages[0].MipMap[0], Is.InstanceOf<ByteArrayMip>());
// Check that the last mip is a LIFO reference.
Assert.That(imageBlock.SubImages[0].MipMap[8], Is.InstanceOf<LifoReferenceMip>());
}
}