Resource Checker (FREE) - List Texture/Material/Mesh Memory use in scene

Hey,

I’ve been trying to find the best way to list currently used textures in a given scene, and the amount of memory they consume (profiler not providing a breakdown of texture memory). Also wanted to see where active textures are being used, by which GameObjects, which materials and the number of materials/meshes currently used in the scene.

I couldn’t find a workflow that best suits this, so knocked up a quick Editor extension that might help if you are looking to do something similar. You can download from here:

Newish to Unity so there might be a really obvious way of doing this already that I’m not aware of. Anyway, its public domain, so do whatever you like with it, hope its of use to some of you :slight_smile:


Resource checker is designed to help bring visibility to resource use in your scenes (ie what assets are using up memory,
which meshes are a bit too detailed, where are my materials and textures being used).

It should also be useful to check for redundant materials, textures you forget you’re using, textures that can be compressed or reduced in size.

To use, just create an “Editor” folder within “Assets” in your project if you don’t already have one, and drop ResourceChecker.cs in there.

In Unity you’ll see a new option under “Window” → “Resource Checker”

To use, once the window is open, just click ‘Refresh’ and it will list all active textures, materials and meshes in the scene

Textures

  • Textures are listed in descending memory size
  • Click the texture name to open the asset in the project window
  • The size of the texture, compression type and dimensions are show
  • Click the ‘X Mat’ button to select all materials that use this texture in the scene
  • Click the ‘X GO’ to select all game objects in the scene that use this texture

Materials

  • Click the material name to open the material asset in the project window
  • Click the ‘X GO’ to select all game objects in the scene that use this material

Meshes

  • Meshes are listed in descending vertex count
  • Click the mesh name to open the mesh in the project window
  • Click the ‘X GO’ to select all game objects in the scene that use this mesh

Its probably got a bunch of bugs and weird issues - feel free to help improve, fix up!

19 Likes

wow dude! this is epic- while something similar exists in the Profiler this looks amazing

This is going to be very useful. Thank you for making it free and opensource!

Thanks It’s probably very useffull for non-pro user.

Cool, glad its of use! Will try to get up to the store soon too.

In terms of profiler, I couldn’t see any breakdown of memory use by asset, just a total of all textures, all meshes. Is there an option in the profiler that lets you dig deeper to see how that chunk of memory is split up?

Really nice plugin!

I’m using it now to reduce my texture settings from 1024 to 512, do you have a way to sort by texture size and to select all textures at once to mass change them?

Thanks

At the moment it sorts by size of Texture in memory (so if you are using similar compression on most assets, this will be the same result as sorting by texture size, except for cubemaps). I can definitely add a sort alphabetically or just by texture size easily enough. Multi-selection should be easy enough to do too, so do you mean ctrl-select for multi-selection, or a “select all” button or both?

I think control select, and shift select range will suffice, sort by texture properties would be nice, ( 256, 512,1024 etc )

Just added Ctrl select, and a select all button at the bottom. Also list mip levels and some size calculation bugs fixed

I made a small hack/modification- now this will update every second… sorta… I think :slight_smile:
(there is probably a MUCH prettier way of doing this)
Thank you again handcircus !!

// Resource Checker
// (c) 2012 Simon Oliver / HandCircus / hello@handcircus.com
// Public domain, do with whatever you like, commercial or not
// This comes with no warranty, use at your own risk!
// https://github.com/handcircus/Unity-Resource-Checker

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;


public class TextureDetails
{
	public bool isCubeMap;
	public int memSizeKB;
	public Texture texture;
	public TextureFormat format;
	public int mipMapCount;
	public List<Object> FoundInMaterials=new List<Object>();
	public List<Object> FoundInRenderers=new List<Object>();
	public TextureDetails()
	{

	}
};

public class MaterialDetails
{

	public Material material;

	public List<Renderer> FoundInRenderers=new List<Renderer>();

	public MaterialDetails()
	{

	}
};

