This project is read-only.

Memory strategy for loading textures and Sprites for Windows Phone

Topics: Performance
Mar 31, 2013 at 3:51 PM
Edited Mar 31, 2013 at 4:24 PM
I recently upgraded to VS2012 and tried my in dev game out on the new WP8 emulators but was dismayed to find out the emulator now crashes and throws an out of memory exception during my sprite loading procedure (funnily, it still works in WP7 emulators and on my WP7).

Regardless of whether the problem is the emulator or not, I want to get a clear understanding of how I should be managing memory in the game.

My game consists of a character whom has 4 or more different animations. Each animation consists of up 7 frames. On top of that, the character has up to 8 stackable visualization modifications (eg eye type, nose type, hair type, clothes type). Pre memory issue, I preloaded all textures for each animation frame and customization and created animate action out of them. The game then plays animations using the customizations applied to that current character.

I re-looked at this implementation when I received the out of memory exceptions and have started playing with RenderTexture instead. Instead of pre loading all possible textures, it only loads textures needed for the current character, renders them onto a single texture, from which the animation is built. This means the animations use 1/8th of the sprites they were before. I thought this would solve my issue, but it hasn't.

Here's a snippet of my code:

var characterTexture = CCRenderTexture.Create((int)width, (int)height);

characterTexture.BeginWithClear(0, 0, 0, 0);
// stamp a body onto my texture
var bodySprite = MethodToCreateSpecificSprite();
bodySprite.Position = centerPoint;
bodySprite.Visit();
bodySprite.Cleanup();
bodySprite = null;
// repeat 8 times to stamp eyes, nose, mouth, clothes, etc...

characterTexture.End();

As you can see, I'm calling CleanUp and setting the sprite to null in the hope of releasing the memory, though I don't believe this is the right way, nor does it seem to work...

I also tried using SharedTextureCache to load textures before stamping my texture out with RenderTexture, and then clearing the SharedTextureCache after each frame is Rendered with:

CCTextureCache.SharedTextureCache.RemoveAllTextures();

But this didn't have an effect either. Any tips on what I'm not doing?
Mar 31, 2013 at 3:54 PM
Edited Mar 31, 2013 at 4:07 PM
I used VS to do a memory profile of the emulation causing the crash.

Both WP7.1 and WP8 emulators peak at about 150mb of usage. WP8 crashes and throws an out of memory exception.

Each customisation/frame is 15kb at the most. Lets say there are 8 layers of customisation = 120kb but I render then onto one texture which I would assume is only 15kb again.
Each animation is 8 frames at the most. That's 15kb for 1 texture, or 960kb for 8 textures of customisation.
There are 4 animation sets. That's 60Kb for 4 sets of 1 texture, or 3.75MB for 4 sets of 8 textures of customisation.

So even if its storing every layer, its 3.75MB.... no where near the 150mb breaking point my profiler seems to suggest :(

Image
WP7.1 Memory Profile

Image
WP8 Memory Profile
Mar 31, 2013 at 5:39 PM
At time index 7 in your graph I see a large number of GC events. are you creating any objects outside of the texture loading? Maybe you are creating a bunch of small temporary objects that are not being released.

make sure you are using CCSpriteBatchNode to render your sprites. If you don't then your game will slow down dramatically as you draw more and more sprites to the screen.

Texture2D instances are held by the ContentManager, both on XNA from Microsoft and XNA from MonoGame. The CCTextureCache is just a convenience bag class that duplicates the behavior of the ContentManager, for the most part. So clearing textures from there will have no effect on your memory usage. Underlying Texture2D instances will be disposed as they leave the memory context. On Android, for instance, this can happen when your game enters the background. In that case, the CCTextureCache is used to automatically reload the textures when the game returns to the foreground.

Also consider that one of your textures is corrupt and the out of memory error is caused by this corrupt texture. try the game with one layer at a time and see where it starts to fail.
Mar 31, 2013 at 6:52 PM
Edited Mar 31, 2013 at 10:02 PM
Well, I commented out AddChild for the sprites and stopped executing any actions. I also tried replacing my CCSprites with CCSpriteBatchNodes, but they stopped rendering and the memory profiler acted exactly the same as when using CCSprites.

I'll paste my code so you can see exactly what I am doing and where it could be going wrong. This is all I need to execute to run into trouble.

public class RenderTextureTestScene : CCLayer
public RenderTextureTestScene()
{
    var characterSpriteFactory = new CharacterSpriteFactory();

    _swingAnimate = characterSpriteFactory.CreateAnimateAction(AnimationName.Swing, ArmorType.LeatherArmor, BeardType.Full, HairColourType.Grey, BodyType.Normal, SkinType.PaleWhite, EyeType.Round, HairType.SideComb, MoustacheType.None, NoseType.Pointy, WeaponType.ShortSword);

    _thrustAnimate = characterSpriteFactory.CreateAnimateAction(AnimationName.Thrust, ArmorType.LeatherArmor, BeardType.Full, HairColourType.Grey, BodyType.Normal, SkinType.PaleWhite, EyeType.Round, HairType.SideComb, MoustacheType.None, NoseType.Pointy, WeaponType.ShortSword);

    _dodgeAnimate = characterSpriteFactory.CreateAnimateAction(AnimationName.Dodge, ArmorType.LeatherArmor, BeardType.Full, HairColourType.Grey, BodyType.Normal, SkinType.PaleWhite, EyeType.Round, HairType.SideComb, MoustacheType.None, NoseType.Pointy, WeaponType.ShortSword);

    _collapseAnimate = characterSpriteFactory.CreateAnimateAction(AnimationName.Collapse, ArmorType.LeatherArmor, BeardType.Full, HairColourType.Grey, BodyType.Normal, SkinType.PaleWhite, EyeType.Round, HairType.SideComb, MoustacheType.None, NoseType.Pointy, WeaponType.ShortSword);
}
}

