Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Workaround for Texture2D.GetData method

I’m converting a game from XNA to iOS with Monogame.
In the code snippet below, smallDeform is a Texture2D on which I call the GetData method.

smallDeform = Game.Content.Load<Texture2D>("Terrain/...");
smallDeform.GetData(smallDeformData, 0, smallDeform.Width * smallDeform.Height);

I’m having some problem with Monogame because the feature in iOS has not been implemented yet because it returns this exception.

#if IOS 
   throw new NotImplementedException();
#elif ANDROID

I tried to serialize the data in a XML file from Windows to load the whole file from iOS. The serialized files weight more than 100MB each, that is quite unacceptable to parse.

Basically, I’m looking for a workaround to get the data (for instance a uint[] or Color[]) from a texture without using the GetData method.

PS: I'm on Mac, so I can't use the Monogame SharpDX library.

Thanks in advance.

like image 903
pinckerman Avatar asked Nov 28 '13 21:11

pinckerman


3 Answers

Serializing the data is actually a pretty clever workaround but XML is far to heavy for the job. What I would do is use a custom made, but simple, binary format so that you can control the size of the output.

Here are a couple of methods I whipped up that should work, but please note that I made them specifically to answer this question and they have not been tested.

To save the data..

    private void SaveTextureData(Texture2D texture, string filename)
    {
        int width = texture.Width;
        int height = texture.Height;
        Color[] data = new Color[width * height];
        texture.GetData<Color>(data, 0, data.Length);

        using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create)))
        {
            writer.Write(width);
            writer.Write(height);
            writer.Write(data.Length);

            for (int i = 0; i < data.Length; i++)
            {
                writer.Write(data[i].R);
                writer.Write(data[i].G);
                writer.Write(data[i].B);
                writer.Write(data[i].A);
            }
        }
    }

And to load the data..

    private Texture2D LoadTextureData(string filename)
    {
        using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
        {                
            var width = reader.ReadInt32();
            var height = reader.ReadInt32();
            var length = reader.ReadInt32();
            var data = new Color[length];

            for (int i = 0; i < data.Length; i++)
            {
                var r = reader.ReadByte();
                var g = reader.ReadByte();
                var b = reader.ReadByte();
                var a = reader.ReadByte();
                data[i] = new Color(r, g, b, a);
            }

            var texture = new Texture2D(GraphicsDevice, width, height);
            texture.SetData<Color>(data, 0, data.Length);
            return texture;
        }
    }

Binary data is far smaller than XML data. It's about as small as you're going to get without compression. Just be aware that binary formats are pretty rigid when it comes to change. If you need to change the format it'll be easier in most cases to write out new files.

If you need to make changes to the methods, be careful to keep them in sync. Every Write should be matched with an identical Read in the exact same order and data type.

I'm interested to know how much smaller the files become. Let me know how it goes?

like image 59
craftworkgames Avatar answered Oct 21 '22 09:10

craftworkgames


I answer my own question, if someone will have the same problem.

Following craftworkgame's suggestion, I saved my data using byte streams, but due to the absence of File class, I had to use WinRT functions:

private async void SaveColorArray(string filename, Color[] array)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.ReplaceExisting);
    IRandomAccessStream writeStream = await sampleFile.OpenAsync(FileAccessMode.ReadWrite);
    IOutputStream outputSteam = writeStream.GetOutputStreamAt(0);
    DataWriter dataWriter = new DataWriter(outputSteam);
    for (int i = 0; i < array.Length; i++)
    {
        dataWriter.WriteByte(array[i].R);
        dataWriter.WriteByte(array[i].G);
        dataWriter.WriteByte(array[i].B);
        dataWriter.WriteByte(array[i].A);
    }

    await dataWriter.StoreAsync();
    await outputSteam.FlushAsync();
}

protected async Task<Color[]> LoadColorArray(string filename)
{
    StorageFile sampleFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename + ".dat", CreationCollisionOption.OpenIfExists);
    IRandomAccessStream readStream = await sampleFile.OpenAsync(FileAccessMode.Read);
    IInputStream inputSteam = readStream.GetInputStreamAt(0);
    DataReader dataReader = new DataReader(inputSteam);
    await dataReader.LoadAsync((uint)readStream.Size);

    Color[] levelArray = new Color[dataReader.UnconsumedBufferLength / 4];
    int i = 0;
    while (dataReader.UnconsumedBufferLength > 0)
    {
        byte[] colorArray = new byte[4];
        dataReader.ReadBytes(colorArray);
        levelArray[i++] = new Color(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
    }

    return levelArray;
}
like image 1
pinckerman Avatar answered Oct 21 '22 10:10

pinckerman


You can get Texture2D.GetData working on iOS if you're willing to build your own version of MonoGame. See the fix here: https://github.com/scottlerch/MonoGame/commit/24c1c3d5398f4ed9dbd9b60c0bbdbc096311632c).

It's from discussions about this same issue here: https://monogame.codeplex.com/discussions/442667

The fix has been confirmed to work on iPad1 v5.1.1+ and an iPhone4 v6.1+.

like image 1
Scott Lerch Avatar answered Oct 21 '22 10:10

Scott Lerch