public class MeshDetails
{

	public Mesh mesh;

	public List<MeshFilter> FoundInMeshFilters=new List<MeshFilter>();

	public MeshDetails()
	{

	}
};

public  class ResourceChecker : EditorWindow {

	float timer=0;
	string[] inspectToolbarStrings = {"Textures", "Materials","Meshes"};

	enum InspectType 
	{
		Textures,Materials,Meshes
	};

	InspectType ActiveInspectType=InspectType.Textures;

	float ThumbnailWidth=40;
	float ThumbnailHeight=40;

	static List<TextureDetails> ActiveTextures=new List<TextureDetails>();
	static List<MaterialDetails> ActiveMaterials=new List<MaterialDetails>();
	static List<MeshDetails> ActiveMeshDetails=new List<MeshDetails>();

	static Vector2 textureListScrollPos=new Vector2(0,0);
	static Vector2 materialListScrollPos=new Vector2(0,0);
	static Vector2 meshListScrollPos=new Vector2(0,0);

	static int TotalTextureMemory=0;
	static int TotalMeshVertices=0;

	bool ctrlPressed=false;

	static int MinWidth=455;
     static ResourceChecker window;
    [MenuItem ("Window/Resource Checker")]
    static void Init ()
	{  
		TotalTextureMemory=0;
		TotalMeshVertices=0;
		textureListScrollPos=new Vector2(0,0);
		materialListScrollPos=new Vector2(0,0);
		meshListScrollPos=new Vector2(0,0);
		ActiveTextures=new List<TextureDetails>();
		ActiveMaterials=new List<MaterialDetails>();
		ActiveMeshDetails=new List<MeshDetails>();
        window = (ResourceChecker) EditorWindow.GetWindow (typeof (ResourceChecker));
		window.CheckResources();
		window.minSize=new Vector2(MinWidth,300);
    }
    
	void Update()
	{
		timer+=Time.deltaTime;
		if(timer>1)
		{
			Init ();
			timer=0;
		}
		
	}
	
    void OnGUI ()
	{
		if (GUILayout.Button("Refresh")) CheckResources();
		GUILayout.BeginHorizontal();
		GUILayout.Label("Materials "+ActiveMaterials.Count);
		GUILayout.Label("Textures "+ActiveTextures.Count+" - "+FormatSizeString(TotalTextureMemory));
		GUILayout.Label("Meshes "+ActiveMeshDetails.Count+" - "+TotalMeshVertices+" verts");
		GUILayout.EndHorizontal();
		ActiveInspectType=(InspectType)GUILayout.Toolbar((int)ActiveInspectType,inspectToolbarStrings);

		ctrlPressed=Event.current.control || Event.current.command;

		switch (ActiveInspectType)
		{
			case InspectType.Textures:
				ListTextures();
				break;
			case InspectType.Materials:
				ListMaterials();
				break;
			case InspectType.Meshes:
				ListMeshes();
				break;	


		}
	}