public class CharacterSpriteFactory{
public CCAnimate CreateAnimateAction(AnimationName animationName, ArmorType armorType, BeardType beardType, HairColourType hairColourType, BodyType bodyType, SkinType skinType, EyeType eyeType, HairType hairType, MoustacheType moustacheType, NoseType noseType, WeaponType weaponType)
{
    var frameList = new List<CCSpriteFrame>();
    var frameIndexSequence = _animationSequenceDictionary[animationName];

    foreach (var frame in frameIndexSequence)
    {
        var texture =
                CreateCharacterTexture(animationName.ToString(), frame, armorType, beardType, hairColourType, bodyType,
                                       skinType, eyeType, hairType, moustacheType, noseType, weaponType);
        var sprite = CCSpriteFrame.Create(texture, new CCRect(0, 0, texture.ContentSize.Width, texture.ContentSize.Height));
        frameList.Add(sprite);
    }
    var animation = CCAnimation.Create(frameList, 0.1f);
    var animate = CCAnimate.Create(animation);

    return animate;
}

private CCTexture2D CreateCharacterTexture(string animationName, int animationIndex, ArmorType armorType, BeardType beardType, HairColourType hairColourType, BodyType bodyType, SkinType skinType, EyeType eyeType, HairType hairType, MoustacheType moustacheType, NoseType noseType, WeaponType weaponType)
{
    const int width = 490;
    const int height = 278;

    var centerPoint = new CCPoint(width / 2, height / 2);

    var characterTexture = CCRenderTexture.Create(width, height);

    characterTexture.BeginWithClear(0, 0, 0, 0);

    var bodySprite = CreateCustomisationSprite(animationName, "Body", bodyType.ToString(), animationIndex, _skinColorDictionary[skinType]);
    bodySprite.Position = centerPoint;
    bodySprite.Visit();
    bodySprite.Cleanup();
    bodySprite.Texture.Dispose();
    bodySprite = null;

    if (armorType != ArmorType.None)
    {
        var armorSprite = CreateCustomisationSprite(animationName, "Armor", armorType.ToString(), animationIndex, null);
        armorSprite.Position = centerPoint;
        armorSprite.Visit();
        armorSprite.Cleanup();
        armorSprite.Texture.Dispose();
        armorSprite = null;
    }

    var eyesSprite = CreateCustomisationSprite(animationName, "Eyes", eyeType.ToString(), animationIndex, null);
    eyesSprite.Position = centerPoint;
    eyesSprite.Visit();
    eyesSprite.Cleanup();
    eyesSprite.Texture.Dispose();
    eyesSprite = null;

    var noseSprite = CreateCustomisationSprite(animationName, "Nose", noseType.ToString(), animationIndex, _skinColorDictionary[skinType]);
    noseSprite.Position = centerPoint;
    noseSprite.Visit();
    noseSprite.Cleanup();
    noseSprite.Texture.Dispose();
    noseSprite = null;

    if (hairType != HairType.None)
    {
        var hairSprite = CreateCustomisationSprite(animationName, "Hair", hairType.ToString(), animationIndex, _hairColorDictionary[hairColourType]);
        hairSprite.Position = centerPoint;
        hairSprite.Visit();
        hairSprite.Cleanup();
        hairSprite.Texture.Dispose();
        hairSprite = null;
    }

    if (moustacheType != MoustacheType.None)
    {
        var moustacheSprite = CreateCustomisationSprite(animationName, "Moustache", moustacheType.ToString(), animationIndex, _hairColorDictionary[hairColourType]);
        moustacheSprite.Position = centerPoint;
        moustacheSprite.Visit();
        moustacheSprite.Cleanup();
        moustacheSprite.Texture.Dispose();
        moustacheSprite = null;
    }

    if (beardType != BeardType.None)
    {
        var beardSprite = CreateCustomisationSprite(animationName, "Beard", beardType.ToString(), animationIndex, _hairColorDictionary[hairColourType]);
        beardSprite.Position = centerPoint;
        beardSprite.Visit();
        beardSprite.Cleanup();
        beardSprite.Texture.Dispose();
        beardSprite = null;
    }

    var weaponSprite = CreateCustomisationSprite(animationName, "Weapon", weaponType.ToString(), animationIndex, null);
    weaponSprite.Position = centerPoint;
    weaponSprite.Visit();
    weaponSprite.Cleanup();
    weaponSprite.Texture.Dispose();
    weaponSprite = null;

    characterTexture.End();

    CCTextureCache.SharedTextureCache.RemoveAllTextures();
    CCTextureCache.PurgeSharedTextureCache();
    CCTextureCache.SharedTextureCache.UnloadContent();
    CCTextureCache.SharedTextureCache.Dispose();


    return characterTexture.Sprite.Texture;
}

private CCSprite CreateCustomisationSprite(string animationName, string customisationType, string customisationName, int animationIndex, CCColor3B? color)
{
    var texture = CCTextureCache.SharedTextureCache.AddImage(string.Format(@"Character\{0}\{1}\{2}\{3}",
        animationName,
        customisationType,
        customisationName,
        animationIndex));

    var sprite = CCSprite.Create(texture);

    if(color != null) sprite.Color = color.Value;

    return sprite;
}    
}
Mar 31, 2013 at 8:04 PM
Edited Mar 31, 2013 at 8:05 PM
Regarding the One Layer At A Time test... it works up till a certain number of sprites are loaded. Loading any more causes the Out Of Memory Exception.

