Unloading content results in black screen

After unloading content, the iPhone simulator is still drawing the texture(a black texture instead of the real texture). Is this normal or is it a bug? Should the screen not be completely blue(Color.CornflowerBlue)?
Or am I doing something wrong?

Before unloading content:

After unloading content:

Game1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace iPhoneGamestates
{
public class Game1 : Game
{
GraphicsDevice graphicsdevice;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

  public Camera camera;
  public Vector2 vp, gameWorldSize = new Vector2(1334, 750);
  public float ScaleX, ScaleY, Scale;
  Vector2 Newcameraposition;
  public Texture2D BallSprite;
  public Texture2D Menubackground, Introbackground;
  public float delta;
  public bool NextGameState = false;
  IState lastState, currentState, savelastState;
  public ContentManager IntroContent;
  public ContentManager MenuContent;
  public enum GameStates
  {
  	IntroState = 0,
  	MenuState = 1
  }
  public void ChangeGameState(GameStates newState)
  {
  	if (newState == currentGameState)
  		return;
  	lastGameState = currentGameState;
  	lastState = currentState;
  	switch (newState)
  	{
  		case GameStates.IntroState:
  			currentState = new Intro(this, GraphicsDevice);
  			currentGameState = GameStates.IntroState;
  			break;
  		case GameStates.MenuState:
  			currentState = new Menu(this, GraphicsDevice);
  			currentGameState = GameStates.MenuState;
  			break;								
  	}
  	currentState.Load(Content);
  }
  public void ChangeToLastandCurrent()
  {
  	savelastGameState = currentGameState;
  	currentGameState = lastGameState;
  	lastGameState = savelastGameState;
  	savelastState = currentState;
  	currentState = lastState;
  	lastState = savelastState;
  }
  public GameStates CurrentState
  {
  	get { return currentGameState; }
  	set { currentGameState = value; }
  }
  public GameStates LastState
  {
  	get { return lastGameState; }
  	set { lastGameState = value; }
  }
  private GameStates currentGameState;
  private GameStates lastGameState;
  private GameStates savelastGameState;
  public Game1()
  {
  	graphics = new GraphicsDeviceManager(this);
  	Content.RootDirectory = "Content";
  	graphics.IsFullScreen = true;
  }
  protected override void Initialize()
  {
  	currentState = new Intro(this, graphicsdevice);
  	currentGameState = GameStates.IntroState;
  	base.Initialize();
  }
  protected override void UnloadContent()
  {
  	Content.Unload();
  }
  public void IntroLoad()
  {
  	Introbackground = IntroContent.Load<Texture2D>("Content/Introbackground");
  }
  public void MenuLoad()
  {
  	Menubackground = MenuContent.Load<Texture2D>("Content/Menubackground");
  }
  protected override void LoadContent()
  {
  	spriteBatch = new SpriteBatch(GraphicsDevice);
  	vp = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
  	ScaleX = vp.X / gameWorldSize.X;
  	ScaleY = vp.Y / gameWorldSize.Y;
  	Scale = Math.Min(ScaleX, ScaleY);
  	camera = new Camera(new Vector2(gameWorldSize.X / 2, gameWorldSize.Y / 2), Scale, vp);
  	currentState.Load(Content);
  	TouchPanel.EnabledGestures = GestureType.Tap;
  }
  protected override void Update(GameTime gameTime)
  {
  	delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
  	Newcameraposition = new Vector2(gameWorldSize.X / 2, gameWorldSize.Y / 2);
  	
  	camera.Update(gameTime, Newcameraposition, Scale, vp);
  	while (TouchPanel.IsGestureAvailable)
  	{
  		GestureSample gs = TouchPanel.ReadGesture();
  		switch (gs.GestureType)
  		{
  			case GestureType.Tap:						
  				NextGameState = true;
  			break;
  		}
  	}
  	currentState.Update(gameTime);
  	base.Update(gameTime);
  }
  protected override void Draw(GameTime gameTime)
  {
  	graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
  	spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, camera.GetMatrix());		  	
  	  currentState.Render(spriteBatch);
  	spriteBatch.End();
  	base.Draw(gameTime);
  }

}
}