	int GetBitsPerPixel(TextureFormat format)
	{
		switch (format)
		{
			case TextureFormat.Alpha8: //	 Alpha-only texture format.
				return 8;
			case TextureFormat.ARGB4444: //	 A 16 bits/pixel texture format. Texture stores color with an alpha channel.
				return 16;
			case TextureFormat.RGB24:	// A color texture format.
				return 24;
			case TextureFormat.RGBA32:	//Color with an alpha channel texture format.
				return 32;
			case TextureFormat.ARGB32:	//Color with an alpha channel texture format.
				return 32;
			case TextureFormat.RGB565:	//	 A 16 bit color texture format.
				return 16;
			case TextureFormat.DXT1:	// Compressed color texture format.
				return 4;
			case TextureFormat.DXT5:	// Compressed color with alpha channel texture format.
				return 8;
			/*
			case TextureFormat.WiiI4:	// Wii texture format.
			case TextureFormat.WiiI8:	// Wii texture format. Intensity 8 bit.
			case TextureFormat.WiiIA4:	// Wii texture format. Intensity + Alpha 8 bit (4 + 4).
			case TextureFormat.WiiIA8:	// Wii texture format. Intensity + Alpha 16 bit (8 + 8).
			case TextureFormat.WiiRGB565:	// Wii texture format. RGB 16 bit (565).
			case TextureFormat.WiiRGB5A3:	// Wii texture format. RGBA 16 bit (4443).
			case TextureFormat.WiiRGBA8:	// Wii texture format. RGBA 32 bit (8888).
			case TextureFormat.WiiCMPR:	//	 Compressed Wii texture format. 4 bits/texel, ~RGB8A1 (Outline alpha is not currently supported).
				return 0;  //Not supported yet
			*/
			case TextureFormat.PVRTC_RGB2://	 PowerVR (iOS) 2 bits/pixel compressed color texture format.
				return 2;
			case TextureFormat.PVRTC_RGBA2://	 PowerVR (iOS) 2 bits/pixel compressed with alpha channel texture format
				return 2;
			case TextureFormat.PVRTC_RGB4://	 PowerVR (iOS) 4 bits/pixel compressed color texture format.
				return 4;
			case TextureFormat.PVRTC_RGBA4://	 PowerVR (iOS) 4 bits/pixel compressed with alpha channel texture format
				return 4;
			case TextureFormat.ETC_RGB4://	 ETC (GLES2.0) 4 bits/pixel compressed RGB texture format.
				return 4;
			case TextureFormat.ATC_RGB4://	 ATC (ATITC) 4 bits/pixel compressed RGB texture format.
				return 4;
			case TextureFormat.ATC_RGBA8://	 ATC (ATITC) 8 bits/pixel compressed RGB texture format.
				return 8;
			case TextureFormat.BGRA32://	 Format returned by iPhone camera
				return 32;
			case TextureFormat.ATF_RGB_DXT1://	 Flash-specific RGB DXT1 compressed color texture format.
			case TextureFormat.ATF_RGBA_JPG://	 Flash-specific RGBA JPG-compressed color texture format.
			case TextureFormat.ATF_RGB_JPG://	 Flash-specific RGB JPG-compressed color texture format.
				return 0; //Not supported yet
		}
		return 0;
	}

	int CalculateTextureSizeBytes(Texture tTexture)
	{

		int tWidth=tTexture.width;
		int tHeight=tTexture.height;
		if (tTexture is Texture2D)
		{
			Texture2D tTex2D=tTexture as Texture2D;
		 	int bitsPerPixel=GetBitsPerPixel(tTex2D.format);
			int mipMapCount=tTex2D.mipmapCount;
			int mipLevel=1;
			int tSize=0;
			while (mipLevel<=mipMapCount)
			{
				tSize+=tWidth*tHeight*bitsPerPixel/8;
				tWidth=tWidth/2;
				tHeight=tHeight/2;
				mipLevel++;
			}
			return tSize;
		}

		if (tTexture is Cubemap)
		{
			Cubemap tCubemap=tTexture as Cubemap;
		 	int bitsPerPixel=GetBitsPerPixel(tCubemap.format);
			return tWidth*tHeight*6*bitsPerPixel/8;
		}
		return 0;
	}


	void SelectObject(Object selectedObject,bool append)
	{
		if (append)
		{
			List<Object> currentSelection=new List<Object>(Selection.objects);
			// Allow toggle selection
			if (currentSelection.Contains(selectedObject)) currentSelection.Remove(selectedObject);
			else currentSelection.Add(selectedObject);

			Selection.objects=currentSelection.ToArray();
		}
		else Selection.activeObject=selectedObject;
	}

	void SelectObjects(List<Object> selectedObjects,bool append)
	{
		if (append)
		{
			List<Object> currentSelection=new List<Object>(Selection.objects);
			currentSelection.AddRange(selectedObjects);
			Selection.objects=currentSelection.ToArray();
		}
		else Selection.objects=selectedObjects.ToArray();
	}