Do you think its the size of the images I am dealing with? Each image is 15KB (I lie, they range from 2KB to 16KB), and all are 490 x 278 pixels. Is this unreasonably large? A friend suggested I reduce the size of the image and scale them up on the screen, but I thought I'm already dealing with reasonably small images and didn't want to reduce the quality unless absolutely needed.
Mar 31, 2013 at 9:40 PM
With newly added images, my phone can no longer play the game without crashing due to this issue :(
Apr 1, 2013 at 1:44 AM
First, don't dispose any of your textures. Although the runtime is saying out of memory, this is probably not the case.

hairSprite.Texture.Dispose();

This will be handled by the content manager when you no longer have any references to the texture.

The size of the images is not the problem. XNA can handle 2048 x 2048 textures. We have games in production release that use more sprites that are larger than yours.

How far does the "it works up til a certain number of sprites are loaded" do you get? Is there a specific number of sprites that you can load?

The best thing you can do is to put your sprites into a sprite sheet using something like Predator.

I am still reviewing your code and looking for a reason for this. One thing to consider is an infinite loop that one of your sprite loaders is incurring during the load.
Apr 1, 2013 at 1:53 AM
You are on windows 8? You are then using an upgraded windows install to windows 8 that had xna 4 refresh 1 on it? Otherwise you are using mono game for the windows store? Any of this correct?
Apr 1, 2013 at 2:22 AM
I have 2 dev environments.

Windows 8 Pro running VS2012. Its not an upgraded windows install, its a brand new laptop that came preloaded with Win8. Emulators for WP7.1 to WP8 512 and 720p.

Other PC is a Win 7 desktop running VS2010 and emulator for WP7 only.

Neither are using mono.

Both environments have the memory leak issue and crash after about 150mb is loaded.

I also have deployed this to my WP7 which also crashes at the same point.

I'm sure its not an infinite loop though as I stepped through it before and witnessed it crash one it loaded one sprite too many. I'll let you know tomorrow how many sprites it took before it crashed.

Thanks for looking into it.
Apr 1, 2013 at 2:24 AM
I'm not sure what XNA 4 refresh is... I'll look it up now to make sure thats not the issue.
Apr 1, 2013 at 2:26 AM
I only added the dispose stuff after identifing that there was a problem. I've thrown everything against cleaning up memory in the solution I posted.
Apr 1, 2013 at 2:35 AM
I think the XNA 4 refresh was the update released for WP8 and 7.8 compatibility, right? I created the project before that update, so I believe its running with the 3.1 dlls for xna. I didn't want to upgrade it as my phone is still a WP7 and was worried about compatibility. Is there a known issue with 3.1?
Apr 1, 2013 at 2:37 AM
Just checked, the XNA dlls in the project are all v4
Apr 1, 2013 at 2:40 AM
Tomorrow I'll make a new project and use some similar code to what I posted and see if i can replicate the issue and then post it.
Apr 1, 2013 at 4:45 AM
XNA4 Refresh 1 was released about two years ago, it was a bug fix release. XNA 3.1 is not supported by cocos2d-xna. We have made changes to the framework that breaks compatibility with 3.1, so your game using cocos2d-xna can not work on a Zune. Zune is XNA 3.1 only.

Let me know what happens when you create a new project.
Apr 1, 2013 at 2:36 PM
Edited Apr 1, 2013 at 2:39 PM
Today has been a write off for development. My "simple" project to demonstrate the issue doesn't even work due to a null reference exception inside part of cocos2d-xna which I can't figure out why its any different to the code I have that works. And my game solution has stopped deploying to my emulator with no specific error message other than "can't deploy". Luckily rolling back to a previous version in my git repo still works, but again, no idea why all of a sudden it stopped working (last night it was working fine)

The new error I get in this sample app is on calls to Visit()

A null reference exception is thrown on line 409 of DrawManager.cs
throw new Exception(String.Format("Effect {0} not supported", m_currentEffect.GetType().Name));
Because m_currentEffect is null for some reason.

Anyway... Here's the project that is supposed to demonstrate the issue which doesn't work. Perhaps you have a better idea as to what's going wrong with it.
namespace SpriteLoaderMemoryTest
{

    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            this.graphics.IsFullScreen = true;

            TargetElapsedTime = TimeSpan.FromTicks(333333);
            InactiveSleepTime = TimeSpan.FromSeconds(1);

            CCApplication application = new AppDelegate(this, graphics);
            this.Components.Add(application);
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }
    }

    public class AppDelegate : CCApplication
    {
        public AppDelegate(Game game, GraphicsDeviceManager graphics)
            : base(game, graphics)
        {
            s_pSharedApplication = this;
            DrawManager.InitializeDisplay(game, graphics, DisplayOrientation.LandscapeRight | DisplayOrientation.LandscapeLeft);

            graphics.PreferMultiSampling = false;
        }

        public override bool ApplicationDidFinishLaunching()
        {
            CCDirector pDirector = CCDirector.SharedDirector;
            pDirector.SetOpenGlView();

            DrawManager.SetDesignResolutionSize(800, 480, ResolutionPolicy.ShowAll);

            pDirector.DisplayStats = false;

            pDirector.AnimationInterval = 1.0 / 60;

            CCScene pScene = CCScene.Create();
            CCLayer pLayer = new LoadSpritesScene();

            pScene.AddChild(pLayer);
            pDirector.RunWithScene(pScene);

            return true;
        }

        public override void ApplicationDidEnterBackground()
        {
            CCDirector.SharedDirector.Pause();
        }

        public override void ApplicationWillEnterForeground()
        {
            CCDirector.SharedDirector.Resume();
        }
    }

    public class LoadSpritesScene : CCLayer
    {
        private CCAnimate _swingAnimate;
        private CCAnimate _thrustAnimate;
        private CCAnimate _dodgeAnimate;
        private CCAnimate _collapseAnimate;

        private CCAnimate _swingAnimate2;
        private CCAnimate _thrustAnimate2;
        private CCAnimate _dodgeAnimate2;
        private CCAnimate _collapseAnimate2;

        private CCSprite _testSprite;
        private CCSprite _testSprite2;

        public LoadSpritesScene()
        {
            var winSize = CCDirector.SharedDirector.WinSize;
            var characterSpriteFactory = new CharacterSpriteFactory();

            _testSprite = CCSprite.Create("TestImage");
            _testSprite2 = CCSprite.Create("TestImage");

            _swingAnimate = characterSpriteFactory.CreateAnimateAction();
            _thrustAnimate = characterSpriteFactory.CreateAnimateAction();
            _dodgeAnimate = characterSpriteFactory.CreateAnimateAction();
            _collapseAnimate = characterSpriteFactory.CreateAnimateAction();

            _swingAnimate2 = characterSpriteFactory.CreateAnimateAction();
            _thrustAnimate2 = characterSpriteFactory.CreateAnimateAction();
            _dodgeAnimate2 = characterSpriteFactory.CreateAnimateAction();
            _collapseAnimate2 = characterSpriteFactory.CreateAnimateAction();

            _testSprite.Position = new CCPoint(winSize.Width / 2 -200, winSize.Height / 2 + 100);
            AddChild(_testSprite);

            _testSprite2.Position = new CCPoint(winSize.Width / 2 + 200, winSize.Height / 2 + 100);
            _testSprite2.FlipX = true;
            AddChild(_testSprite2);

            AnimationLoop();
            AnimationLoop2();
        }

        private void AnimationLoop()
        {
            var seq = CCSequence.Create(_swingAnimate, _thrustAnimate, _dodgeAnimate, _collapseAnimate, CCCallFunc.Create(AnimationLoop));
            _testSprite.RunAction(seq);
        }

        private void AnimationLoop2()
        {
            var seq = CCSequence.Create(_swingAnimate2, _thrustAnimate2, _dodgeAnimate2, _collapseAnimate2, CCCallFunc.Create(AnimationLoop2));
            _testSprite2.RunAction(seq);
        }
    }

    public class CharacterSpriteFactory
    {
        private CCSprite CreateCustomisationSprite()
        {
            var sprite = CCSprite.Create(@"TestImage");
            sprite.Color = new CCColor3B
            {
                B = (byte)Random.Next(0, 255),
                G = (byte)Random.Next(0, 255),
                R = (byte)Random.Next(0, 255)
            };
            return sprite;
        }

        public CCTexture2D CreateCharacterTexture()
        {
            const int width = 490;
            const int height = 278;

            var centerPoint = new CCPoint(width / 2, height / 2);
            var characterTexture = CCRenderTexture.Create(width, height);

            characterTexture.BeginWithClear(100, 0, 0, 0);

            var bodySprite = CreateCustomisationSprite();
            bodySprite.Position = centerPoint;
            bodySprite.Visit();

            var armorSprite = CreateCustomisationSprite();
            armorSprite.Position = centerPoint;
            armorSprite.Visit();

            var eyesSprite = CreateCustomisationSprite();
            eyesSprite.Position = centerPoint;
            eyesSprite.Visit();

            var noseSprite = CreateCustomisationSprite();
            noseSprite.Position = centerPoint;
            noseSprite.Visit();

            var hairSprite = CreateCustomisationSprite();
            hairSprite.Position = centerPoint;
            hairSprite.Visit();

            var moustacheSprite = CreateCustomisationSprite();
            moustacheSprite.Position = centerPoint;
            moustacheSprite.Visit();

            var beardSprite = CreateCustomisationSprite();
            beardSprite.Position = centerPoint;
            beardSprite.Visit();

            var helmutSprite = CreateCustomisationSprite();
            helmutSprite.Position = centerPoint;
            helmutSprite.Visit();

            var weaponSprite = CreateCustomisationSprite();
            weaponSprite.Position = centerPoint;
            weaponSprite.Visit();

            characterTexture.End();

            return characterTexture.Sprite.Texture;
        }


        public CCAnimate CreateAnimateAction()
        {
            var frameList = new List<CCSpriteFrame>();

            for (var i = 0; i < 7; i++)
            {
                var texture = CreateCharacterTexture();

                var sprite = CCSpriteFrame.Create(texture, new CCRect(0, 0, texture.ContentSize.Width, texture.ContentSize.Height));
                frameList.Add(sprite);
            }
            var animation = CCAnimation.Create(frameList, 0.1f);
            var animate = CCAnimate.Create(animation);

            return animate;
        }
    }
}
Apr 1, 2013 at 2:38 PM
Oh, and yes, XNA 4 refresh was always installed on my dev pc, so that wasn't the issue.
Apr 1, 2013 at 7:51 PM
I am looking at your sample code. I will have a response for you in a few hours.
Apr 1, 2013 at 9:34 PM
Thanks. I've drawn a complete blank today and can't get the simplest of code to do as expected. Hopefully the error in the code is a trivial thing to spot.
Apr 1, 2013 at 10:07 PM
I put your code, verbatim, into a test case using one of the grossini images, and it worked just great. i'll check it into github so you can see it for your self and run it on your windoze box.
Apr 1, 2013 at 10:13 PM
I thought it seemed solid. Man, I've spent the whole day just trying to recover a version of my project that's deployable. It all seems very fragile all of a sudden. I also found out that VS2012 doesn't support XNA gamestudio for windows games out of the box so I had issues with getting that latest cocos2d-xna source compiling. It's not been fun. Thanks so much for you investigations. Can I ask, what sort of dev environment do you use/recommend for cocos2d-xna WP development? Perhaps its time to invite some other devs into my project :)
Apr 1, 2013 at 10:14 PM
https://github.com/totallyevil/cocos2d-xna/blob/master/tests/tests/classes/tests/RenderTextureTest/RenderTextureCompositeTest.cs