Intro.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace iPhoneGamestates
{
public class Intro : IState
{
private Game1 game1;
GraphicsDevice graphicsDevice;
float Count = 5f;
bool ChangeGameState = true;

  public Intro(Game1 game, GraphicsDevice device)
  {
  	game1 = game;
  	graphicsDevice = device;
  	game1.IntroContent = new ContentManager(game1.Services);
  }
  public void Load(ContentManager content)
  {
  	game1.IntroLoad();
  }
  public void Update(GameTime gametime)
  {
  	Count -= game1.delta;
  	if ((Count < 0) && (ChangeGameState == true))
  	{
  		ChangeGameState = false;
  		if (game1.IntroContent != null)
  			game1.IntroContent.Unload();
  	}
  }
  public void Render(SpriteBatch batch)
  {
  	batch.Draw(game1.Introbackground, new Rectangle((int)game1.gameWorldSize.X / 2, (int)game1.gameWorldSize.Y / 2, game1.Introbackground.Width, game1.Introbackground.Height), null, Color.White, 0, new Vector2(game1.Introbackground.Width / 2, game1.Introbackground.Height / 2), SpriteEffects.None, 0.10f);
  }

}
}

It looks like you are unloading the content, but continuing to draw using the texture you just unloaded. iOS uses OpenGL, which tends to draw black if anything goes wrong with the texture, such as the texture being unloaded but you still try to use it afterwards.

Calling ContentManager.Unload() will not set references to the textures managed by that ContentManager to null. That is up to you.

Is the memory free after unloading the texture or is it necessary to set references to null to free the memory?

I believe if no references point to the Texture2D the GC will get it eventually.

Alternatively you could call Texture2D.Dispose() and that might free up some of the unmanaged resources associated with it, however the Texture2D object will still be alive if there are references to it still.

Texture2D contains two types of resources: managed and unmanaged. Managed resources is that allocated by the .NET runtime, e.g. objects. Unmanaged is that allocated by the system, such as by graphics drivers. When you call Unload() on the ContentManager, it calls Dispose() on all objects that it loaded that implement IDisposable. Disposing a graphics resource such as Texture2D releases the unmanaged resources. The managed resources are released when all references the managed resource are set to null.

In this case, what I believe is happening is that the textures are being disposed by the call to ContentManager.Unload(), thus releasing the OpenGL resources, but the managed resources, i.e. the Texture2D object itself is staying alive because the fields in the Game object are still referencing the object.

How can I find out how much memory is used by one specific texture or by the entire game?
I followed this tutorial but I don’t know where to find the used memory. I want to check if everything gets unloaded and how many memory is free after unloading content.

After calling Unload(), set the relevant Texture2D members in Game1 to null. The ContentManager no longer has any references to those Texture2D objects, and you must null your own references to those Texture2D objects. In your Draw, check the texture is not null before drawing it.

Do the Texture2D have to be set to null or just simply have no references left to it?

In my case, will it be enough to change the code like this?

public void Update(GameTime gametime)
{
Count -= game1.delta;
if ((Count < 0) && (ChangeGameState == true))
{
ChangeGameState = false;
if (game1.IntroContent != null)
game1.IntroContent.Unload();
game1.Introbackground = null;
}
}
public void Render(SpriteBatch batch)
{
if (game1.Introbackground != null)
batch.Draw(game1.Introbackground, new Rectangle((int)game1.gameWorldSize.X / 2, (int)game1.gameWorldSize.Y / 2, game1.Introbackground.Width, game1.Introbackground.Height), null, Color.White, 0, new Vector2(game1.Introbackground.Width / 2, game1.Introbackground.Height / 2), SpriteEffects.None, 0.10f);
}

There need to be no references to an object before the garbage collector will free the memory used by a managed object. By setting introBackground to null, you are releasing the final reference to the object, since the ContentManager already released its references when it cleared the internal lists of resources it loaded.

That code looks OK and should work I believe.

Quick question - if i unload a texture can i not reload it ?

Eg Reload()

if(loadedTexture!=null)
            loadedTexture.Dispose();
loadedTexture = _contentManager.Load<Texture2D>("test");

Edit:
The issue seems to be that if i dispose a texture it will stay in the assets[] of ContentManager. So if i want to load a new texture from the same directory and the same name it will return a black screen.

Yes it will not be removed from the ContentManager unless you call Unload. Maybe we could let a ContentManager listen for Disposing events on IDisposables assets and remove them in that case since this has come up a couple times before.

but unload removes all, right?

Yes, see the implementation (it’s really simple): https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Content/ContentManager.cs#L438-L448

ye i followed the path and saw the same. Sorry for laziness, am doing exciting work :slight_smile:

We’re all lazy sometimes :wink:

Care to share?

XNA’s ContentManager was designed to load a block of assets and dispose of them all at once. Loading and unloading individual assets at different times is not covered by that design. If you were wanting to load an unload related assets, such as those for a level, create a new ContentManager for that level. Anything more granular than that would likely be best served by a different implementation.

I understand, thanks, I didn’t even think of using multiple content managers.

“Exciting” work, exciting mainly because it moves so fast forward. Posted in the general showcase thread

1 Like