This member has provided no bio about themself...

RSS My Blogs

Determine CPU and Count in Unity.

travisbulford Blog


Why

OK as an introduction let me start by explaining why we want to identify the CPU in Unity. This is predominately for Windows Store but its quite passable it will be useful later as platform architectures expand.
We are close to releasing our game Toxic Bunny HD on the Windows Store and Phone. Due to a technical hiccup outside our control we have found ourselves with a little more time then we expected. With that time we have been dreaming up improvements to make the project better.
When we moved the game to the Windows Store obviously we had to target the lowest common denominator; the ARM CPU's on tablets. To achieve this we have had to remove a lot of things such as varlet managed ropes and slime. As well as some lighting and other cool effects that work perfectly on a desktop Windows 8 machine.
Naturally with enough time on our hands we decided we want those features back and the first thing we had to do was identify the CPU the game is running on. We could of course create 2 completely different Unity projects but that will lead to unpleasant version control.

The Meat.

So we decided to create a c++ DLL that can call the Windows function GetNativeSystemInfo this will return the CPU type as well as the number of cores which for now we will just consider to be the number of cpu's. For our immediate need we could see no use to the number of cores, but for the tiny little bit of effort we included them anyway.

Visual Studio

See below the code for the .h and .cpp files
UTOOLSSystem.h

#pragma once

extern "C" {
 __declspec(dllexport) int GetCPUType();
 __declspec(dllexport) int GetCPUCount();
};

UTOOLSSystem.cpp

#include "stdafx.h"
#include "UTOOLSSystem.h"
#include <windows.h>
#include <VersionHelpers.h>

__declspec(dllexport) int GetCPUType(){
 _SYSTEM_INFO info;
 GetNativeSystemInfo(&amp;info);
 if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM){
  return 0;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ALPHA){
  return 1;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ALPHA64){
  return 2;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64){
  return 3;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA32_ON_WIN64){
  return 4;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64){
  return 5;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL){
  return 6;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_MIPS){
  return 7;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_MSIL){
  return 8;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_NEUTRAL){
  return 9;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_PPC){
  return 10;
 }
 else if (info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_SHX){
  return 11;
 }
 return -1;
}

__declspec(dllexport) int GetCPUCount(){
 _SYSTEM_INFO info;
 GetNativeSystemInfo(&amp;info);
 return info.dwNumberOfProcessors;
}

Unity

using UnityEngine;
using System.Runtime.InteropServices;

namespace UTOOLS 
{

 public class System 
 {
  [DllImport ("UTOOLSSystem")]
  private static extern int GetCPUType ();
  [DllImport ("UTOOLSSystem")]
  private static extern int GetCPUCount ();
  
  public enum CPU 
  {
   ARM,
   ALPHA,
   ALPHA64,
   AMD64,
   IA32_ON_WIN64,
   IA64,
   INTEL,
   MIPS,
   MSIL,
   NEUTRAL,
   PPC,
   SHX,
   UNKNOWN
  }
 
  public static CPU[] cpus=new CPU[]
  {
   CPU.ARM,
   CPU.ALPHA,
   CPU.ALPHA64,
   CPU.AMD64,
   CPU.IA32_ON_WIN64,
   CPU.IA64,
   CPU.INTEL,
   CPU.MIPS,
   CPU.MSIL,
   CPU.NEUTRAL,
   CPU.PPC,
   CPU.SHX
  };
  
  
  public static CPU CpuType()
  {
   int cpu=GetCPUType();
   if (cpu>=0 &amp;&amp; cpu<cpus.Length)
   {
    return cpus[cpu];
   }
   return CPU.UNKNOWN;
  }

  public static int CpuCount()
  {
   return GetCPUCount();
  }
 }
}

Other things needed.

OK so that's pretty much the operating version. The methods called are guaranteed to work on Windows Phone too but for our version we created empty methods that return ARM and 1 CPU for the Windows Phone. We have 3 versions of the DLL added one in Plugins one in Plugins\WP8 and Plugins\Metro. For this version the base and Metro are identical.
Down the line we will split the solution up to work on MAC, iOS, Android and so on. But since the intimidate need was resolved we leaving it here.
One downside is you will need Unity Pro to use the DLL's I believe the feature of importing a DLL from c++ is a pro one.

Download

It is our intention to turn this into a free Unity asset on the asset store when we get the time to package it properly. But in the mean time here is a download link for anyone that could use.

Download

Also posted here

Porting Toxic Bunny HD to Windows 8 in Unity

travisbulford Blog

OK so few of you might know this but we actually started the whole Unity port with Windows 8 in mind from the beginning. In Feb we were chatting to Dave Russel from Microsoft SA. He brought the Windows 8 opportunity to our attention. That's not to say we haven't seen many other advantages porting Toxic Bunny HD to Unity along the way. But now we on the last leg of that process. Porting to Windows 8 mobile.

Just to clarify I am personally finding the branding around Windows 8 mobile space a little confusing. So to clarify I am talking about Mobile Phone OS Win 8.