There it is, exactly how you have it, just without your bootstrapper. Run it yourself to see that it works for you. I am running it on Windows 7 Ultimate in VS 2010 Professional.
Apr 1, 2013 at 10:15 PM
Out of curiosity, did you run memory profiler on it to see what sort of usage it was creating? I'd be interested to see if it gets anywhere near the 150mb that I was seeing.
Apr 1, 2013 at 10:17 PM
:) ok, so I tried setting up a Win 8 dev box for XNA about 4 months ago, it's still sitting there. Could not get XNA to install on it. If you are going to dev on Win 8 then you'll need to forgo the Microsoft XNA integration.

Now, I also have a Win 8 laptop that was converted from Win 7 as a full xna game dev box. That continues to successfully build and deploy XNA games to xbox and WP7/WP8. I keep that laptop under precious protection.

As for VS2012 and cocos2d-xna, I have not tried building it there, but it would only work with MonoGame for Windows (GL or DX).
Apr 1, 2013 at 10:17 PM
I will run it using the WP7 simulator next. I am just using the Windows desktop runtime right now.
Apr 1, 2013 at 10:36 PM
On the WP7 (512MB) simulator it works fine. Sadly, my simulator says it only uses < 50 MB and there are only 3 GC events. That's how I would expect this to work.

What you are seeing is just compatibility issues in VS2012 and/or Win 8.

Note that you can still develop for Windows 8 and Windows 7 phones on Win 8 without having to build a Win 8 project, if you upgrade from Win 7 and have XNA 4 r1 already installed. You just have to use the super-secret XAP Deployment tool. It comes with the Win 8 phone sdk.
Apr 2, 2013 at 12:08 AM
So I tried creating my solution again (on my Win 7 pc this time), and what do you know, it works even though its using the exact same source files. I suppose some how Win8 or VS2012 did something different in the solution or project file? So now I'm seeing about 35mb of memory being used. I'll give it a go with multiple image files now and see if it makes a difference.
Apr 2, 2013 at 12:35 AM
Edited Apr 2, 2013 at 12:36 AM
Okay, getting somewhere now.

I've modified the test to use different images like an animation might. I have 10 folders in my Content project named 1 - 10 representing the different layers of a sprite (eg. Head, Eyes, Clothes, etc). Inside each folder is 7 images (again, 1 - 7), representing individual frames of the animation for that customisation. Using the modified code below, my memory profiler picks up that its using about 75mb now.

As before, each image is approx 15kb in size. All together there is about 1mb of images. Should I be concerned that it is taking 75mb of memory to render 2 x 1mb worth of images?
public class CharacterSpriteFactory
{
    private CCSprite CreateCustomisationSprite(int folederName, int fileName)
    {
        var sprite = CCSprite.Create(string.Format(@"{0}\{1}", folederName, fileName));
        sprite.Color = new CCColor3B
        {
            B = (byte)Random.Next(0, 255),
            G = (byte)Random.Next(0, 255),
            R = (byte)Random.Next(0, 255)
        };
        return sprite;
    }

    public CCTexture2D CreateCharacterTexture(int fileName)
    {
        const int width = 490;
        const int height = 278;

        var centerPoint = new CCPoint(width / 2, height / 2);
        var characterTexture = CCRenderTexture.Create(width, height);

        characterTexture.BeginWithClear(100, 0, 0, 0);

        for (var folderName = 1; folderName < 11; folderName++)
        {
            var bodySprite = CreateCustomisationSprite(folderName, fileName);
            bodySprite.Position = centerPoint;
            bodySprite.Visit();
        }

        characterTexture.End();

        return characterTexture.Sprite.Texture;
    }


    public CCAnimate CreateAnimateAction()
    {
        var frameList = new List<CCSpriteFrame>();

        for (var fileName = 1; fileName < 8; fileName++)
        {
            var texture = CreateCharacterTexture(fileName);

            var sprite = CCSpriteFrame.Create(texture, new CCRect(0, 0, texture.ContentSize.Width, texture.ContentSize.Height));
            frameList.Add(sprite);
        }
        var animation = CCAnimation.Create(frameList, 0.1f);
        var animate = CCAnimate.Create(animation);

        return animate;
    }
}
Apr 2, 2013 at 12:41 AM
Upping it to 20 folders of 7x15kb images (about 2.1mb) climbs to about 110mb in the profiler.
Apr 2, 2013 at 12:45 AM
Consider a 15 kb JPEG. That's a DCT compressed, lossy image that is resolution neutral. You blow it up to 490 x 278, which is 136,220 pixels. Each pixel is 24 bits say, so that's 3 bytes per pixel x 136220 pixels = 399KB per image. Now you have 10 x 7 of these, or about 27 MB of uncompressed texture data loaded at runtime.

Originally your game ran at about 35 MB. Add in new content (+27) and you have 62 MB. You say 75MB, sure, your framebuffer supports 32 bit pixels probably.

The frame buffer will compress the images for itself using DXT format, or whatever format it supports. You don't get access to that information unless you GetData<Color> on the framebuffer to see what is in there.
Apr 2, 2013 at 1:10 AM
I've modified the test to be closer to what my game does.

4 folders representing different animations in folders:
Content\Animation1, 2, 3, 4 (e.g. jumping, running, attacking, dying)

