Speed-up Unity2019 Terrains by a factor of 20x using Buffer.BlockCopy

Unity Terrain is a good Terrain renderer, but the API’s behind it are famously badly documented and rather clunky (most of the documentation still hasn’t been written, almost 10 years after it was launched). At Unite this year they were showing-off some of the “new Terrain” features/tools, all of which were aimed at artists, and look great.

But what about the Terrain itself? Unity 2019.2 and 2019.3 still have the ponderous old API and it seems we’re stuck with it for at least another few years. Today I found and fixed an issue in my custom Terrain-tools that took our Editor rendering from < 4 FPS back to normal realtime speeds.

The secret is to use Unity’s required float[,,] arrays (which Microsoft only partially supports in C#) but plug the gap in C# which causes them to be slow when interacting with Unity’s Serializer (which fires 6 times per frame in the Editor, magnifiying any slowdown considerably!)

NB: As far as I can tell, you cannot fix the Unity “serialize 6 times even if it’s not needed, where only 1 would have been fine” issue, because the methods to do that only exist on Custom EditorWindows, and not on Custom Inspectors. But it’s bad practice to be slowing-down the serializer anyway, so I’m happy with fixing MY code to run fast, and then stop worrying about the Unity Serialization layers being inefficient.

The problem: float[,,] isn’t supported by Unity

Unity requires you to use float[,,] for textures/splats/alphamaps on their terrain.

However, Unity has never supported multi-dimensional arrays in their engine (this is finally getting fixed sometime in 2020, I believe, with the new Serializer). So your data gets wiped every frame. Thats a pain when making Terrain-editing scripts.

The workaround is to implement Unity’s ISerializationCallbackReceiver interface, and provide the missing code that Unity doesn’t (i.e. serialize a float[,,]). The standard way of doing this is something like:

NB: I’m only showing half of the serialize/deserialize here, just to illustrate the point

[code language=”csharp”]
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
deltas = new float[_Serialize_2D_Length0, _Serialize_2D_Length1, _Serialize_2D_Length2];

/** NB: iterate in C#’s internal storage order for [,,] */
for (int i0 = 0; i0 &lt; _Serialize_2D_Length0; i0++)
for (int i1 = 0; i1 &lt; _Serialize_2D_Length1; i1++)
for( int i2 = 0; i2 &lt; _Serialize_2D_Length2; i2++ )
deltas[i0,i1,i2] = _Serialize_1DArray[i0 * _Serialize_2D_Length1 * _Serialize_2D_Length2
+ i1 * _Serialize_2D_Length2
+ i2];
}
}
[/code]

…which retrieves every cell in the float[,,] from a cell in a private float[] (which Unity DOES support and will auto-serialize for you).

The problem is that C# for-loops are extremely slow when used like this, simply because of the scale of the operation. For a typical Unity terrain, you’re copying up to 4096 x 4096 samples (your splatmap) with anywhere from 5 to 10 values for each. Each value is a 4-byte 32-bit float.

i.e. 4k x 4k x 10 x 4 == 640 MB of data

…which destroys your 100+ FPS frame-time, taking it to 1 FPS or worse.

You need to copy this data in a single call, not in 640,000,000 separate method calls.

But … how?

Array.Copy() to the rescue!

It doesn’t work. You can compile your C# class, and then the C# runtime will cry when you try to execute it:

RankException: Only single dimension arrays are supported here.

Bummer. In theory, Array.Copy() would have solved the problem – this is literally what it was designed for: bulk copying of large arrays without the overhead of doing millions of tiny copy-calls.

Try again … Buffer.BlockCopy()

Fortunately there’s another method in C# core that steps-in and saves us. I often find that when C# ties your hands behind your back, the reason it hasn’t been changed/updated/improved is that there’s a lesser-known behind-the-scenes low-level method that you can (ab)use to achieve what you need, and the language maintainers recommend you do that instead of them updating the mainstream stuff. Fair enough!

The one caveat with BlockCopy is that you need to tell it the size in bytes that you’re copying NOT the number of array items.

i.e.: [code language=”csharp”]Array.Copy( from, 0, to, 0, length )[/code]
becomes: [code language=”csharp”]Buffer.BlockCopy( from, 0, to, 0, 4 * length ) // if copying float, or int, or any of the other 32bit primitives[/code]

20x faster Terrain data handling