	void ListTextures()
	{
		textureListScrollPos = EditorGUILayout.BeginScrollView(textureListScrollPos);
		try
		{
		foreach (TextureDetails tDetails in ActiveTextures)
		{			

			GUILayout.BeginHorizontal ();
			GUILayout.Box(tDetails.texture, GUILayout.Width(ThumbnailWidth), GUILayout.Height(ThumbnailHeight));
			
			if(tDetails.texture==null)
				Init();
			
			if(tDetails!=null)
			try
			{	
				if(tDetails.texture!=null)
				if(GUILayout.Button(tDetails.texture.name,GUILayout.Width(150)))
				{
					SelectObject(tDetails.texture,ctrlPressed);
				
				
					string sizeLabel=""+tDetails.texture.width+"x"+tDetails.texture.height;
					if (tDetails.isCubeMap) sizeLabel+="x6";
					sizeLabel+=" - "+tDetails.mipMapCount+"mip";
					sizeLabel+="\n"+FormatSizeString(tDetails.memSizeKB)+" - "+tDetails.format+"";
		
					GUILayout.Label (sizeLabel,GUILayout.Width(120));
				}
				if(GUILayout.Button(tDetails.FoundInMaterials.Count+" Mat",GUILayout.Width(50)))
				{
					SelectObjects(tDetails.FoundInMaterials,ctrlPressed);
				}
	
				if(GUILayout.Button(tDetails.FoundInRenderers.Count+" GO",GUILayout.Width(50)))
				{
					List<Object> FoundObjects=new List<Object>();
					foreach (Renderer renderer in tDetails.FoundInRenderers) FoundObjects.Add(renderer.gameObject);
					SelectObjects(FoundObjects,ctrlPressed);
				}
	
				GUILayout.EndHorizontal();	
			}
			catch
			{
				
			}
		}
		if (ActiveTextures.Count>0)
		{
			GUILayout.BeginHorizontal ();
			GUILayout.Box(" ",GUILayout.Width(ThumbnailWidth),GUILayout.Height(ThumbnailHeight));

			if(GUILayout.Button("Select All",GUILayout.Width(150)))
			{
				List<Object> AllTextures=new List<Object>();
				foreach (TextureDetails tDetails in ActiveTextures) AllTextures.Add(tDetails.texture);
				SelectObjects(AllTextures,ctrlPressed);
			}
			EditorGUILayout.EndHorizontal();
			
		}
				}
		catch{}
		EditorGUILayout.EndScrollView();
	
    }

	void ListMaterials()
	{
		materialListScrollPos = EditorGUILayout.BeginScrollView(materialListScrollPos);

		foreach (MaterialDetails tDetails in ActiveMaterials)
		{			
			if (tDetails.material!=null)
			{
				GUILayout.BeginHorizontal ();

				if (tDetails.material.mainTexture!=null) GUILayout.Box(tDetails.material.mainTexture, GUILayout.Width(ThumbnailWidth), GUILayout.Height(ThumbnailHeight));
				else	
				{
					GUILayout.Box("n/a",GUILayout.Width(ThumbnailWidth),GUILayout.Height(ThumbnailHeight));
				}

				if(GUILayout.Button(tDetails.material.name,GUILayout.Width(150)))
				{;
					SelectObject(tDetails.material,ctrlPressed);
				}

				if(GUILayout.Button(tDetails.FoundInRenderers.Count+" GO",GUILayout.Width(50)))
				{
					List<Object> FoundObjects=new List<Object>();
					foreach (Renderer renderer in tDetails.FoundInRenderers) FoundObjects.Add(renderer.gameObject);
					SelectObjects(FoundObjects,ctrlPressed);
				}


				GUILayout.EndHorizontal();	
			}
		}
		EditorGUILayout.EndScrollView();		
    }