Inside each Animation folder are 10 folders representing layers of cutomisation in folders:
Content\AnimationN\Customisation1 - 10 (e.g. HairStyle, Clothes, Shoes, etc)

And inside each Customisation folder are 7 images representing individual frames of the animation for each customized layer:
1 - 7 (e.g. Frames 1 - 7 of HairStyle.Mohawk for jumping animation)

So I am dealing with a large number of files, 4mb+. Perhaps I'm misunderstanding the usage of RenderTexture, as I thought it would no longer need to store all the originally loaded sprites in memory once it has rendered onto a single texture. As you may have expected, memory profiler now reports near 200mb of usage. I guess my question is... is there a better way of achieving my goal in cocos2d-xna? What are some tactics I might use to avoid running up so much memory without cutting content from my game?
public class CharacterSpriteFactory
{
    private CCSprite CreateCustomisationSprite(int animationName, int folederName, int frameName)
    {
        var sprite = CCSprite.Create(string.Format(@"Animation{0}\Customisation{1}\{2}", animationName, folederName, frameName));
        sprite.Color = new CCColor3B
        {
            B = (byte)Random.Next(0, 255),
            G = (byte)Random.Next(0, 255),
            R = (byte)Random.Next(0, 255)
        };
        return sprite;
    }

    public CCTexture2D CreateCharacterTexture(int animationName, int fileName)
    {
        const int width = 490;
        const int height = 278;

        var centerPoint = new CCPoint(width / 2, height / 2);
        var characterTexture = CCRenderTexture.Create(width, height);

        characterTexture.BeginWithClear(100, 0, 0, 0);

        for (var customisationName = 1; customisationName < 11; customisationName++)
        {
            var bodySprite = CreateCustomisationSprite(animationName, customisationName, fileName);
            bodySprite.Position = centerPoint;
            bodySprite.Visit();
        }

        characterTexture.End();

        return characterTexture.Sprite.Texture;
    }


    public CCAnimate CreateAnimateAction(int animationName)
    {
        var frameList = new List<CCSpriteFrame>();

        for (var fileName = 1; fileName < 8; fileName++)
        {
            var texture = CreateCharacterTexture(animationName, fileName);

            var sprite = CCSpriteFrame.Create(texture, new CCRect(0, 0, texture.ContentSize.Width, texture.ContentSize.Height));
            frameList.Add(sprite);
        }
        var animation = CCAnimation.Create(frameList, 0.1f);
        var animate = CCAnimate.Create(animation);

        return animate;
    }
}
Apr 2, 2013 at 1:19 AM
hehe. The content manager keeps the textures in memory. Typically instead of pre-rendering the composite texture, you could just layer them at runtime. That's probably not going to be very fast though.

You could call Content.UnloadContent to unload the content that the content loader has kept in memory. That should flush out the loaded textures and leave you with just the rendered textures.
Apr 2, 2013 at 1:31 AM
For the sake of experimentation, I halved the image size of my test images to 245x139 and 5kb png each, and then scale them up when I rendered them. Now I've reduced memory to about 75mb, but at the cost of my images not looking that great anymore. Do you think I am aiming too high for a WP7 game with these kind of expectations?
Apr 2, 2013 at 1:41 AM
Edited Apr 2, 2013 at 1:45 AM
When you say Content.UnloadContent, is there a shared ContentManager I run that on? I take it we're getting into the nitty gritty of XNA now. :)
Apr 2, 2013 at 2:24 AM
Edited Apr 2, 2013 at 2:25 AM
Layer them at run time you say... :) funny you mention that, as that's what I was doing when I first ran into this memory issue (preloading the textures in a loading scene beforehand). I went down the route of RenderTexture to try and save on memory. FYI, it didn't make any substantial difference in execution time with these two different methods. RenderTexture actually takes quite a while to complete on my phone, a hit I'd be willing to take if it actually helped my memory problems (which it doesn't).
Apr 2, 2013 at 4:20 AM
The shared CCApplication instance gives you the content manager that XNA uses to load content. You can call ContentManager.Unload():

http://stackoverflow.com/questions/4264995/how-do-i-unload-content-from-the-content-manager

Once you create the textures, and are able to keep them around, then you no longer need the source sprites and can unload them. whether or not that reduces your memory footprint, I don't know. I don't know enough about your game loop to know either way.
Apr 2, 2013 at 10:26 AM
Edited Apr 2, 2013 at 4:53 PM
I have some very interesting results now :)

Tested on the train ride in this morning. Adding a call to Content.Unload after each frame is Rendered like so:
...
    characterTexture.End();
    CCApplication.SharedApplication.Content.Unload();
    return characterTexture.Sprite();
}
In Windows 8, VS2012, WP7.1 emulator: OutOfMemoryException. Memory Profiler shows that it reaches about 300mb in memory before it crashes. GC is called in regular time periods (assumably when Content.Unload() is called), however memory usage is not effected and keeps rising.

BUT

In Windows 8, VS2012, WP8 WVGA 512mb Emulator: no problem, faster than normal, Memory Profiler never exceeds 40mb! GC is only called a couple of times at the beginning, doesn't seem to coincide with the Unload calls, though you can see the memory rise and dip until it reaches it 40mb constant.

I know its just an emulator, but it is interesting. Especially since prior to this, WP7 was the emulator that worked when WP8 would crash.
Apr 2, 2013 at 4:39 PM
The GC on Windows 8 is probably much smarter about the heap crawl. Make sure that you null out references to the textures and sprites returned during the texture building process. We may want to add a method in CCSprite or CCTexture2D to purge itself from the cache so you can purge the sprites selectively. We could also maintain a weak reference to the Texture2D in the texture cache which may help as well.