The modified serialization callback becomes:

[code language=”csharp”]
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
deltas = new float[_Serialize_2D_Length0, _Serialize_2D_Length1, _Serialize_2D_Length2];

int bytesPerFloat = 4;
Buffer.BlockCopy( _Serialize_2D, 0, deltas, 0, bytesPerFloat * deltas.GetLength( 0 ) * deltas.GetLength( 1 )*deltas.GetLength( 2 ) );
}
}
[/code]

Improving the Unity AssetStore: what I’d like to see in 2020

One of Unity3D’s greatest successes has been the Asset Store. Bursting to life 9 years ago, initially sounding a lot like an optimistic clone of Apple’s 2-year-old App Store (and boasting the same 70%/30% revenue share), it turned out to be so much more.

But Unity still struggles to figure out what it should look like and how it should work for users/purchasers. The raw content is the biggest determinant of the store’s success, but closely followed by the browsing and discovery experience – which have hardly improved at all (and in some ways have gone backwards) over this past decade.

Based on hundreds of purchases, and having launched and maintained some small assets on the store myself over the past 5 years, here’s what I’d like to see now.

The A-test for Unity Assets

Every asset-purchase page should have a section that answers the critical, machine-answerable, fully 100% automatable questions that matter to purchasers. There is no excuse to miss this out – these make a huge difference both to users, and to authors, and to Unity itself: they massively reduce the amount of refund requests, and increase the purchase volume due to increased buyer-confidence.

How well do your assets score on these? If you’re an author, do you publish all this information up-front (some do – their Full Description on the asset page is long and scrolly)

Art Assets

  • Min/avg/max verts per model in package
  • Top 3 shaders in package, with number of models that use each
  • Min/avg/max texture sizes in package
  • Total number of materials with unassigned textures/colors vs fully assigned
  • Total number of materials using Standard shader with Albedo, Metallic, Normal, Roughness maps assigned
  • Total number of models with LODs vs number of models without LODs
  • Min/Max LOD levels for models with at least one LOD
  • Number of prefabs in package that have same prefix-name as an FBX/model file

Code Assets

  • Number of files that include source code (C#) vs number without source code
  • Number of (Unity official) Unit-tests in package
  • (one line for each Unity version): Num Errors, Num Warnings, when installing the project
  • (one line for each Unity version): Num Errors, Num Warnings, when opening the marked demo-scene
  • (one line for each Unity version): Num Errors, Num Warnings, when pressing play in the marked demo-scene
  • (one line for each Unity version): Number of Unit tests passed, number failed

All Assets

  • Number of demo scenes in package
  • PDF documentation in package
  • Time since Author’s last edit of package (upload)
  • Time since Author’s last discussion of package (meta files + comment threads)

Self-reported / author-tagged info

All the above was easily automatable by Unity (Apart from “issue reports”, which Unity keeps private, I’ve written scripts myself that do all of them!)

What happens when we extend the Asset Store Publisher Upload tool to let authors add extra info that Unity can then collate and publish? Well…

Art Assets – author controlled

  • Render-pipelines supported: Default?, URP?, HDRP? (tickboxes)
  • Player platforms compatible: Windows, PS, XB, VR, iOS, Android, WebGL (tickboxes: “compatible” means “author expects it to work, but isn’t actively testing that platform – it may not work out of the box”)
  • Player platforms supported: Windows, PS, XB, VR, iOS, Android, WebGL (tickboxes: “supported” means “author actively tests on this platform and promises it will work out of the box (or fixed very rapidly)”)

Nice-to-have’s only Unity can provide

  • Ask-the-author question box on purchase page (SO MANY TIMES people need to check if an asset supports X or Y, or ask the author if they include something, and Unity still provides no way for the purchaser to do this)
    …with answered-questions automatically appearing for all to see (just like Amazon has done for the past 15+ years)
  • Number of reported issues per Unity version (OR: star-rating per unity version)
  • Number of refund requests (successful + unsuccessful) by Unity version and/or package version

2020 future-looking awesome “make everyone rich” features

  • When author uploads the asset, choose a Demo scene that will be auto-built as a WebGL build and embedded in Asset Store page (if build succeeds)

Google’s struggles with UX design, 2019 edition

I witnessed three basic flaws in latest Android this week – all of them redolent of bad UX design on Google’s part – and surprising in an almost 10 years old OS (none of them are new features).