	void ListMeshes()
	{
		meshListScrollPos = EditorGUILayout.BeginScrollView(meshListScrollPos);

		foreach (MeshDetails tDetails in ActiveMeshDetails)
		{			
			if (tDetails.mesh!=null)
			{
				GUILayout.BeginHorizontal ();
				/*
				if (tDetails.material.mainTexture!=null) GUILayout.Box(tDetails.material.mainTexture, GUILayout.Width(ThumbnailWidth), GUILayout.Height(ThumbnailHeight));
				else	
				{
					GUILayout.Box("n/a",GUILayout.Width(ThumbnailWidth),GUILayout.Height(ThumbnailHeight));
				}
				*/

				if(GUILayout.Button(tDetails.mesh.name,GUILayout.Width(150)))
				{
					SelectObject(tDetails.mesh,ctrlPressed);
				}
				string sizeLabel=""+tDetails.mesh.vertexCount+" vert";

				GUILayout.Label (sizeLabel,GUILayout.Width(100));


				if(GUILayout.Button(tDetails.FoundInMeshFilters.Count+" GO",GUILayout.Width(50)))
				{
					List<Object> FoundObjects=new List<Object>();
					foreach (MeshFilter meshFilter in tDetails.FoundInMeshFilters) FoundObjects.Add(meshFilter.gameObject);
					SelectObjects(FoundObjects,ctrlPressed);
				}


				GUILayout.EndHorizontal();	
			}
		}
		EditorGUILayout.EndScrollView();		
    }

	string FormatSizeString(int memSizeKB)
	{
		if (memSizeKB<1024) return ""+memSizeKB+"k";
		else
		{
			float memSizeMB=((float)memSizeKB)/1024.0f;
			return memSizeMB.ToString("0.00")+"Mb";
		}
	}


	TextureDetails FindTextureDetails(Texture tTexture)
	{
		foreach (TextureDetails tTextureDetails in ActiveTextures)
		{
			if (tTextureDetails.texture==tTexture) return tTextureDetails;
		}
		return null;

	}

	MaterialDetails FindMaterialDetails(Material tMaterial)
	{
		foreach (MaterialDetails tMaterialDetails in ActiveMaterials)
		{
			if (tMaterialDetails.material==tMaterial) return tMaterialDetails;
		}
		return null;

	}

	MeshDetails FindMeshDetails(Mesh tMesh)
	{
		foreach (MeshDetails tMeshDetails in ActiveMeshDetails)
		{
			if (tMeshDetails.mesh==tMesh) return tMeshDetails;
		}
		return null;

	}