Glad to see progress though.
Apr 2, 2013 at 4:51 PM
Selective purging does sound quite useful. I quickly tried Content.Unload in my actual game but it now crashes for other reasons... I presume it's due to unloading something that is still needed elsewhere.
Apr 2, 2013 at 5:07 PM
Interesting. Do you have a stack trace for the crash that shows a cocos2d path? My guess would be that you purged a sound that needed to be played. The texture cache should keep your texture2d instances in memory ...
Apr 2, 2013 at 5:11 PM
I haven't had a chance to look into it yet. I'll investigate it in more depth soon. I did have Denison playing some music, so perhaps that's what killed it.
Apr 3, 2013 at 9:04 PM
Edited Apr 3, 2013 at 9:06 PM
Trying to investigate the crash now... actually it appears that no exception is thrown and the application is just exiting elegantly, though I don't know why. This is on my Windows 8 PC using WP8 emulator. I can't step into the cocos2d-xna to find out whether its something it's doing. Running it on my Windows 7 PC on a WP7 emulator crashes with the original memory issue still. I'm installing VS2010 on my Win 8 laptop to see if I can get XNA Game Studio 4 to install and build cocos2d-xna on it so I can jump in and see whats going on.
Apr 3, 2013 at 9:48 PM
That sounds like an Assert() being triggered. MonoGame has a Windows AppStore build that would allow you to deploy directly to the Win8 phone and debug on it.
Apr 4, 2013 at 12:49 PM
I don't have a win8 phone yet, just my win7 phone, and so far it still seems to have the memory issues despite the unload fix. I'm not familiar with assertion, is there any advice or documentation on analysing (logging, debugging, etc) assertion in cocos2d-xna?
Apr 4, 2013 at 6:00 PM
Edited Apr 5, 2013 at 5:02 PM
cocos2d-xna uses Debug.Assert() so asserts are triggered in a debug build. You will get a message in the device logs when the assert is triggered. If you are attached to the simulator using the debugger, then the assert will show as a kind of exception.
Apr 7, 2013 at 3:17 PM
Arkiliknam wrote:
I thought it seemed solid. Man, I've spent the whole day just trying to recover a version of my project that's deployable. It all seems very fragile all of a sudden. I also found out that VS2012 doesn't support XNA gamestudio for windows games out of the box so I had issues with getting that latest cocos2d-xna source compiling. It's not been fun. Thanks so much for you investigations. Can I ask, what sort of dev environment do you use/recommend for cocos2d-xna WP development? Perhaps its time to invite some other devs into my project :)
" I also found out that VS2012 doesn't support XNA gamestudio for windows games "
Apr 12, 2013 at 5:40 AM
I had a chance to catch up with some Cocos2d-x devs I used to work with and they quickly pointed out that my textures were very wasteful as I had them in a separate image file each with much transparency wasting memory. I did it this way as I layer customizations such as eyes onto a body and wanted to maintain the position without having to place it in code. They explained I didn't need to do that as a Plist will maintain all this information for me in XML and is integrated with cocos2dx! I was already using predator but didnt know about this featuring in cocos2d-x and cocos2d-xna. This sounds like an amazing memory saver for me as the eyes, for example are only a few pixels per frame, yet I was using the entire frame sized image as a texture.

So I'm ready to go... but now my solution has problems again. In my Content project, I've added references to cocos2d-xna and cocos2d.Content.Pipeline.Importers (ContentImporters is the only thing I can build on my win8 laptop, the cocos2d-xna dll is one I built earlier on my Win7 machine), I've added my an image (spriteset) called Normal.png and the new plist file Normal.plist.xml (renamed to .plist.xml as XNA compiling ignores extensions and will cause a conflict with the png file if named Normal.plist). I've set the properties of the plist file to:
Assent Name: Normal.plist
Build Action: Compile
Content Importer: Cocos2D - Plist
Content Processor: Cocos2D - Plist
Copy to Output: Do not copy
File Name: Normal.plist.xml

However, if I try to build my project now, I get the following error:

Error 1 Building content threw FileNotFoundException: Could not load file or assembly 'System.Xml, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' or one of its dependencies. The system cannot find the file specified.
at cocos2d.PlistDocument.LoadFromXml(String data)

Unfortunatly this is on my Win8 ultrabook which is all I have access to right now so am not sure if this is another environment related issue...

I'm trying to add a reference to System.Xml but so far no luck. In the mean time, am I going about this the right way? Anything obvious that you can point out to me?
Apr 12, 2013 at 4:48 PM
Typically the sprite sheet is stored in an "images" directory and the plist is stored above that:

content/images/sheet.png
content/sheet.plist

Then you can load the "sheet" plist and it will know how to pull in the sheet png file. That's the typical pattern that we see other developers doing, but that is a hokey way of doing things.

You can control the name of the output asset XNB using the "Asset Name" property. You will see this in action via the tests - open the testContent project, open zwoptex and you will see a plist and png file both in the same directory. The plist asset is given its extension in the asset name property so that the output generated is named "grossini-generic.plist.xnb" which is desirable to prevent name collisions.

The content project only needs a reference to the cocos2d content importers project and not cocos2d-xna.

The System.Xml version 2.0.5 comes from the Windows Phone 7 sdk (7.1). Did you set your target framework to 7.0 or 7.1?
Apr 18, 2013 at 7:54 AM
Still trying to get cocos2d-xna running on my win8 laptop so that I can continue working on the game. I did just resolve one very annoying issue that others may be affected by.

Here's the build error I was getting:
Error 25 Error loading pipeline assembly "C:\Development\cocos2d-xna\cocos2d.Content.Pipeline.Importers\bin\x86\Debug\ICSharpCode.SharpZipLib.dll". tests.Windows

And the solution? Well, after much frustration, I found ICSharpCode.SharpZipLib.dll in the folder "C:\Development\cocos2d-xna\cocos2d\external lib", right clicked on it to view properties, and see the following message: "Security: This file came from another computer and might be blocked to help protect this computer." !!!!! Clicked the Unblock button, crisis averted. Thanks Windows 8 for protecting me from myself and the hazards of development. :)
Apr 19, 2013 at 4:50 PM
Yeah, we are phasing out the ICSharpCode library as it is not portable to console devices and has encryption that we do not want. Hopefully soon this will not be an issue on Win 8, and beyond.

Thanks for the tip!
Jul 11, 2013 at 10:27 AM
After a long time of no game dev, I'm back into it and trying to solve this issue once and for all.