Our first run hit a brick wall, black screen nothing happened eventually a heap overflow. Not a great start, but not uncommon with mobile ports. We had the same issues when porting to Android for the GameStick. We have already shaved the draw calls down to about 20 so we have confidence there, so what next.

Where you live counts !?

What caught us off guard was a few complete gotchas. The biggest relating to differences in the Microsoft CLR and the Mono one Unity usually uses. For our game we have a custom sprite atlas. We store the UV positions in a nice and neat text file like so.

0,0.0,0.0,0.09765625,0.09765625 0,0.0,0.109375,0.09765625,0.09765625 0,0.0,0.21875,0.09765625,0.09765625 0,0.0,0.328125,0.09765625,0.09765625 0,0.0,0.4375,0.09765625,0.09765625 0,0.0,0.546875,0.09765625,0.09765625 0,0.0,0.65625,0.09765625,0.09765625 0,0.0,0.765625,0.09765625,0.09765625

Arguably not elegant but it gets the job done and is only an "on load" issue so not something we plan to change. Its also the source of our first issue. We get a Number format error exception (caught in Visual Studio when running the game from there, highly recommended to run out of VS and Unity during your debugging phase not just one or the other). So comes down to the following lines of code.

csharp code:
u=float.Parse(parts[1]);
v=1f-float.Parse(parts[2]);

The solution was struggling to convert "0.0" into a float. It appears here in South Africa we supposedly use a "," as out decimal point. I will admit that's news to me as a South African but non the less the Microsoft framework was correctly looking for "0,0" in accordance with our country code. I am grateful we caught this here as apposed to finding out later when run in another country that uses a ",".

This is easily solved by changing all parse methods to the following (This works fine on the Win 8 phones and standard desktop).

csharp code:
u=float.Parse(parts[1],  System.Globalization.CultureInfo.InvariantCulture.NumberFormat); v=1f-float.Parse(parts[2], System.Globalization.CultureInfo.InvariantCulture.NumberFormat);

Once this was changed we got the game up and running at a stunning 1 fps. Its a start even if a slow one. The next trick was to find the main causes of pain and steadily remove them. Below is a snap shot from the profiler, edited to fit the screen better for the blog.

Starting Picture

I still do not know what "Overhead" is I can only hope its an overhead that goes away when I am not profiling.

Of interest is the highlighted line. Mesh.CreateVBO. There are many ways to cause these calls. In our case the issue was scaling. Each time a sprite changes we scale the holding box in case that sprite is a different size on the screen.

csharp code:
transform.localScale=sl.size*gfxscale;

Unity creates a new mesh each time we scale. This is an unfortunate but seemingly necessary thing for Unity to do. Essentially though we hardly ever actually need to re scale the sprites are almost always exactly the same size. The following code removed this issue.

csharp code:
Vector3 newScale=sl.size*gfxscale;
if (transform.localScale!=newScale){
transform.localScale=newScale;
 

So only change it when you need to dropped 16-32ms from our process. This extended to all many of other areas esp nGUI ui where we scaled health bars and life bars each frame. These optimizations gained us another 10ms or so per frame.

Small sound issue worth mentioning.

When you run the application from Unity onto your phone any streamed music will stutter. This goes away when you run from VS with the application in Master mode. Nuff said.

OnGUI nooooo!

Before I started using Unity on mobile I had no idea why people were so against the OnGUI methods. I thought it was mostly about DrawCalls. Man did this experience show that to be wrong. OnGUI added more then 14ms just for preparation. The total OnGUI cost for a FPS counter was 22ms. All OnGUI gui is removed and has been replaced with nGUI.

Mob Updates.

On most Toxic Bunny levels there are about 600-800 monsters. (Getting progressively more as you move to later levels. To reduce the overhead all monsters check if they are in range before running there script. The code looks as follows.

csharp code:
public void FixedUpdate() {
if (!shouldThink()){
return ;  

This seamed good enough at the time. But now we see 300+ Rat scripts been called costing us 8-12ms. With 4 mobs on the level the total costs there added up to about 40-60ms. The answer is to switch the script off completely when out of range as apposed to having a fall through function. We poly-morphed the shouldThink function and added one new Class to each monster object.

csharp code:
// This code from a ThinkAgain script attached to the monster
public void OnBecameVisible() {
monsterScript.enabled=true;
}// This code from the monster base class
s
public override bool shouldThink(){
bool soThinking=base.shouldThink();
if (!soThinking){
this.enabled=false;
}
retur

Now when a monster is far enough away it should stop thinking we actually disable the script. The script then automatically is enabled when that monster returns into the view as Unity will call OnBecameVisible for us that will enable the script.

Results.

As you can see out end of day one looks much better. That having been said we now need to add some of the parts back into the game. Some things that needed OnGUI must be re written to nGUI (This was to be expected)

End of day one profile

From despair to pretty pleased with the results. We have learned a lot about optimizing for Unity and esp Unity on Windows 8 Mobile and are now spending time on controls and scaling features for small phone screens.

This also blogged here Blog.celestial-games.com