	void CheckResources()
	{
		ActiveTextures.Clear();
		ActiveMaterials.Clear();
		ActiveMeshDetails.Clear();

		Renderer[] renderers = (Renderer[]) FindObjectsOfType(typeof(Renderer));
		//Debug.Log("Total renderers "+renderers.Length);
		foreach (Renderer renderer in renderers)
		{
			//Debug.Log("Renderer is "+renderer.name);
			foreach (Material material in renderer.sharedMaterials)
			{

				MaterialDetails tMaterialDetails=FindMaterialDetails(material);
				if (tMaterialDetails==null)
				{
					tMaterialDetails=new MaterialDetails();
					tMaterialDetails.material=material;
					ActiveMaterials.Add(tMaterialDetails);
				}
				tMaterialDetails.FoundInRenderers.Add(renderer);
			}
		}

		foreach (MaterialDetails tMaterialDetails in ActiveMaterials)
		{
			Material tMaterial=tMaterialDetails.material;
			foreach (Object obj in EditorUtility.CollectDependencies(new UnityEngine.Object[] {tMaterial}))
		    {
				if (obj is Texture)
				{
					Texture tTexture=obj as Texture;
					TextureDetails tTextureDetails=FindTextureDetails(tTexture);
					if (tTextureDetails==null)
					{
						tTextureDetails=new TextureDetails();
						tTextureDetails.texture=tTexture;
						tTextureDetails.isCubeMap=tTexture is Cubemap;

						int memSize=CalculateTextureSizeBytes(tTexture);

						tTextureDetails.memSizeKB=memSize/1024;
						TextureFormat tFormat=TextureFormat.RGBA32;
						int tMipMapCount=1;
						if (tTexture is Texture2D)
						{
							tFormat=(tTexture as Texture2D).format;
							tMipMapCount=(tTexture as Texture2D).mipmapCount;
						}
						if (tTexture is Cubemap)
						{
							tFormat=(tTexture as Cubemap).format;
						}

						tTextureDetails.format=tFormat;
						tTextureDetails.mipMapCount=tMipMapCount;
						ActiveTextures.Add(tTextureDetails);
					}
					tTextureDetails.FoundInMaterials.Add(tMaterial);
					foreach (Renderer renderer in tMaterialDetails.FoundInRenderers)
					{
						if (!tTextureDetails.FoundInRenderers.Contains(	renderer)) tTextureDetails.FoundInRenderers.Add(renderer);
					}
				}
			}
		}


		MeshFilter[] meshFilters = (MeshFilter[]) FindObjectsOfType(typeof(MeshFilter));

		foreach (MeshFilter tMeshFilter in meshFilters)
		{
			Mesh tMesh=tMeshFilter.sharedMesh;
			if (tMesh!=null)
			{
				MeshDetails tMeshDetails=FindMeshDetails(tMesh);
				if (tMeshDetails==null)
				{
					tMeshDetails=new MeshDetails();
					tMeshDetails.mesh=tMesh;
					ActiveMeshDetails.Add(tMeshDetails);
				}
				tMeshDetails.FoundInMeshFilters.Add(tMeshFilter);
			}
		}


		TotalTextureMemory=0;
		foreach (TextureDetails tTextureDetails in ActiveTextures) TotalTextureMemory+=tTextureDetails.memSizeKB;

		TotalMeshVertices=0;
		foreach (MeshDetails tMeshDetails in ActiveMeshDetails) TotalMeshVertices+=tMeshDetails.mesh.vertexCount;

		// Sort by size, descending
		ActiveTextures.Sort(delegate(TextureDetails details1, TextureDetails details2) {return details2.memSizeKB-details1.memSizeKB;});
		ActiveMeshDetails.Sort(delegate(MeshDetails details1, MeshDetails details2) {return details2.mesh.vertexCount-details1.mesh.vertexCount;});

	}


}

Thanks for this great tool, it’s so useful.
I have a question about texture sizes. Even though I set the “Texture Quality” = “Half Res” in Quality settings, the tool still displays the full resolution. Is this normal?

I think this is the case because in the Unity Editor they are fully loaded into memory… I think, while when in standalone they might not be. That or the script just looks at the original texture size.

This script is amazing though, especially for mobile development, where an accidentally loaded texture atlas can be the difference between your app working and not

OK, thanks…
And yes this is amazing… using this tool I have spot and reduced all my 10241024 textures to 256256 without much affecting overall quality.

Cool!

Amazing work man!
Thanks!!

Add support SkinnedMeshRenderer, see on GitHub

Really useful, thanks!

Quick question for the thread. When loading up a scene directly the Profiler says I’m using 1068 textures using 53.2 MB.
When loading the same scene from another scene, the Profiler says I’m using 1071 textures using 70.4 MB.

I used this tool (which is FANTASTIC, btw) in both cases, but the tool said that I was using the exact same amount of resources (textures, materials, objects).

I think the tool is working properly, but that means that there’s some (large) textures still around somewhere eating up memory.
Is there a way that I can track down those 3 extra textures lurking around in memory that the tool isn’t catching?

Thanks
-Mo

Freaking amazing utility. Can’t believe I never used it before. Now it is an integral part of our pipeline.

Looks very good, wish the profiler had a texture breakdown like this =)