My projects now build perfectly on my dev environment and I'm working on the spritesheet implementations. I experimented with Predator first to create my sprite sheets but was unable to load it's animations (which I'd created with it) in Cocos2d-xna. So I've switched to TexturePacker which seems like it doesn't support Animations persay. The strategy I'm pursuing now is to create a spriteset and plist for each layer of my character.

Eg.

Body.plist contains body layer images for:
Dodge1.png
Dodge2.png
Dodge3.png
Swing1.png
Swing2.png
Swing3.png
Thrust1.png
Thrust2.png
Thrust3.png
Hair.plist would then contain hair images in the same structure:
Dodge1.png
Dodge2.png
Dodge3.png
Swing1.png
Swing2.png
Swing3.png
Thrust1.png
Thrust2.png
Thrust3.png
I may be able to squeeze more animations into the sprite sets later on, such as storing various haircut sprites in the Hair.plist to save even more on memory.

Then I load my frames when a character is being loaded with:
var cache = CCSpriteFrameCache.SharedSpriteFrameCache;
cache.AddSpriteFramesWithFile("Body.plist");
Then I construct animations for them:
foreach (var frame in frameIndexSequence)
{
   var spriteFrame = CCSpriteFrameCache.SharedSpriteFrameCache.SpriteFrameByName("NormalBody" + animationName + frame.ToString() + ".png");
   frameList.Add(spriteFrame);
}
var animation = CCAnimation.Create(frameList, 0.1f);
var animate = CCAnimate.Create(animation);
Now I have to rethink how to go about colouring and stacking these animations. My plan now is to create a sprite for each layer of a character, set the colour of those sprites based on the characters info, and play an animation for each layer of the animation when the character performs an action. This is still a heafty amount of textures, but I think it will reduce memory issues.

Can you see any problems with this plan of action? I'll report back on how it all goes. Hopefully I can get this out of the way as it really put my project to a standstill for such a long time.
Jul 11, 2013 at 6:01 PM
Predator does not write the correct cocos2d animation plist. It fails to write the version number, and it's writing version 1 of the animation plist. I've spoken to the author of predator about this, but he has not fixed the bug.

In your plist file from predator, at the end where there is metadata, you add the following:

<key>format</key><integer>1</integer>

Once you set the format of the plist file, cc2d-xna can read it. We do this in one of our games and we use predator to make some animations.
Jul 11, 2013 at 6:10 PM
Make sure you name the frames with unique names. The frame cache is global and uses the frame name as the key. When you add a plist to the frame cache it returns a prefix key that you should use to reference your individual frames.
Jul 11, 2013 at 7:21 PM
I tried adding the xml like you mentioned and end up with this:
    <key>metadata</key>
    <dict>
        <key>format</key>
        <integer>1</integer>
        <key>size</key>
        <string>{1024,1024}</string>
        <key>textureFileName</key>
        <string>NormalBodyPredator.png</string>
    </dict>
However when I call:

cache.AddAnimationsWithFile("NormalBodyPredator.plist");

I can see that no animations have been added to m_pAnimations in the cache.
Jul 11, 2013 at 8:30 PM
Are you pulling out frames using the frame prefix returned by the AddAnimationsWithFile() method?
Jul 11, 2013 at 9:54 PM
I use the following:

var cache = CCAnimationCache.SharedAnimationCache;
cache.AddAnimationsWithFile(@"Character\NormalBodyPredator.plist"); var animation = CCAnimationCache.SharedAnimationCache.AnimationByName(animationName.ToString());

I also checked the cache object in debug to make sure there were no animations being loaded after calling AddAnimationsWithFile. Also, AddAnimationsWithFile() does not return a prefix as it is void. Should I be adding them with a different method?

Ahh... do I also have to add the frames to the frames cache using .AddSpriteFramesWithFile(@"Character\NormalBody.plist"); for the animation to load?
Jul 11, 2013 at 10:24 PM
Sorry for the confusion. Yes, I use "AddSpriteFramesWithFile" and I specify the prefix in the third parameter:
            CCSpriteFrameCache cache = CCSpriteFrameCache.SharedSpriteFrameCache;
            cache.AddSpriteFramesWithFile(plistFile, plistFile + "_Sprites", anim + ":");
AddAnimationsWithFile - not sure if that works properly with Predator. Again, there Predator is not specifying the format, so you need to specify it. Format 1 animation file should have a properties section with "format=1" in there, and then under that the spritesheets.

I guess we should start working on a cocos2d-xna spritesheet/animation tool. We have a tool that does this, but it generates code instead of plist files. I don't really see the need for a plist file since this can be coded quite easily using a tool that composes the animations for you.

Here is a simple coin animation class from Mayan Epic:
public class CoinAnimation : CCSprite
{
    private CCAction _MyAction;

    public CoinAnimation()
    {
        CCAnimation anim = CCAnimationCache.SharedAnimationCache["CoinForStoreTabAnimation"];
        base.InitWithSpriteFrame(anim.Frames[0].SpriteFrame);
        anim.RestoreOriginalFrame = true;
        CCAnimate animation = new CCAnimate(anim);
        _MyAction = new CCRepeatForever((CCActionInterval)animation);
    }

    public void Play()
    {
        RunAction(_MyAction.Copy());
    }
}
The definition of this CoinForStoreTabAnimation:
        CCTexture2D txCoinForStoreTabAnimation = CCTextureCache.SharedTextureCache.AddImage("images/coinanim");
        var animFramesCoinForStoreTabAnimation = new List<CCSpriteFrame>();
        animFramesCoinForStoreTabAnimation.Add(new CCSpriteFrame(txCoinForStoreTabAnimation, new CCRect(0,0,64,64)));
        animFramesCoinForStoreTabAnimation.Add(new CCSpriteFrame(txCoinForStoreTabAnimation, new CCRect(64,0,64,64)));
        animFramesCoinForStoreTabAnimation.Add(new CCSpriteFrame(txCoinForStoreTabAnimation, new CCRect(0,64,64,64)));
        animFramesCoinForStoreTabAnimation.Add(new CCSpriteFrame(txCoinForStoreTabAnimation, new CCRect(64,64,64,64)));
        CCAnimation CoinForStoreTabAnimation_Animation = new CCAnimation(animFramesCoinForStoreTabAnimation, 0.2f);
        CCAnimationCache.SharedAnimationCache.AddAnimation(CoinForStoreTabAnimation_Animation, "CoinForStoreTabAnimation");

Jul 11, 2013 at 10:32 PM
Ahh, cool. Well in that case I already had it working. And then you add it to the animation cache. Cool, so I just need to add my created animations to the SharedAnimationCache and can create multiple Animate instances using the one Animation.
Jul 11, 2013 at 10:40 PM
Correct. Never reuse the CCAnimate action unless you want to resume an animation where it had paused once before.

We're working to make this much easier.
Jul 17, 2013 at 12:07 AM
Edited Jul 17, 2013 at 12:08 AM
I've now reworked all of my images into spritesets like below:
Body.plist
Body,png
Hair.plist
Hair.png
Eyes.plist
Eyes.png
etc
Each set contains all variations of that type. (Eg Hair contains Mohawk images, ShortFringe images and SideComb images)
Each set contains all the animation frames of each variation (Eg Dodge, Swing, Collapse, Thrust)

I've created a test scene which contains:
Dictionary<CustomisationType, CCAnimate> _swingAnimateDictionary;
Dictionary<CustomisationType, CCAnimate> _thrustAnimateDictionary;
Dictionary<CustomisationType, CCAnimate> _dodgeAnimateDictionary;
Dictionary<CustomisationType, CCAnimate> _collapseAnimateDictionary;
// for the four different animation types
// eg hair = hairAnimate

a Dictionary<CustomisationType, CCSprite> _customisationLayerDictionary;
// for finding the sprite for a customisation type
// eg hair = hairSprite

a Dictionary<CustomisationType, CustomisationName> _customisationTypeNameDictionary;
// for finding a customisation for a customisation.
// eg Hair = Mohawk

The test then assigns a single customisation to each customisationType. I hard coded this for testing purposes so that I know it should work. Part of the assignment process calls my SpriteAnimation factory, which first tries to find the Animation in the cache using a key, and if it fails, then loads the plist and all the animations associated with that plist into the animation cache. If it still cant find the animation, I throw an exception, otherwise I use the animation to create an Animate for that customisation. This is done for the four different animation types.

Finally, the test begins looping animation playback using the following code:
private void AnimationLoop()
        {
            foreach (var sprite in _customisationLayerDictionary)
            {
                var seq = CCSequence.Create(
                _swingAnimateDictionary[sprite.Key],
                _thrustAnimateDictionary[sprite.Key],
                _dodgeAnimateDictionary[sprite.Key],
                _collapseAnimateDictionary[sprite.Key],
                CCCallFunc.Create(AnimationLoop));
                _customisationLayerDictionary[sprite.Key].RunAction(seq);    
            }
        }
The intent is to loop through each layer of the character's sprites and runs it's respective Animate object on it, all at the same time, and uses a CCSequence to do this indefinitely.

Everything loads super quick compared to before. It all works well for the first 4 iterations of each of the animation loops. But after that, the frames jerk to a halt and the application crashes. I'm still looking into it, but so far I haven't spotted what's wrong.

Assuming my code all does what I say it does, is there any blatantly obvious aspect that I still haven't grasped here? I'm not sure why all the animate objects would get created fine and then after being run 4 times stop preforming. Is it perhaps the way I create the sequence? I'll keep investigating, but in the mean time if you spot any step that is a definite or possible offender, please let me know.
Jul 17, 2013 at 12:18 AM
CCCallFunc.Create() -> you are using an older version of the framework. Make sure you work from the github source and not the codeplex source.

Next, you are likely running into a problem of drawing each sprite individually rather than using the batch. You can probably get about 75 sprites running at the same time before you see some slowing. With spritebatch (CCSpriteBatch) you won't see the happen as quickly. You can probably get about 10x as many sprites using CCSpriteBatch.
Jul 19, 2013 at 12:49 AM
I updated my code to use a single SpriteBatchNode for a spriteset containing all of my character frames. It still suffers from the issue where although it initially loads all the animations very quickly, after playing them each 4 or 5 times, frame rate suddenly dramatically drops and the app eventually crashes. Memory profiler shows my classic problem of memory usage continuing to rise until it gets unstable and crashes. This confuses me as I thought that memory shouldn't continue to rise as all the frames are already loaded and I'm simply calling an action to run. I'm searching my code for bugs to make sure I'm not doing anything stupid.

Speaking of spritesets and plists, I have a best usage question. Is there a preference to sticking as many sprites in a single spriteset as opposed to separating them into what will be needed for an action? I currently have a spriteset of all possible frames for my character, is it detrimental to performance this way as a specific character only cares about his specific sprites, eg BigNose sprites, not any other type of nose. Is there a significant cost of loading frames from a plist that would sway me one way or the other?

I downloaded the latest cocos2d-xna source from GitHub and built it successfully, however after updating my solutions reference with the new assemblies it's now having an issue with loading my plist. I get the error:
Error loading "Character\Character.plist". Cannot find ContentTypeReader Cocos2D.PlistDocument+PlistDocumentReader, cocos2d-xna, Version=2.0.3.0, Culture=neutral, PublicKeyToken=null.
Probably something to do with my Win8 VS2012 environment again. I'll see if VS2010 can do any better. This is why I don't update it as regularly as I should :)
Jul 19, 2013 at 4:38 PM
Ah, so now we are getting more details on your runtime.

Windows 8 + VS2012 = MonoGame. Are you using MonoGame OpenGL or DirectX?

The Plist error comes from the PlistDocument inner class - PlistDocumentReder - getting removed from the binary in release mode. There might also be another exception in the log that gives you more details about why the PlistDocumentReader was not able to read your plist.

We're trying to get away from using the custom content builder for cocos2d-xna. I recommend loading the plist as a raw asset and not including the xnb of it in your project.

That means having "Content/myPlist.plist" as content in your project. Then you do something like this:
        System.IO.Stream s = TitleContainer.OpenStream(System.IO.Path.Combine("Content", "Female_Mourner_death.plist"));
        PlistDocument doc = new PlistDocument(s);
TitleContainer is in the Game class so you do this loading inside of LoadContent (Game1.LoadContent).

Once you have the PlistDocument, you can then add the frames to the animation cache like you are doing already. This will bypass the sometimes-wacky content pipeline issues associated with custom content loading.

I have to make a change to PlistDocument for this to work properly though. let me do that now.
Jul 22, 2013 at 10:30 AM
I'm a bit confused when you ask whether I'm using MonoGame OpenGL or DirectX. I'm using the git solution from your repo, building the cocos2d-xna project for windows phone. Should I be using a different project? I also work on this project on my Win7 vs2010 pc sometimes. Do I have to reference different assemblies when using different environments?

For now, I'll update my code to your alternate method for loading plists when I next jump into my project.
Jul 29, 2013 at 8:44 PM
A friend of mine reviewed my code in this test scene and thought my implementation of the animation loop looked suspicious and said, why not just use CCRepeatForever. I tried this, and it resolved the memory leak problem in the test scene. This is using the previous cocos2d-xna dlls I had compiled. Next step, to find out whether all these fixes fix the main game problem I originally had.

So FYI. Animation looping using a recursive like CallFunc in cocos2d-xna == very bad. So bad that after several iterations your memory will be exceeded.
        // Example of the offending code
        private void AnimationLoop()
        {
             var seq = CCSequence.Create(
                _myAnimateAction,
                _myAnimateAction2,
                CCCallFunc.Create(AnimationLoop));
             _mySprite.RunAction(seq);    
        }
Jul 29, 2013 at 9:07 PM
haha. Yeah, you're never releasing memory in that animation loop, so it will run out quickly.... It's basically an infinite loop that allocates objects.

Make sure you use "_myAnimateAction.Copy()" if you want the animation to reset back to frame 0 when you restart it. Otherwise, the animation will just resume where the last time step stopped.
Nov 18, 2013 at 6:19 PM
totallyevil wrote:
Ah, so now we are getting more details on your runtime.

Windows 8 + VS2012 = MonoGame. Are you using MonoGame OpenGL or DirectX?

The Plist error comes from the PlistDocument inner class - PlistDocumentReder - getting removed from the binary in release mode. There might also be another exception in the log that gives you more details about why the PlistDocumentReader was not able to read your plist.

We're trying to get away from using the custom content builder for cocos2d-xna. I recommend loading the plist as a raw asset and not including the xnb of it in your project.

That means having "Content/myPlist.plist" as content in your project. Then you do something like this:
        System.IO.Stream s = TitleContainer.OpenStream(System.IO.Path.Combine("Content", "Female_Mourner_death.plist"));
        PlistDocument doc = new PlistDocument(s);
TitleContainer is in the Game class so you do this loading inside of LoadContent (Game1.LoadContent).

Once you have the PlistDocument, you can then add the frames to the animation cache like you are doing already. This will bypass the sometimes-wacky content pipeline issues associated with custom content loading.

I have to make a change to PlistDocument for this to work properly though. let me do that now.
@totallyevil - I've been working on getting a sprite sheet loaded into Cocos2D-XNA but haven't succeeded so far. I was trying to get it setup through an XNA Content project but was getting some errors during the process. After some searching, I found this post where you talked about loading the plist file as a raw asset instead of through the XNB file. Unfortunately, it looks like the PlistDocument class doesn't have the constructor that accepts the Stream object... I'm assuming that's the change you mentioned above. Do you know if that was removed or if it ever made it into the PlistDocument class? Am I approaching this the wrong way? If you have any code examples handy on the best way to consume a sprite sheet in Cocos2D-XNA, I'd be happy to work it out from there. Thanks.
Nov 19, 2013 at 12:04 AM
After some more reading on the Discussion section, I found this thread:

AddSpriteFramesWithFile plist

This looks like it should answer my questions, I'll post again if I still have issues. Thanks.